views:

491

answers:

6

I am writing a static analysis tool for an assignment, it analyses Java bytecode using the ASM library. One of the parts of ASM that we use requires (or at least, appears to require) that the class be loaded from the ClassLoader.

We were hoping the tool would be able to analyse .class files without requiring them on the classpath. We already load the .classes from a specified directory at run time and read them in using an InputStream. This is acceptable for ASM in most cases. There are some classes, such as SimpleVerifier, which attempt to load the classes though.

Is it possible, under this scenario, to register the .class files to be loaded so that calls to Class.forName() will load them? Or is there an easy way to extend the ClassLoader to allow this?


Edit: the information on URLClassLoader was useful. Unfortunately, using Thread.currentThread().setContextClassLoader() to an instance of that didn't work in this scenario. The library code I'm calling into uses a loader it retrieves on instance initialisation using getClass().getClassLoader().

By the time I set the URLClassLoader the class hasn't been initialised so I guess the contextClassLoader does not load that class.

Have I understand the responses correctly? Would using the URLClassLoader to load the 3rd party class be a possibility?

+1  A: 

You can't, as far as I know, extend the System class loader at runtime, but you can dynamically load classes from an arbitrary location (jar or directory) using URLClassLoader.

Dave Ray
It won't be a great idea, but it is actually possible to add classes into a system class loader.
Eugene Kuleshov
+3  A: 

Almost.

If you have classes compiled somewhere, you can load them with a URLClassLoader. You can then set this ClassLoader to be the ClassLoader for the current Thread: Thread.setContextClassLoader(ClassLoader)

Users can that get the current threads context class loader and use that to access the class definition.

Kevin
@Kevin Nice hint, thanks. But I failed make it working for my case. In particular: I have a `MyClass` in my project, that depends on the classes in `${java.home}/lib/deploy.jar`. When I try to load this jar via `URLClassLoader` and then try to call a method in `MyClass` I get `java.lang.ClassNotFoundException: com.sun.deploy.services.ServiceManager`. From here (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6667564) I see I have to add deploy.jar to bootclasspath (adding to classpath does not help), but I would like not doing it and resolve dependency dynamically. Is there any way out?
dma_k
Can you set your custom classloaders parent to be the boot classloader?
Kevin
@Kevin: I think, I can. But I would like to avoid VM-specific arguments, like `-Xboothclasspath/a:`, which bind my implementation to Sun VM. Anyway, give me a hint how to make this. If there is purely Java-code solution, let me now. If you can explain, what is so specific in `deploy.jar`, that I cannot load it with `URLClassLoader` you are very welcome.
dma_k
+1  A: 

You could try to setup a "launcher" in the startup of your application that creates an URLClassLoader passing it the locations on the classpath and your own .class locations and start the application from that classloader.

When the SimpleVerifier is loaded by the URLClassLoader it will also be able to load the classes from the extra locations.

rsp
+1  A: 

Yes, you can use URLClassLoader

I have a test where I do load the class at runtime. This class is not in the classpath (nor even exist when the test is run for that matter ), later is it loaded and works great.

Here's the code.

void testHello() throws MalformedURLException, ClassNotFoundException {
    URL[] url = {
            new URL("file:/home/oreyes/testwork/")
    };

    try {
        new URLClassLoader(url).loadClass("Hello");
        throw new AssertionError("Should've thrown ClassNotFoundException");
    } catch ( ClassNotFoundException cnfe ){}


    c.process();// create the .class file 

    new URLClassLoader(url).loadClass("Hello");

    // it works!!
}

Taken from this question.

OscarRyz
A: 

First of all, as one of the developers of ASM framework I can assure you that you can use ASM in a such way that it won't use ClassLoader to obtain information about classes.

There is several places in ASM framework where it loads classes by default but all those places can be overriden in your own subclasses. Out of the top of my head:

  • ClassWriter.getCommonSuperClass() method is called only when ClassWriter.COMPUTE_FRAMES flag is used and can be overwriten to not use ClassLoader to get inforamtion about classes. You can find an example of that in ClassWriterComputeFramesTest that introduces a ClassInfo abstraction
  • Similarly SimpleVerifier.getClass() method is used by SimpleVerifier.isAssignableFrom() and you can overwrite the latter and use the ClassInfo abstraction to find the common super type. If I am not mistaken, AspectWerkz project had implemented similar thing in its type pattern matching code. Also note that there is SimpleVerifier.setClassLoader() method, which you can use if you still want to load your own classes.

On a side note, on a Sun's JVMs, loaded classes gets to PermGen area and can't be unloaded, so it is not a good idea to load classes only for static code analysis purposes if you can avoid that, especially if tool would be integrated into a long-live process, such as IDE.

Eugene Kuleshov
It was only the SimpleVerifier's protected `getClass()` method that was causing the problem for me, as it uses `getClass().getClassLoader()`, but I did miss the obvious answer of subclassing SimpleVerifier when I asked this question. Subclassing to use a different ClassLoader was the solution I used. It was my first run with ASM and I was very pleased with it.
Grundlefleck
If you are working on a static analysis tool, you may not want to load classes. I updated my answer to clarify that SimpleVerifier.getClass() method is only used by SimpleVerifier.isAssignableFrom() which you can reimplement without loading classes and make it more performant, especially for the static analysys needs.
Eugene Kuleshov
A: 

I created my own ClassLoader its quite simple.

 /**
 * Used to hold the bytecode for the class to be loaded.
 */
private final static ThreadLocal<byte[]> BYTE_CODE = new ThreadLocal<byte[]>();

@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
    final byte[] bytes = BYTE_CODE.get();
    if (null == bytes) {
        throw new ClassNotFoundException(name);
    }
    return this.defineClass(null, bytes, 0, bytes.length);
}
mP