3

I'm using ASM to generate some bytecode and execute it dinamically. But there is a case where I need to invoke a private constructor, but i cound't figure out how. I know it is possible to invoke private constructor throug reflection (setAccessible), but how can i do that directly in bytecode/jvm ?

mv.visitMethodInsn(
        INVOKESPECIAL, target.byteCodeName(), "<init>", "()V", false
    )

When this code gets executed by JVM, it throws java.lang.IllegalAccessError.

  • If you're not in the same class, then no it wont be accessible (unless it's been made "package private" by the compiler for nested classes - and even that appears to be going away). – Tom Hawtin - tackline Jan 24 '19 at 01:00
  • (Had an interesting time checking: Using `javac -source 6 -target 6` adds a synthetic constructor that takes a synthetic class (similar to an anonymous inner class but has absolutely no constructors). You can call this from another class in the same package, but javac screens out synthetic methods. (Compile the second class against the first modified to include what would be the synthetic constructor . Recompile the proper first class. Then run.) JDK 11 seems to use the new nests thing, which is no fun. – Tom Hawtin - tackline Jan 24 '19 at 01:40
  • Are you sure that you are using “CGLIB”? This looks very much like ASM. – Holger Jan 24 '19 at 10:53
  • Holger, you're right xD – Guilherme Pohlmann Jan 24 '19 at 11:31

1 Answers1

5

Reflection is the only legal way to invoke a private constructor of the unrelated class. But of course this is not a good idea to make a reflective invocation each time.

The solution is invokedynamic. It allows to bind a call site to the constructor (obtained via reflection) just once and then invoke it with no overhead. Here is an example.

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");
        }
    }
}

Before JDK 9 there was an alternative dirty hack. If you inherited your generated class from sun.reflect.MagicAccessorImpl, JVM would skip access checks and allow to call any private method or constructor. But the encapsulation of private APIs in JDK 9 made difficult to perform this trick. Furthermore, MagicAccessorImpl is specific to HotSpot JVM and is not supposed to work on other implementations. So I'd definitely not recommend this alternative.

apangin
  • 92,924
  • 10
  • 193
  • 247