Define multiple classes at runtime using Unsafe.defineClass

守給你的承諾、 提交于 2021-02-08 05:58:39

问题


I am working on a REPL for a custom programming language of mine. It is implemented on top of the compiler, which it uses to generate the bytecode for the input and convert it to a Class<?> instance using the sun.misc.Unsafe.defineClass(String, byte[], int, int, ClassLoader, ProtectionDomain) method. The relevant code looks like this (irrelevant parts like exception handling omitted):

void compileAndLoad(List<ICompilable> compilables)
{
    List<Class<?>> classes = ...;
    for (ICompilable c : compilables)
    {
        classes.add(compile(compilable));
    }
    for (Class<?> c : classes)
    {
        UNSAFE.ensureClassInitialized(c);
    }
}

// CLASS_LOADER = Enclosing.class.getClassLoader()
// PROTECTION_DOMAIN = Enclosing.class.getClassLoader()

Class<?> compile(ICompilable compilable)
{
    byte[] bytecode = genBytecode(compilable);
    String name = compilable.getFullName() // e.g. 'foo.bar.Baz'
    return UNSAFE.defineClass(name, bytes, 0, bytes.length, CLASS_LOADER, PROTECTION_DOMAIN);
}

Say the input requires multiple classes to be compiled and loaded.

> class A { interface B { }; func b() = new B { /* anonymous class */ } }

The compilables list has the contents

[ repl.Result_0, repl.Result_0$A, repl.Result_0$A$0, repl.Result_0$A$B ]

The repl.Result_0$A class depends on the repl.Result_0$A$0 (anonymous) class and the repl.Result_0$B class and references their names in the bytecode. When defining it using Unsafe, the following error will occur:

java.lang.NoClassDefFoundError: repl/Result_0$A$B
    at sun.misc.Unsafe.defineClass(Native Method)
    at MyClass.compile(MyClass.java:42)
    // ... snip
Caused by: java.lang.ClassNotFoundException: repl.Result_0$A$B
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 9 more

I know this could be solved by reordering the list and defining repl.Result_0$A$B first, but that would not be a general solution since there can be references from B -> A as well.

Is there a way to define and load multiple classes using Unsafe.defineClass without causing verification errors for unresolved classes?


回答1:


Your problem is not specific to Unsafe.defineClass, but related to the program logic. Whenever you “push” multiple new classes, regardless of whether you use ClassLoader.defineClass or Unsafe.defineClass, you have to avoid forward references, which precludes having loops in your class dependencies.

For the actual intended use cases of Unsafe.defineClass, e.g. reflective accessors, there is a clear dependency direction and hence, no problem, but for your use case, it’s not the right tool. You have to define a class loader which allows the JVM to “pull” the classes when needed, e.g.

void compileAndLoad(List<ICompilable> compilables) {
    Map<String,byte[]> compiled = new HashMap<>(compilables.size());
    for(ICompilable c: compilables)
        compiled.put(c.getFullName(), genBytecode(c));
    ClassLoader l = new ClassLoader(CLASS_LOADER) {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] code = compiled.get(name);
            if(code == null) throw new ClassNotFoundException(name);
            return defineClass(name, code, 0, code.length);
        }
    };
    // the code below this line is questionable; it seems you are relying
    // on the side effects of a class initializer
    for(String name: compiled.keySet()) try {
        Class.forName(name, true, l);
    } catch (ClassNotFoundException ex) { throw new AssertionError(ex); }
}

Note that the code uses Class.forName rather than loadClass to enforce the initialization as your original code does. Normally, code should not rely on immediate initialization, but you’re not using the loaded classes for anything else, so it’s not clear, with what to substitute. The usual procedure would be to use loadClass for the class intended to be used subsequently and return it; the initialization (and loading and initialization of dependencies) would happen on its actual use.

Further note, that the entire code works without using Unsafe



来源:https://stackoverflow.com/questions/39729671/define-multiple-classes-at-runtime-using-unsafe-defineclass

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!