问题
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