4

I need to programmatically find out which JRE classes can be referenced in a compilation unit without being imported (for static code analysis). We can disregard package-local classes. According to the JLS, classes from the package java.lang are implicitly imported. The output should be a list of binary class names. The solution should work with plain Java 5 and up (no Guava, Reflections, etc.), and be vendor agnostic.

Any reliable Java-based solution is welcome.


Here are some notes on what I've tried so far:

At first glance, it seems that the question boils down to "How to load all classes from a package?", which is of course practically impossible, although several workarounds exist (e.g. this and this, plus the blog posts linked there). But my case is much simpler, because the multiple classloaders issue does not exist. java.lang stuff can always be loaded by the system/bootstrap classloader, and you cannot create your own classes in that package. Problem is, the system classloader does not divulge its class path, which the linked appoaches rely on.

So far, I haven't managed to get access to the system classloader's class path, because on the HotSpot VM I'm using, Object.class.getClassLoader() returns null, and Thread.currentThread().getContextClassLoader() can load java.lang.Object by delegation, but does not itself include the classpath. So solutions like this one don't work for me. Also, the list of guaranteed system properties does not include properties with this kind of classpath info (such as sun.boot.class.path).

It would be nice if I didn't have to assume that there is an rt.jar at all, and rather scan the list of resources used by the system classloader. Such an approach would be safer with respect to vendor specific JRE implementations.

Community
  • 1
  • 1
barfuin
  • 16,865
  • 10
  • 85
  • 132
  • Note that the JLS paragraph you refer to says those packages are "observable", but that does not mean they are implicitly imported. Paragraph 7.3 says that there's always an implicit `import java.lang.*;`. – Jesper Jan 13 '14 at 22:31
  • @Jesper Thanks, you are right. That is also what I observed empirically. I updated the question accordingly. – barfuin Jan 13 '14 at 22:56
  • Didn't you forget the package-local classes? – Joop Eggen Jan 13 '14 at 23:01
  • @JoopEggen Thanks. AFAIK, `java.lang` is a forbidden package name. I clarified the question with regard to package-local classes outside of `java.lang`. – barfuin Jan 13 '14 at 23:06

2 Answers2

1

Compiled classes appear to contain readable java/lang text. So I wrote a little bit of code to see if these imports can be extracted. It's a hack, so not reliable, but assuming you can extract/list all classes in a jar-file, this could be a starting point.

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

public class Q21102294 {

public static final String EXTERNAL_JAR = "resources/appboot-1.1.1.jar";
public static final String SAMPLE_CLASS_NAME = "com/descartes/appboot/AppBoot.class";

public static HashSet<String> usedLangClasses = new HashSet<String>();

public static void main(String[] args) {

    try {
        Path f = Paths.get(EXTERNAL_JAR);
        if (!Files.exists(f)) {
            throw new RuntimeException("Could not find file " + f);
        }
        URLClassLoader loader = new URLClassLoader(new URL[] { f.toUri().toURL() }, null);
        findLangClasses(loader, SAMPLE_CLASS_NAME);

        ArrayList<String> sortedClasses = new ArrayList<String>();
        sortedClasses.addAll(usedLangClasses);
        Collections.sort(sortedClasses);
        System.out.println("Loaded classes: ");
        for (String s : sortedClasses) {
            System.out.println(s);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public static void findLangClasses(URLClassLoader loader, String classResource) throws Exception {

    URL curl = loader.getResource(classResource);
    if (curl != null) {
        System.out.println("Got class as resource.");
    } else {
        throw new RuntimeException("Can't open resource.");
    }
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    InputStream in = curl.openStream();
    try { 
        byte[] buf = new byte[8192];
        int l = 0;
        while ((l = in.read(buf)) > -1) {
            bout.write(buf, 0, l);
        }
    } finally {
        in.close();
    }
    String ctext = new String(bout.toByteArray(), StandardCharsets.UTF_8);
    int offSet = -1;
    while ((offSet = ctext.indexOf("java/lang/", offSet)) > -1) {
        int beginIndex = offSet;
        offSet += "java/lang/".length();
        char cnext = ctext.charAt(offSet);
        while (cnext != ';' && (cnext == '/' || Character.isAlphabetic(cnext))) {
            offSet += 1;
            cnext = ctext.charAt(offSet);
        }
        String langClass = ctext.substring(beginIndex, offSet);
        //System.out.println("adding class " + langClass);
        usedLangClasses.add(langClass);
    }
}

}

Gives the following output:

Got class as resource.
Loaded classes: 
java/lang/Class
java/lang/ClassLoader
java/lang/Exception
java/lang/Object
java/lang/RuntimeException
java/lang/String
java/lang/StringBuilder
java/lang/System
java/lang/Thread
java/lang/Throwable
java/lang/reflect/Method

Source code of the used compiled class is available here.

vanOekel
  • 6,358
  • 1
  • 21
  • 56
  • Good thinking outside the box! It does not yield the full list of `java.lang` classes though (only those referenced). Also, it requires access to the compiled class file (which i don't have at this point, but well). Still, thank you for the interesting new approach! – barfuin Jan 14 '14 at 20:51
  • Aha, I understand the question better now. Hopefully somebody has another creative spark. – vanOekel Jan 14 '14 at 22:12
0

OK, I misread the question. Checking the JLS, all I see is:

"Every compilation unit implicitly imports every public type name declared in the predefined package java.lang, as if the declaration import java.lang.*; appeared at the beginning of each compilation unit immediately after any package statement. As a result, the names of all those types are available as simple names in every compilation unit."

(http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html)

If you want to know which types that includes, it's going to vary from version to version of Java...

keshlam
  • 7,931
  • 2
  • 19
  • 33