我正在尝试在运行时编译和加载动态生成的 Java 代码。由于 ClassLoader::defineClass 和 Unsafe::defineAnonymousClass 在这种情况下都有严重的缺陷,我尝试通过Lookup::defineHiddenClass使用隐藏类。这适用于我尝试加载的所有类,除了那些调用 lambda 表达式或包含匿名类的类。
调用 lambda 表达式会引发以下异常:
Exception in thread "main" java.lang.NoClassDefFoundError: tests/HiddenClassLambdaTest$LambdaRunner/0x0000000800c04400
at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:22)
Caused by: java.lang.ClassNotFoundException: tests.HiddenClassLambdaTest$LambdaRunner.0x0000000800c04400
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
... 1 more
执行实例化匿名类的代码会引发以下错误:
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400.run()V @5: invokespecial
Reason:
Type 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' (current frame, stack[2]) is not assignable to 'tests/HiddenClassLambdaTest$LambdaRunner'
Current Frame:
bci: @5
flags: { }
locals: { 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' }
stack: { uninitialized 0, uninitialized 0, 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' }
Bytecode:
0000000: bb00 1159 2ab7 0013 4cb1
at java.base/java.lang.ClassLoader.defineClass0(Native Method)
at java.base/java.lang.System$2.defineClass(System.java:2193)
at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2446)
at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2427)
at java.base/java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2133)
at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:25)
这是一个重现问题的简短示例:
import java.lang.invoke.MethodHandles;
public class HiddenClassLambdaTest {
/** This class is to be loaded and executed as hidden class */
public static final class LambdaRunner implements Runnable {
@Override public void run() {
Runnable runnable = () -> System.out.println("Success");
runnable.run();
}
}
public static void main(String[] args) throws Throwable {
// Path to the class file of the nested class defined above
String nestedClassPath = HiddenClassLambdaTest.class.getTypeName().replace('.','/') + "$LambdaRunner.class";
// Class file content of the LambdaRunner class
byte[] classFileContents = HiddenClassLambdaTest.class.getClassLoader().getResourceAsStream(nestedClassPath).readAllBytes();
Class<?> lambdaRunnerClass = MethodHandles.lookup().defineHiddenClass(classFileContents, true).lookupClass();
Runnable lambdaRunnerInstance = (Runnable) lambdaRunnerClass.getConstructor().newInstance();
lambdaRunnerInstance.run();
}
}
我已经尝试使用不同的 JDK 编译和运行代码,使用不同的方式创建隐藏类的新实例,在https://bugs.openjdk.java.net/上搜索错误,弄乱字节码本身和几个其他事情。我不是 Java 内部的专家,所以我不确定我是否没有理解正确引入隐藏类的 JEP。
我做错了什么,这是不可能的还是这是一个错误?
编辑:JEP状态
迁移应考虑以下因素: 要从隐藏类中的代码调用私有的 nestmate 实例方法,请使用 invokevirtual 或 invokeinterface 而不是 invokespecial。使用invokespecial 调用私有nestmate 实例方法的生成字节码将无法验证。invokespecial 应该只用于调用私有的 nestmate 构造函数。
这可能是匿名类的问题。有没有办法编译代码,以便在字节码中避免调用特殊?