问题描述
我正在为我的自定义编程语言开发REPL.它在编译器的顶部实现,用于生成输入的字节码,并使用 sun.misc.Unsafe.defineClass(String,byte [],int,int,ClassLoader,ProtectionDomain)
方法.相关代码如下所示(省略了诸如异常处理之类的不相关部分):
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 */ } }
可编译对象
列表包含内容
[ repl.Result_0, repl.Result_0$A, repl.Result_0$A$0, repl.Result_0$A$B ]
repl.Result_0 $ A
类取决于 repl.Result_0 $ A $ 0
(匿名)类和 repl.Result_0 $ B
类,并在字节码中引用其名称.使用 Unsafe
进行定义时,会发生以下错误:
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
我知道可以通过重新排序列表并首先定义 repl.Result_0 $ A $ B
来解决此问题,但这不是一般的解决方案,因为可以从 B引用->一个
.
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.
是否可以使用 Unsafe.defineClass
定义和加载多个类,而不会导致未解决类的验证错误?
Is there a way to define and load multiple classes using Unsafe.defineClass
without causing verification errors for unresolved classes?
推荐答案
您的问题并非特定于 Unsafe.defineClass
,而是与程序逻辑有关.每当您"推"多个新类时,无论是否使用 ClassLoader.defineClass
或 Unsafe.defineClass
,您必须避免使用前向引用,从而避免在您的循环中出现循环类依赖项.
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.
对于 Unsafe.defineClass
的实际预期使用案例,例如反射访问器,有一个明确的依赖方向,因此没有问题,但是对于您的用例来说,它不是正确的工具.您必须定义一个类加载器,该类加载器允许JVM在需要时"拉"这些类,例如
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); }
}
请注意,该代码使用 Class.forName
而不是 loadClass
来执行初始化,就像您的原始代码一样.通常,代码不应依赖于立即初始化,但是您不会将加载的类用于其他任何事情,因此尚不清楚用什么替代.通常的过程是将 loadClass
用于随后要使用的类,并返回它;初始化(以及依赖项的加载和初始化)将在其实际使用中进行.
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.
还要注意,整个代码无需使用 Unsafe
...
Further note, that the entire code works without using Unsafe
…
这篇关于使用Unsafe.defineClass在运行时定义多个类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!