使用 ASM 的 JVM INVOKESPECIAL 私有构造函数

Gui*_*ann 3 java jvm bytecode java-bytecode-asm jvm-bytecode

我正在使用 ASM 生成一些字节码并动态执行它。但在某些情况下,我需要调用私有构造函数,但我不知道如何调用。我知道可以通过反射(setAccessible)调用私有构造函数,但是我如何直接在字节码/jvm中执行此操作?

mv.visitMethodInsn(
        INVOKESPECIAL, target.byteCodeName(), "<init>", "()V", false
    )
Run Code Online (Sandbox Code Playgroud)

当 JVM 执行此代码时,它会抛出 java.lang.IllegalAccessError。

apa*_*gin 5

反射是调用不相关类的私有构造函数的唯一合法方法。当然,每次都进行反思性调用并不是一个好主意。

解决办法是invokedynamic。它允许将调用站点绑定到构造函数(通过反射获得)一次,然后无需任何开销即可调用它。这是一个例子。

import org.objectweb.asm.*;
import java.lang.invoke.*;
import java.lang.reflect.Constructor;

import static org.objectweb.asm.Opcodes.*;

public class InvokeGenerator extends ClassLoader {

    private static Class<?> generate() {
        ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cv.visit(V1_7, ACC_PUBLIC, "InvokeImpl", null, "java/lang/Object", null);

        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);

        // Generate INVOKEDYNAMIC instead of NEW+INVOKESPECIAL.
        // This will instantiate the target class by calling its private constructor.
        // Bootstrap method is called just once to link this call site.
        mv.visitInvokeDynamicInsn("invoke", "()LInvokeGenerator$Target;",
                new Handle(H_INVOKESTATIC, "InvokeGenerator", "bootstrap", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false));
        // Here we have newly constructed instance of InvokeGenerator.Target
        mv.visitInsn(POP);

        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        cv.visitEnd();
        byte[] classData = cv.toByteArray();

        return new InvokeGenerator().defineClass(null, classData, 0, classData.length);
    }

    public static void main(String[] args) throws Exception {
        Class<?> cls = generate();
        cls.newInstance();
    }

    public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type) throws Exception {
        // Derive the constructor signature from the signature of this INVOKEDYNAMIC
        Constructor c = type.returnType().getDeclaredConstructor(type.parameterArray());
        c.setAccessible(true);
        // Convert Constructor to MethodHandle which will serve as a target of INVOKEDYNAMIC
        MethodHandle mh = lookup.unreflectConstructor(c);
        return new ConstantCallSite(mh);
    }

    public static class Target {
        private Target() {
            System.out.println("Private constructor called");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 JDK 9 之前,还有另一种肮脏的 hack。如果您从 继承生成的类sun.reflect.MagicAccessorImpl,JVM 将跳过访问检查并允许调用任何私有方法或构造函数。但 JDK 9 中私有 API 的封装使得执行此技巧变得困难。此外,MagicAccessorImpl它是特定于 HotSpot JVM 的,不应该在其他实现上工作。所以我绝对不会推荐这种替代方案。