Частный конструктор JVM INVOKESPECIAL с ASM

Я использую ASM для генерации некоторого байт-кода и его динамического выполнения. Но есть случай, когда мне нужно вызвать частный конструктор, но я не понял, как это сделать. Я знаю, что можно вызвать частный конструктор через отражение (setAccessible), но как я могу сделать это непосредственно в байт-коде/jvm?

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

Когда этот код выполняется JVM, он выдает ошибку java.lang.IllegalAccessError.


person Guilherme Pohlmann    schedule 23.01.2019    source источник
comment
Если вы не в том же классе, то нет, он не будет доступен (если компилятор не сделал его закрытым для вложенных классов - и даже это, похоже, уходит).   -  person Tom Hawtin - tackline    schedule 24.01.2019
comment
(Интересно было проверить: использование javac -source 6 -target 6 добавляет синтетический конструктор, который принимает синтетический класс (похожий на анонимный внутренний класс, но не имеет абсолютно никаких конструкторов). Вы можете вызывать это из другого класса в том же пакете, но javac отсеивает синтетические методы. (Скомпилируйте второй класс против первого измененного, чтобы включить то, что будет синтетическим конструктором. Перекомпилируйте правильный первый класс. Затем запустите.) JDK 11, похоже, использует новую вещь гнезд, что неинтересно.   -  person Tom Hawtin - tackline    schedule 24.01.2019
comment
Вы уверены, что используете «CGLIB»? Это очень похоже на ASM.   -  person Holger    schedule 24.01.2019
comment
Хольгер, ты прав xD   -  person Guilherme Pohlmann    schedule 24.01.2019


Ответы (1)


Отражение — единственный законный способ вызвать закрытый конструктор несвязанного класса. Но, конечно, не стоит каждый раз делать рефлективный призыв.

Решение 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");
        }
    }
}

До JDK 9 был альтернативный грязный хак. Если бы вы унаследовали сгенерированный класс от sun.reflect.MagicAccessorImpl, JVM пропустила бы проверки доступа и разрешила вызывать любой закрытый метод или конструктор. Но инкапсуляция частных API в JDK 9 затруднила выполнение этого трюка. Кроме того, MagicAccessorImpl специфичен для HotSpot JVM и не должен работать в других реализациях. Так что я бы определенно не рекомендовал эту альтернативу.

person apangin    schedule 24.01.2019