13

I've been messing around with ClassLoaders in java recently, trying to test code which uses dynamic loading of classes (using Class.forName(String name)) with a custom ClassLoader.

I've got my own custom ClassLoader set up, which is supposed to be configurable to throw a ClassNotFoundException when trying to load a given class.

public class CustomTestClassLoader extends ClassLoader {
    private static String[] notAllowed = new String[]{};
    public static void setNotAllowed(String... nonAllowedClassNames) {
        notAllowed = nonAllowedClassNames;
    }
    public static String[] getNotAllowed() {
        return notAllowed;
    }
    public CustomTestClassLoader(ClassLoader parent){super(parent);}
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        for (String s : notAllowed) {
            if (name.equals(s)) {
                throw new ClassNotFoundException("Loading this class is not allowed for testing purposes.");
            }
        }

        if(name.startsWith("java") || name.startsWith("sun") || getClass().getName().equals(name)) {
            return getParent().loadClass(name);
        }

        Class<?> gotOne = super.findLoadedClass(name);
        if (gotOne != null) {
            return gotOne;
        }

        Class<?> c;
        InputStream in = getParent().getResourceAsStream(name.replace('.', '/')+".class");
        if (in == null) {
            throw new ClassNotFoundException("Couldn't locate the classfile: "+name);
        }
        try {
            byte[] classData = readBytes(in);
            c = defineClass(name, classData, 0, classData.length);
        } catch(IOException e) {
            throw new ClassNotFoundException("Couldn't read the class data.", e);
        } finally {
            try {
                in.close();
            } catch (IOException e) {/* not much we can do at this point */}
        }

        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    private byte[] readBytes(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[4194304];
        int read = in.read(buffer);
        while (read != -1) {
            out.write(buffer, 0, read);
            read = in.read(buffer);
        }
        out.close();
        return out.toByteArray();
    }
}

I'm using -Djava.system.class.loader=com.classloadertest.test.CustomTestClassLoader to set this classloader as default ClassLoader. I was hoping to be able to force a ClassNotFoundException by disallowing certain class names using CustomTestClassLoader.setNotAllowed(String...). However, it only works for ClassLoader.loadClass, and not for Class.forName:

public void test() {
    ClassLoader loader = this.getClass().getClassLoader();
    CustomTestClassLoader custom = (CustomTestClassLoader)loader; 
    CustomTestClassLoader.setNotAllowed(NAME);
    for (String s : custom.getNotAllowed())
        System.out.println("notAllowed: "+s);
    try {
        System.out.println(Class.forName(NAME));
    } catch (ClassNotFoundException e) {
        System.out.println("forName(String) failed");
    }
    try {
        System.out.println(Class.forName(NAME,false,custom));
    } catch (ClassNotFoundException e) {
        System.out.println("forName(String,boolean,ClassLoader) failed");
    }
    try {
        System.out.println(custom.loadClass(NAME));
    } catch (ClassNotFoundException e) {
        System.out.println("ClassLoader.loadClass failed");
    }
}

Now I expected all three try blocks to fail, since the documentation of Class.forName says it uses the ClassLoader of the caller (which should be custom/loader in this test). However, only the final try block fails. Here is the output I get:

notAllowed: com.classloadertest.test.Test
class com.classloadertest.test.Test
class com.classloadertest.test.Test
ClassLoader.loadClass failed

Does Class.forName really use the classloader? And if so, which methods? It seems to be using a native call, so I have no idea what it does under the covers.

Of course if anyone knows any alternative ways of testing a Class.forName() call, it would be much appreciated as well.

partlov
  • 13,789
  • 6
  • 63
  • 82
Volker
  • 133
  • 1
  • 5
  • For openjdk you can have a look here: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/lang/Class.java#Class.forName%28java.lang.String%29 – Fildor Jan 16 '13 at 09:25
  • 1
    Your class isn't loaded before you add it to notAllowed ? – JEY Jan 16 '13 at 09:37
  • Following up on @JEY, if you set NAME = MyClass.toString(), you are loading the class; internal classes also behave weird. – tucuxi Jan 16 '13 at 10:02
  • NAME is indeed set to Test.class.getCanonicalName(). I figured a call to Class.forName would still invoke a load on the ClassLoader. So if it doesn't, it probably uses a native lookup, basically bypassing the ClassLoader? I tried debugging it, but I can't step through the native forName0 method. – Volker Jan 16 '13 at 10:24

1 Answers1

0

Class.forName() uses the classloader of the class where it is called from (e.g in your case the class that contains the test() method). So, if you are running it in a different environment this will cause the problem.

UPDATE That ClassLoader will be used in Class.forName() which loaded your Test class. And that may be the solution: It may be an Eclipse-defined classloader, that has access to your class, so it will load it. Despite that its parent (or root) classloaders have explicit rule to forbid the loading of that class.

I still recommend to make a wrapper class for this instantiation. You should load that class with your CustomTestClassLoader, then you can use Class.forName() in that class.

gaborsch
  • 15,408
  • 6
  • 37
  • 48
  • Thanks for the answer. I thought the class containing the `test()` method was already loaded by my custom loader since I used the `loader vmarg`. I stepped through the example with the Eclipse debugger to be sure and `Class.forName()` does use an instance of `CustomTestClassLoader` as ClassLoader in this example. – Volker Jan 16 '13 at 14:11
  • I guess I should rephrase what I said, so here it goes: I verified that `Class.forName(NAME)` calls `Class.forName(NAME, true, loader)` where `loader` is an instance of my custom classloader. So there is no need to write a wrapper as the classloader is already resolved correctly. – Volker Jan 17 '13 at 12:45
  • 1
    I recommend the following steps: (1) Run the test in Eclipe in **Debug mode**, set a breakpoint to the beginning of your `loadClass()` method (it can be conditional for the classname, to avoid mass breaks). (2) if it does not help, set a **Class loading breakpoint** (under Run menu) for your classname. – gaborsch Jan 17 '13 at 14:01
  • 1
    I just did a new test and noticed the following behaviour: if the class requested by `Class.forName` has not yet been loaded, it uses the `loadClass` method of the classloader of the caller. However, if the class has already been loaded once, it completely ignores the classloader and (this part is just a guess) uses some kind of native code to obtain the class directly from the JVM. So my question is now, is there any way for me to make `Class.forName` throw a `ClassNotFoundException` even when the class is already loaded? – Volker Jan 18 '13 at 19:10
  • Can you debug your code from the `Class.forName()`call, what will be the first `ClassLoader` instance it enters? I also make some edit, I was imprecise. – gaborsch Jan 19 '13 at 22:38
  • It enters my custom classloader. So now it behaves as expected when the requested class has never been loaded before. I also verified that it completely skips any interaction with the classloader if the class has already been loaded. So I'll just have to make sure that the classes I wish to block are not loaded and run my testcases in a new JVM. Thanks for all the help. – Volker Jan 20 '13 at 14:05