所有访问通过 ASM 生成的类构造函数的反射方法,如果类引用基元类型,则抛出 NoClassDefFoundError



>我正在编写一个应用程序,其中具有特定签名的反射方法对象被解包为通过 ASM 生成的类中的常规 INVOKEVIRTUAL 调用,以便可以以更注重性能的方式重复调用这些方法。要解包的方法将始终具有特定的返回类型和第一个参数,但可以具有超过该点的任何类型的任何给定数量的其他参数。

我定义了两个类来执行此操作,InvokerProxyNewInvokerProxyFactory

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、float 等),尝试通过任何通常提供的反射方法(Class.getConstructorsClass.getDeclaredConstructor 等)查找该类的构造函数将导致java.lang.NoClassDefFoundError引用方法签名中的第一个基元类型作为其消息。该异常显然是由URLClassLoader.findClass引起的,其中抛出具有相同消息的ClassNotFoundException

显然,这个问题甚至超出了构造函数的范围,因为即使Unsafe.allocateInstance在创建生成的类的实例时也会抛出相同的异常。当解包的方法没有任何基元参数时,查找构造函数或创建实例也绝对没有问题。

以下代码看起来很可疑

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

此代码是无条件调用的,即使paramTypes[i]是基元类型也是如此。但是,ASM 文档说只能为实际对象或数组类型调用getInternalName。ASM 在给定原语时可能只会生成一个虚假的类名,因此会出现错误。

public static String getInternalName(Class c)

返回内部给定类的名称。类的内部名称是其完整的限定名称,由 Class.getName() 返回,其中"." 被替换通过"/"。

参数:

C - 对象或数组类

返回:

给定类的内部名称。

另外,请注意,无论如何,CHECKCAST 指令对基元类型都无效。

相关内容

  • 没有找到相关文章

最新更新