Skip to content

Commit

Permalink
Issue #91: Fix isRuntime to mark all classes below ${java.home} or bo…
Browse files Browse the repository at this point in the history
…otclasspath (for Apple JDK) as runtime classes (previously only bootclasspath was added, missing extension classes); improve lookup with reverse ordered tree set.
  • Loading branch information
uschindler committed Dec 27, 2015
1 parent 7dd7764 commit 11f5b25
Showing 1 changed file with 47 additions and 33 deletions.
80 changes: 47 additions & 33 deletions src/main/java/de/thetaphi/forbiddenapis/Checker.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
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,8 +71,9 @@ public static enum Option {

final Logger logger;

final Set<File> bootClassPathJars;
final Set<String> bootClassPathDirs;
// must be sorted backwards
final SortedSet<String> runtimePaths;

final ClassLoader loader;
final java.lang.reflect.Method method_Class_getModule, method_Module_getResourceAsStream;
final EnumSet<Option> options;
Expand Down Expand Up @@ -142,48 +146,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 SortedSet<String> runtimePaths = new TreeSet<String>(Collections.reverseOrder());

// 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 = Collections.unmodifiableSortedSet(runtimePaths);
// logger.info("Runtime paths: " + runtimePaths);

if (isSupportedJDK) {
try {
Expand Down Expand Up @@ -225,27 +238,28 @@ 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();
return path.startsWith(runtimePaths.tailSet(path).first());
} catch (NoSuchElementException nse) {
return false;
} 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 Down

0 comments on commit 11f5b25

Please sign in to comment.