Skip to content

Commit

Permalink
Merge pull request #94 from /issues/91
Browse files Browse the repository at this point in the history
Add new bundled signatures for disallowing internal runtime APIs + fix class loading order bugs. This closes #91
  • Loading branch information
uschindler committed Dec 27, 2015
2 parents 25ec866 + b9b0b5d commit abab494
Show file tree
Hide file tree
Showing 7 changed files with 340 additions and 41 deletions.
12 changes: 12 additions & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@
<signatures>
<bundled name="jdk-unsafe-${jdk.version}"/>
<bundled name="jdk-deprecated-${jdk.version}"/>
<bundled name="jdk-internal-${jdk.version}"/>
<bundled name="jdk-system-out"/>
<bundled name="jdk-reflection"/>
</signatures>
Expand All @@ -480,6 +481,7 @@
<signatures>
<bundled name="jdk-unsafe-${build.java.runtime}"/>
<bundled name="jdk-deprecated-${build.java.runtime}"/>
<bundled name="jdk-internal-${build.java.runtime}"/>
<bundled name="jdk-system-out"/>
<bundled name="jdk-reflection"/>
</signatures>
Expand Down Expand Up @@ -579,6 +581,7 @@
<include name="src/**"/>
<exclude name="README.md"/>
<exclude name="**/jdk-deprecated-*.txt"/>
<exclude name="**/jdk-internal-*.txt"/>
</fileset>
</rat:report>
<!-- now print the output, for review -->
Expand Down Expand Up @@ -612,6 +615,15 @@
<groovy classpathref="path.tools-run" src="${groovy-tools.dir}/generate-deprecated.groovy"/>
</target>

<target name="generate-internal" depends="compile-tools" description="Generates deprecated API signature lists for current JDK">
<mkdir dir="${signatures.dir}"/>
<property name="internal.output.file" location="${signatures.dir}/jdk-internal-${build.java.runtime}.txt"/>
<java classpathref="path.tools-run" classname="de.thetaphi.forbiddenapis.InternalApiGen" fork="false">
<arg value="${build.java.runtime}"/>
<arg file="${internal.output.file}"/>
</java>
</target>

<target name="-generate-test-classes-init">
<condition property="-gen.sunmisc">
<available classname="sun.misc.BASE64Encoder"/>
Expand Down
101 changes: 60 additions & 41 deletions src/main/java/de/thetaphi/forbiddenapis/Checker.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
Expand All @@ -68,13 +69,13 @@ public static enum Option {

final Logger logger;

final Set<File> bootClassPathJars;
final Set<String> bootClassPathDirs;
final NavigableSet<String> runtimePaths;

final ClassLoader loader;
final java.lang.reflect.Method method_Class_getModule, method_Module_getResourceAsStream;
final EnumSet<Option> options;

// key is the internal name (slashed):
// key is the binary name (dotted):
final Map<String,ClassSignature> classesToCheck = new HashMap<String,ClassSignature>();
// key is the binary name (dotted):
final Map<String,ClassSignature> classpathClassCache = new HashMap<String,ClassSignature>();
Expand Down Expand Up @@ -142,48 +143,57 @@ public Checker(Logger logger, ClassLoader loader, EnumSet<Option> options) {
this.method_Class_getModule = method_Class_getModule;
this.method_Module_getResourceAsStream = method_Module_getResourceAsStream;

final Set<File> bootClassPathJars = new LinkedHashSet<File>();
final Set<String> bootClassPathDirs = new LinkedHashSet<String>();
final NavigableSet<String> runtimePaths = new TreeSet<String>();

// fall back to legacy behavior:
if (!isSupportedJDK) {
try {
final URL objectClassURL = loader.getResource(AsmUtils.getClassResourceName(Object.class.getName()));
if (objectClassURL != null && "jrt".equalsIgnoreCase(objectClassURL.getProtocol())) {
// this is Java 9 with modules!
// this is Java 9 without Jigsaw! TODO: Remove this heuristic once final JDK 9 is out!
isSupportedJDK = true;
} else {
String javaHome = System.getProperty("java.home");
if (javaHome != null) {
javaHome = new File(javaHome).getCanonicalPath();
if (!javaHome.endsWith(File.separator)) {
javaHome += File.separator;
}
runtimePaths.add(javaHome);
}
// Scan the runtime's bootclasspath, too! This is needed because
// Apple's JDK 1.6 has the main rt.jar outside ${java.home}!
final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
if (rb.isBootClassPathSupported()) {
final String cp = rb.getBootClassPath();
final StringTokenizer st = new StringTokenizer(cp, File.pathSeparator);
while (st.hasMoreTokens()) {
final File f = new File(st.nextToken());
File f = new File(st.nextToken().trim());
if (f.isFile()) {
bootClassPathJars.add(f.getCanonicalFile());
} else if (f.isDirectory()) {
f = f.getParentFile();
}
if (f.exists()) {
String fp = f.getCanonicalPath();
if (!fp.endsWith(File.separator)) {
fp += File.separator;
}
bootClassPathDirs.add(fp);
runtimePaths.add(fp);
}
}
}
isSupportedJDK = !(bootClassPathJars.isEmpty() && bootClassPathDirs.isEmpty());
isSupportedJDK = !runtimePaths.isEmpty();
if (!isSupportedJDK) {
logger.warn("Boot classpath appears to be empty; marking runtime as not suppported.");
logger.warn("Boot classpath appears to be empty or ${java.home} not defined; marking runtime as not suppported.");
}
}
} catch (IOException ioe) {
logger.warn("Cannot scan boot classpath due to IO exception; marking runtime as not suppported: " + ioe);
logger.warn("Cannot scan boot classpath and ${java.home} due to IO exception; marking runtime as not suppported: " + ioe);
isSupportedJDK = false;
bootClassPathJars.clear();
bootClassPathDirs.clear();
runtimePaths.clear();
}
}
this.bootClassPathJars = Collections.unmodifiableSet(bootClassPathJars);
this.bootClassPathDirs = Collections.unmodifiableSet(bootClassPathDirs);
this.runtimePaths = runtimePaths;
// logger.info("Runtime paths: " + runtimePaths);

if (isSupportedJDK) {
try {
Expand Down Expand Up @@ -225,27 +235,27 @@ private InputStream getBytecodeFromJigsaw(String classname) {
}
}

private boolean isRuntimePath(URL url) throws IOException {
if (!"file".equalsIgnoreCase(url.getProtocol())) {
return false;
}
try {
final String path = new File(url.toURI()).getCanonicalPath();
final String lookup = runtimePaths.floor(path);
return lookup != null && path.startsWith(lookup);
} catch (URISyntaxException e) {
// should not happen, but if it's happening, it's definitely not a below our paths
return false;
}
}

private boolean isRuntimeClass(URLConnection conn) throws IOException {
final URL url = conn.getURL();
if ("file".equalsIgnoreCase(url.getProtocol())) {
try {
final String path = new File(url.toURI()).getCanonicalPath();
for (final String bcpDir : bootClassPathDirs) {
if (path.startsWith(bcpDir)) {
return true;
}
}
} catch (URISyntaxException use) {
// ignore (should not happen, but if it's happening, it's definitely not a runtime class)
}
if (isRuntimePath(url)) {
return true;
} else if ("jar".equalsIgnoreCase(url.getProtocol()) && conn instanceof JarURLConnection) {
final URL jarUrl = ((JarURLConnection) conn).getJarFileURL();
if ("file".equalsIgnoreCase(jarUrl.getProtocol())) try {
final File jarFile = new File(jarUrl.toURI()).getCanonicalFile();
return bootClassPathJars.contains(jarFile);
} catch (URISyntaxException use) {
// ignore (should not happen, but if it's happening, it's definitely not a runtime class)
}
return isRuntimePath(jarUrl);
} else if ("jrt".equalsIgnoreCase(url.getProtocol())) {
// all 'jrt:' URLs refer to a module in the Java 9+ runtime (see http://openjdk.java.net/jeps/220)
// This may still be different with module system. We support both variants for now.
Expand All @@ -255,7 +265,7 @@ private boolean isRuntimeClass(URLConnection conn) throws IOException {
return false;
}

/** Reads a class (binary name) from the given {@link ClassLoader}. */
/** Reads a class (binary name) from the given {@link ClassLoader}. If not found there, falls back to the list of classes to be checked. */
private ClassSignature getClassFromClassLoader(final String clazz) throws ClassNotFoundException,IOException {
final ClassSignature c;
if (classpathClassCache.containsKey(clazz)) {
Expand Down Expand Up @@ -288,6 +298,12 @@ private ClassSignature getClassFromClassLoader(final String clazz) throws ClassN
return c;
}
}
// try to get class from our list of classes we are checking:
c = classesToCheck.get(clazz);
if (c != null) {
classpathClassCache.put(clazz, c);
return c;
}
// all failed => the class does not exist!
classpathClassCache.put(clazz, null);
throw new ClassNotFoundException(clazz);
Expand All @@ -300,10 +316,9 @@ public ClassSignature lookupRelatedClass(String internalName) {
if (type.getSort() != Type.OBJECT) {
return null;
}
ClassSignature c = classesToCheck.get(internalName);
if (c == null) try {
try {
// use binary name, so we need to convert:
c = getClassFromClassLoader(type.getClassName());
return getClassFromClassLoader(type.getClassName());
} catch (ClassNotFoundException cnfe) {
if (options.contains(Option.FAIL_ON_MISSING_CLASSES)) {
throw new WrapperRuntimeException(cnfe);
Expand All @@ -312,11 +327,11 @@ public ClassSignature lookupRelatedClass(String internalName) {
"The referenced class '%s' cannot be loaded. Please fix the classpath!",
type.getClassName()
));
return null;
}
} catch (IOException ioe) {
throw new WrapperRuntimeException(ioe);
}
return c;
}

/** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */
Expand Down Expand Up @@ -498,7 +513,8 @@ public void addClassToCheck(final InputStream in) throws IOException {
} finally {
in.close();
}
classesToCheck.put(reader.getClassName(), new ClassSignature(reader, false, true));
final String binaryName = Type.getObjectType(reader.getClassName()).getClassName();
classesToCheck.put(binaryName, new ClassSignature(reader, false, true));
}

/** Parses and adds a class from the given file to the list of classes to check. Does not log anything. */
Expand Down Expand Up @@ -578,6 +594,9 @@ public void run() throws ForbiddenApiException {
}
}

// Cleanup cache to get statistics right:
classpathClassCache.keySet().removeAll(classesToCheck.keySet());

final String message = String.format(Locale.ENGLISH,
"Scanned %d (and %d related) class file(s) for forbidden API invocations (in %.2fs), %d error(s).",
classesToCheck.size(), classesToCheck.isEmpty() ? 0 : classpathClassCache.size(), (System.currentTimeMillis() - start) / 1000.0, errors);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file contains API signatures that are marked as internal in Java.
# It is provided here for reference, but can easily regenerated by executing from the source folder of forbidden-apis:
# $ ant generate-internal

# This file contains all internal packages listed in Security.getProperty("package.access") of Java version 1.6 (extracted from build 1.6.0_32).

@defaultMessage non-public internal runtime class in Java 1.6

com.sun.imageio.**
com.sun.org.apache.xalan.internal.utils.**
com.sun.org.apache.xerces.internal.utils.**
com.sun.xml.internal.bind.**
com.sun.xml.internal.ws.**
sun.**
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This file contains API signatures that are marked as internal in Java.
# It is provided here for reference, but can easily regenerated by executing from the source folder of forbidden-apis:
# $ ant generate-internal

# This file contains all internal packages listed in Security.getProperty("package.access") of Java version 1.7 (extracted from build 1.7.0_80).

@defaultMessage non-public internal runtime class in Java 1.7

com.sun.corba.se.**
com.sun.imageio.**
com.sun.istack.internal.**
com.sun.java.accessibility.**
com.sun.jmx.**
com.sun.naming.internal.**
com.sun.org.apache.bcel.internal.**
com.sun.org.apache.regexp.internal.**
com.sun.org.apache.xalan.internal.extensions.**
com.sun.org.apache.xalan.internal.lib.**
com.sun.org.apache.xalan.internal.res.**
com.sun.org.apache.xalan.internal.templates.**
com.sun.org.apache.xalan.internal.utils.**
com.sun.org.apache.xalan.internal.xslt.**
com.sun.org.apache.xalan.internal.xsltc.cmdline.**
com.sun.org.apache.xalan.internal.xsltc.compiler.**
com.sun.org.apache.xalan.internal.xsltc.trax.**
com.sun.org.apache.xalan.internal.xsltc.util.**
com.sun.org.apache.xerces.internal.**
com.sun.org.apache.xml.internal.res.**
com.sun.org.apache.xml.internal.security.**
com.sun.org.apache.xml.internal.serializer.utils.**
com.sun.org.apache.xml.internal.utils.**
com.sun.org.apache.xpath.internal.**
com.sun.org.glassfish.**
com.sun.proxy.**
com.sun.script.**
com.sun.xml.internal.**
oracle.jrockit.jfr.**
org.jcp.xml.dsig.internal.**
sun.**
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# This file contains API signatures that are marked as internal in Java.
# It is provided here for reference, but can easily regenerated by executing from the source folder of forbidden-apis:
# $ ant generate-internal

# This file contains all internal packages listed in Security.getProperty("package.access") of Java version 1.8 (extracted from build 1.8.0_60).

@defaultMessage non-public internal runtime class in Java 1.8

com.oracle.webservices.internal.**
com.oracle.xmlns.internal.**
com.sun.activation.registries.**
com.sun.browser.**
com.sun.corba.se.**
com.sun.glass.**
com.sun.imageio.**
com.sun.istack.internal.**
com.sun.java.accessibility.**
com.sun.javafx.**
com.sun.jmx.**
com.sun.media.**
com.sun.media.sound.**
com.sun.naming.internal.**
com.sun.openpisces.**
com.sun.org.apache.bcel.internal.**
com.sun.org.apache.regexp.internal.**
com.sun.org.apache.xalan.internal.extensions.**
com.sun.org.apache.xalan.internal.lib.**
com.sun.org.apache.xalan.internal.res.**
com.sun.org.apache.xalan.internal.templates.**
com.sun.org.apache.xalan.internal.utils.**
com.sun.org.apache.xalan.internal.xslt.**
com.sun.org.apache.xalan.internal.xsltc.cmdline.**
com.sun.org.apache.xalan.internal.xsltc.compiler.**
com.sun.org.apache.xalan.internal.xsltc.trax.**
com.sun.org.apache.xalan.internal.xsltc.util.**
com.sun.org.apache.xerces.internal.**
com.sun.org.apache.xml.internal.res.**
com.sun.org.apache.xml.internal.security.**
com.sun.org.apache.xml.internal.serializer.utils.**
com.sun.org.apache.xml.internal.utils.**
com.sun.org.apache.xpath.internal.**
com.sun.org.glassfish.**
com.sun.pisces.**
com.sun.prism.**
com.sun.proxy.**
com.sun.scenario.**
com.sun.t2k.**
com.sun.webkit.**
com.sun.xml.internal.**
jdk.internal.**
jdk.management.resource.internal.**
jdk.nashorn.internal.**
jdk.nashorn.tools.**
oracle.jrockit.jfr.**
org.jcp.xml.dsig.internal.**
sun.**
Loading

0 comments on commit abab494

Please sign in to comment.