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?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Grundlefleck
  • 124,925
  • 25
  • 94
  • 111
  • 1
    I don't think ASM does require that. – Tom Hawtin - tackline Dec 09 '09 at 13:34
  • 1
    any why can't you create a new class loader and point ASM at that? – bmargulies Dec 09 '09 at 13:38
  • @Tom Hawtin: for the most part yes, classes can be read with a `ClassReader` which takes an input stream. The `SimpleVerifier` however, does try to load the class, and we are using that to analyse the stack at certain points. If there's an alternative to using the `SimpleVerifier` with `Analyzer` to inspect the stack at the point of field assignments, information on that would be a valid answer. – Grundlefleck Dec 09 '09 at 13:53
  • @bmargulies, if that's possible, and it's easily done, it's that kind of information I'm asking for. Telling me how to go about that would also be a valid answer. – Grundlefleck Dec 09 '09 at 13:55
  • @Grundlefleck Have you found the working solution for your case? – dma_k Mar 08 '10 at 13:41
  • @dma_k: I did indeed, you can view what I did here (http://tinyurl.com/yznrrkf), in a method named `setCustomClassLoader()`. – Grundlefleck Mar 10 '10 at 19:31
  • @dma_k: ... and then it's actually used in the class here (http://tinyurl.com/ybeg92e) – Grundlefleck Mar 10 '10 at 20:03

6 Answers6

5

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
  • 30,111
  • 9
  • 76
  • 83
  • 1
    @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 Mar 09 '10 at 14:01
  • Can you set your custom classloaders parent to be the boot classloader? – Kevin Mar 09 '10 at 20:40
  • 1
    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 Mar 11 '10 at 16:05
5

First of all, ASM can be used in a such way that it won't use ClassLoader to obtain information about classes.

There are several places in ASM framework where it loads classes by default but all those places can be overridden 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
  • 31,461
  • 5
  • 66
  • 67
  • 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 Dec 16 '09 at 19:30
  • 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 Dec 17 '09 at 16:12
  • @EugeneKuleshov - as one of the developers of ASM, I wonder if you can help me with the following: I have a java agent which modify classes using ASM and used on clients app. I've seen scenario which a third party class loader (PowerMock) fails to run with ClassDefNotFound since it can't find one of my classes. Is there a way for me using ASM to introduce to it (PowerMock) my class so it will not fail? I'll appreciate your help. Thanks, – nadavy May 21 '16 at 15:06
  • @nadavy why don't you just ask a new question on that and let people answer? Either here on stackoverflow or at the PowerMock's own forums or what's not. – Eugene Kuleshov May 24 '16 at 20:32
  • @EugeneKuleshov - First, thanks for your answer. Second, normally when I'm asking about ASM I fail to get answers from people with experience and when I saw you are on of the developers, I've decided to take a chance and ask for assistance. Regarding the PowerMock's forums - I wasn't aware that one exists. I'll try it. I'm sorry if that's bothered you and I appreciate you got back to me! – nadavy May 25 '16 at 08:58
  • 1
    @nadavy not bothering me. You need to give an opportunity to stackoverflow members to answer your question and earn reputation. That can't be done in comments here. So, ask a new question on this site or directly ask developers of the powermock (or any other tool you are using). – Eugene Kuleshov May 27 '16 at 18:22
2

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
  • 39,616
  • 7
  • 83
  • 82
2

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
  • 23,135
  • 6
  • 55
  • 69
2

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.

Community
  • 1
  • 1
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
1

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.
  • 18,002
  • 10
  • 71
  • 105