2016-07-12 12 views
0

Я пишу приложение, в котором отраженные объекты метода со специфическими сигнатурами разворачиваются на обычные вызовы INVOKEVIRTUAL в классах, сгенерированных через ASM, так что эти методы могут быть многократно вызываемых более сознательным образом. Методы, которые необходимо развернуть, всегда будут иметь определенный тип возврата и первый параметр, но могут иметь любое заданное количество других параметров любого типа, прошедших эту точку.Все методы отражения доступа к конструктору класса, сгенерированного через ASM throw NoClassDefFoundError, если класс ссылается на примитивный тип

Для этого я определил два класса, InvokerProxy и NewInvokerProxyFactory.

public interface InvokerProxy { 
    ExitCode execute(IODescriptor io, Object... args); 
} 

public final class NewInvokerProxyFactory { 

    private static final String GENERATED_CLASS_NAME = "InvokerProxy"; 

    private static final Map<Class<?>, Consumer<MethodVisitor>> UNBOXING_ACTIONS; 

    private static final AtomicInteger NEXT_ID = new AtomicInteger(); 

    private NewInvokerProxyFactory() {} 

    public static InvokerProxy makeProxy(Method backingMethod, Object methodParent) { 
     String proxyCanonicalName = makeUniqueName(InvokerProxyFactory.class.getPackage(), backingMethod); 
     String proxyJvmName = proxyCanonicalName.replace(".", "/"); 

     ClassWriter cw = new ClassWriter(0); 
     FieldVisitor fv; 
     MethodVisitor mv; 

     cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, proxyJvmName, null, Type.getInternalName(Object.class), new String[]{Type.getInternalName(InvokerProxy.class)}); 

     cw.visitSource("<dynamic>", null); 

     { 
      fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "parent", Type.getDescriptor(Object.class), null, null); 
      fv.visitEnd(); 
     } 

     { 
      mv = cw.visitMethod(ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)), null, null); 
      mv.visitCode(); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false); 
      mv.visitVarInsn(ALOAD, 0); 
      mv.visitVarInsn(ALOAD, 1); 
      mv.visitFieldInsn(PUTFIELD, proxyJvmName, "parent", Type.getDescriptor(Object.class)); 
      mv.visitInsn(RETURN); 
      mv.visitMaxs(2, 2); 
      mv.visitEnd(); 
     } 

     { 
      mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "execute", Type.getMethodDescriptor(Type.getType(ExitCode.class), Type.getType(IODescriptor.class), Type.getType(Object[].class)), null, null); 
      mv.visitCode(); 

      mv.visitVarInsn(ALOAD, 0); 
      mv.visitFieldInsn(GETFIELD, proxyJvmName, "parent", Type.getDescriptor(Object.class)); 
      mv.visitTypeInsn(CHECKCAST, Type.getInternalName(methodParent.getClass())); 
      mv.visitVarInsn(ALOAD, 1); 

      Class<?>[] paramTypes = backingMethod.getParameterTypes(); 
      for (int i = 1; i < paramTypes.length; i++) { 
       mv.visitVarInsn(ALOAD, 2); 
       mv.visitLdcInsn(i-1); 
       mv.visitInsn(AALOAD); 
       mv.visitTypeInsn(CHECKCAST, Type.getInternalName(paramTypes[i])); 
       if (paramTypes[i].isPrimitive()) { 
        UNBOXING_ACTIONS.get(paramTypes[i]).accept(mv); 
       } 
      } 

      mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(methodParent.getClass()), backingMethod.getName(), Type.getMethodDescriptor(backingMethod), false); 
      mv.visitInsn(ARETURN); 
      mv.visitMaxs(backingMethod.getParameterTypes().length + 2, 3); 
      mv.visitEnd(); 
     } 
     cw.visitEnd(); 

     try { 
      return (InvokerProxy) SystemClassLoader.defineClass(proxyCanonicalName, cw.toByteArray()).getDeclaredConstructor(Object.class).newInstance(methodParent); 
     } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 
      throw new InvokerProxyGenerationException("Exception creating invoker proxy for method '" + backingMethod + "'", e); 
     } 
    } 

    private static String makeUniqueName(Package parentPackage, Method method) { 
     return String.format("%s.%s_%d", parentPackage.getName(), GENERATED_CLASS_NAME, NEXT_ID.getAndIncrement()); 
    } 

    static { 
     Map<Class<?>, Consumer<MethodVisitor>> actions = new HashMap<>(); 
     actions.put(Byte.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", "()B", false)); 
     actions.put(Short.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", "()S", false)); 
     actions.put(Integer.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false)); 
     actions.put(Long.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", "()J", false)); 
     actions.put(Float.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", "()F", false)); 
     actions.put(Double.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", "()D", false)); 
     actions.put(Boolean.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", "()Z", false)); 
     actions.put(Character.TYPE, mv -> mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Character.class), "charValue", "()C", false)); 
     UNBOXING_ACTIONS = actions; 
    } 
} 

Через тестирования я обнаружил, что если метод будучи разворачивал в InvokerProxyFactory имеет какие-либо примитивные параметры (INT, CHAR, поплавок и т.д ..), пытаясь посмотреть конструктор для этого класса через любой из обычно предоставляемых методов отражения (Class.getConstructors, Class.getDeclaredConstructor и т. д.) приведет к , цитирующему первый примитивный тип, найденный в сигнатуре метода в качестве его сообщения. Исключением, по-видимому, является URLClassLoader.findClass, где ClassNotFoundException сбрасывается с тем же сообщением.

Очевидно, этот вопрос выходит за рамки конструкторов, так как даже Unsafe.allocateInstance генерирует это же исключение при создании экземпляра сгенерированного класса. Также нет проблем с поиском конструкторов или созданием экземпляров, когда развернутый метод не имеет примитивных параметров.

+0

Не могли бы вы разместить стоп-трек исключения и сгенерированный файл классов, если это возможно? Кроме того, есть ли причина, по которой вы не можете просто использовать invokedynamic? Он уже разработан, чтобы делать то, что вы делаете, но более эффективно. – Antimony

+0

Интересный проект, но я уверен, что реализация отражения и компилятор hotspot уже делают это. Меня бы интересовали контрольные показатели, как только у вас будет этот запуск. –

+0

@ Jörn Horstmann: Я вполне уверен, что прямой метод MethodHandle, который не нуждается в боксом varargs, потенциально более эффективен или, по крайней мере, по сравнению с этим. И если количество параметров фиксировано, 'LambdaMetaFactory' может сделать то же самое. – Holger

ответ

1

Следующий код выглядит очень подозрительно

mv.visitTypeInsn(CHECKCAST, Type.getInternalName(paramTypes[i])); 

Этот код называется безусловно, даже если paramTypes[i] примитивный тип. Однако, ASM documentation говорит, что getInternalName может быть вызван только для реального объекта или типа массива. ASM, вероятно, просто генерирует имя фиктивного класса, когда ему присваивается примитив, следовательно, ошибка.

public static String getInternalName(Class c) 

Возвращает внутреннюю имя данного класса. Внутренним именем класса является его полное имя , возвращаемое Class.getName(), где '.' заменяются на '/'.

Параметры:

гр - объекта или класса массива.

Возврат:

внутреннее название данного класса.

Также обратите внимание, что инструкция CHECKCAST не действует для примитивных типов в любом случае.

+0

Результат вполне предсказуем. 'Class.getName()' возвращает имя примитивного типа, например. 'int', заменяя'. 'на'/', не имеет никакого эффекта и использует это как внутреннее имя, заставляет JVM искать класс с таким именем, например. класс, имеющий имя 'int' (если верификатор не отклоняет его заранее, потому что ссылочный тип' int' не может быть передан методу, ожидающему примитивный тип 'int', что проверено первым, является специфичным для реализации). – Holger

+0

@holger Вот что я понял, так как это была бы наивная реализация, но я не хотел делать предположения, не глядя на источник реализации. – Antimony

+0

Ну, это также описывает документацию. Поскольку нет другого упоминаемого поведения, нет оснований предполагать особое поведение (единственное предположение о специальном поведении - это исключение, которое, очевидно, не произошло). – Holger