【问题标题】:How to record all parameters of any invoked java method dynamically using java agent and ASM?如何使用 java 代理和 ASM 动态记录任何调用的 java 方法的所有参数?
【发布时间】:2020-07-09 13:38:28
【问题描述】:

我要做的是记录这些参数并与之前输入的参数进行一些比较。每个调用的方法我都需要记录参数,所以参数列表的长度是不确定的。

我想我可以解析方法描述符来知道这个方法有多少个参数。但问题是,如何从操作数栈中记录任意数量的值?

现在我只能写一些示例代码。在我的MethodVisitorAdapter 课堂上:

public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface){
    int n = getParameterCount(desc);
    // How to duplicate arbitrary number of values at operand stack?
    // ...
    mv.visitMethodInsn(INVOKESTATIC, MyClass, "recordParameters",
                    /* should be what kind of descriptor (may have arbitrary number of parameters)? */, 
                    false);
    mv.visitMethodInsn(opc, owner, name, desc, isInterface);
}

【问题讨论】:

    标签: java jvm instrumentation java-bytecode-asm javaagents


    【解决方案1】:

    您可以将recordParameters 设为可变参数方法 (varargs)。它将接受所有参数作为单个 Object[] 参数。

    因此,您需要创建一个数组并用(可能是装箱的)参数填充它。以下构建器样式类将有助于从堆栈中收集参数。它包含所有可能类型的push 方法,并且还处理自动装箱。

    记录参数后,助手可以通过逆序调用对应的pop方法将参数放回堆栈。

    public class ArgCollector {
        private final Object[] args;
        private int index;
    
        public ArgCollector(int length) {
            this.args = new Object[length];
            this.index = length;
        }
    
        public ArgCollector push(Object o) {
            args[--index] = o;
            return this;
        }
    
        public Object pop() {
            return args[index++];
        }
    
        public static ArgCollector push(boolean a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(byte    a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(char    a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(short   a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(int     a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(long    a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(float   a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(double  a, ArgCollector c) { return c.push(a); }
        public static ArgCollector push(Object  a, ArgCollector c) { return c.push(a); }
    
        public boolean popZ() { return (boolean) pop(); }
        public byte    popB() { return (byte)    pop(); }
        public char    popC() { return (char)    pop(); }
        public short   popS() { return (short)   pop(); }
        public int     popI() { return (int)     pop(); }
        public long    popJ() { return (long)    pop(); }
        public float   popF() { return (float)   pop(); }
        public double  popD() { return (double)  pop(); }
    
        public Object[] toArray() {
            return args;
        }
    }
    

    现在,任务是为以下 Java 代码生成等效的字节码:

        ArgCollector collector = new ArgCollector(N);
        recordParameters(
                ArgCollector.push(arg1,
                    ArgCollector.push(arg2,
                        ArgCollector.push(argN, collector)))
                .toArray()
        );
    
        originalMethod(
                collector.popI(),
                collector.popJ(),
                (String) collector.pop()
        );
    

    这是使用 ASM 的方法:

        Type[] args = Type.getArgumentTypes(desc);
        String collector = Type.getInternalName(ArgCollector.class);
    
        // new ArgCollector(argCount)
        mv.visitTypeInsn(NEW, collector);
        mv.visitInsn(DUP);
        mv.visitIntInsn(SIPUSH, args.length);
        mv.visitMethodInsn(INVOKESPECIAL, collector, "<init>", "(I)V", false);
    
        // For each argument call ArgCollector.push(arg, collector)
        for (int i = args.length; --i >= 0; ) {
            Type arg = args[i];
            String argDesc = arg.getDescriptor().length() == 1 ? arg.getDescriptor() : "Ljava/lang/Object;";
            mv.visitMethodInsn(INVOKESTATIC, collector, "push",
                    "(" + argDesc + "L" + collector + ";)L" + collector + ";", false);
        }
    
        // Call recordParameters(collector.toArray())
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKEVIRTUAL, collector, "toArray", "()[Ljava/lang/Object;", false);
        mv.visitMethodInsn(INVOKESTATIC, MyClass, "recordParameters", "([Ljava/lang/Object;)V", false);
    
        // Push original arguments back on stack
        for (Type arg : args) {
            String argDesc = arg.getDescriptor().length() == 1 ? arg.getDescriptor() : "Ljava/lang/Object;";
    
            mv.visitInsn(DUP);
            if (argDesc.length() == 1) {
                mv.visitMethodInsn(INVOKEVIRTUAL, collector, "pop" + argDesc, "()" + argDesc, false);
            } else {
                mv.visitMethodInsn(INVOKEVIRTUAL, collector, "pop", "()Ljava/lang/Object;", false);
                if (!arg.getDescriptor().equals("Ljava/lang/Object;")) {
                    // Need to cast object arguments to the original type
                    mv.visitTypeInsn(CHECKCAST, arg.getDescriptor());
                }
            }
    
            // Swap the last argument with ArgCollector, so that ArgCollector is on top again
            if (arg.getSize() == 1) {
                mv.visitInsn(SWAP);
            } else {
                mv.visitInsn(DUP2_X1);
                mv.visitInsn(POP2);
            }
        }
    
        // Pop off the remaining ArgCollector, and call the original method
        mv.visitInsn(POP);
        mv.visitMethodInsn(opc, owner, name, desc, isInterface);
    

    【讨论】:

    • 感谢您的回答!这真的很酷,但让我感到困惑的是这一行mv.visitVarInsn(arg.getOpcode(ILOAD), index);,似乎您想将参数从本地加载到堆栈中。但据我所知,当invokevirtual 某个实例方法时,参数在操作数堆栈中,不是吗?你能详细解释一下你为什么在这里使用ILOAD吗?
    • @Instein using ILOADType.getOpcode(…) 的约定,表示您想要特定类型的实际…load 操作码。你是对的,这个答案假设值存储在局部变量中。要使用这种方法,您首先必须将调用参数存储到局部变量中。另外,当您想要执行原始调用指令时,您需要在之后重复此推送。
    • @Holger 谢谢!!我现在很清楚了。如果我首先将调用参数存储到局部变量中,是否需要排序器或其他东西来防止“存储”操作影响后续指令的行为?
    • @Instein 我更新了答案,现在从堆栈中收集参数,并在调用 recordParameters 后返回堆栈。
    • @apangin 谢谢!这对我来说真的很清楚也很有用。其实我对 ASM 很陌生,对 MethodHandle 不熟悉,我目前只能完全理解你的答案。我认为我需要一些时间来阅读一些材料并理解其他答案......
    【解决方案2】:

    另一种选择是将invoke* 指令替换为invokedynamic,并提供引导方法:

    private static final Handle BSM = new Handle(H_INVOKESTATIC, "com/example/Bootstraps", "invokeProxy", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;", false);
    
    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        if (opcode == INVOKESPECIAL && name.equals("<init>") {
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            return;
        }
        Type method = Type.getMethodType(descriptor);
        Type[] oldTypes = method.getArgumentTypes();
        final int htype;
        switch (opcode) {
            case INVOKEINTERFACE:
                htype = H_INVOKEINTERFACE;
                break;
            case INVOKESPECIAL:
                htype = H_INVOKESPECIAL;
                break;
            case INVOKESTATIC:
                htype = H_INVOKESTATIC;
                break;
            case INVOKEVIRTUAL:
                htype = H_INVOKEVIRTUAL;
                break;
            default:
                throw new IllegalArgumentException("Unknown opcode: " + opcode);
        }
        final Type[] newTypes;
        switch (opcode) {
            case INVOKESPECIAL:
            case INVOKEINTERFACE:
            case INVOKEVIRTUAL:
                newTypes = new Type[oldTypes.length + 1];
                newTypes[0] = Type.getObjectType(owner);
                System.arraycopy(oldTypes, 0, newTypes, 1, oldTypes.length);
                break;
            case INVOKESTATIC:
                newTypes = oldTypes;
                break;
            default:
                throw new AssertionError(); // can not happen.
        }
        Handle h = new Handle(htype, owner, name, descriptor, isInterface);
        String indyDesc = Type.getMethodType(method.getReturnType(), newTypes).getDescriptor();
        visitInvokeDynamicInsn("_", indyDesc, BSM, h);
    }
    

    还有一个像这样的简单引导方法:

    package com.example; // Change the com/example/Bootstraps if you use a different package.
    
    import java.lang.invoke.CallSite;
    import java.lang.invoke.ConstantCallSite;
    import java.lang.invoke.MethodHandle;
    import java.lang.invoke.MethodHandleInfo;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.MethodHandles.Lookup;
    import java.lang.invoke.MethodType;
    import java.util.Arrays;
    
    import static java.lang.invoke.MethodType.methodType;
    
    public class Bootstraps {
    
        private static final MethodHandle PRINT_ARRAY;
    
        static {
            try {
                Lookup l = MethodHandles.lookup();
                MethodHandle printLn = l.findVirtual(PrintStream.class, "println", methodType(void.class, String.class))
                        .bindTo(System.err);
                MethodHandle arraysDeepToString = l.findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
                PRINT_ARRAY = MethodHandles.foldArguments(
                        MethodHandles.dropArguments(printLn, 1, Object[].class), arraysDeepToString);
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
        }
    
        public static CallSite invokeProxy(Lookup lookup, String name, MethodType type, MethodHandle target) {
            Object method = lookup.revealDirect(target);
            MethodHandle printArgsMH = MethodHandles.foldArguments(target, 
                    PRINT_ARRAY.asCollector(Object[].class, type.parameterCount() + 1)
                        .bindTo(method)
                        .asType(type.changeReturnType(void.class)))
                    .asType(type);
            return new ConstantCallSite(printArgsMH);
        }
    }
    

    这可能看起来比实际上更复杂。

    【讨论】:

    • 天哪,这太美了!!
    • 通过有几个问题:你不能拦截invokespecial对于构造函数,它要求字节码版本为51或更高(这也是你不能使用@987654327的版本@/RET)。除此之外,根据我的经验,它大大简化了仪器。此外,您可以拦截 GETSTATICPUTSTATICGETFIELDSETFIELD - set* 仅用于非最终字段。
    • 这也是我在考虑 invokedynamic 时的想法,尽管我会尽可能避免使用 revealDirect;无论如何,这些类型都是可用的,并且可以通过 .ust 将 "_" 替换为 name,将目标方法名称作为 invokedynamic 指令的方法名称提供。通常,日志记录不需要更多。无法对构造函数调用执行此操作是唯一的缺点。我的解决方案还假设 Java 7 最低,因为它依赖于堆栈映射框架。我认为对旧版本的支持并不重要。
    • 您实际上需要目标类和名称。是的,您可以使用 com/example/SomeType.method 作为名称对其进行编码。有它的好处。这段代码背后的基本思想是,一个仅return new ConstantCallSite(target); 的引导方法不应该改变程序的行为,即使你是invokedynamic all the things (Slide 51)
    【解决方案3】:

    要在操作数堆栈上复制任意序列的值,没有办法将它们临时存储到新的局部变量中。然后,将所有这些值推送到操作数堆栈,调用您的报告方法,再次推送它们,然后调用原始方法。

    一种避免LocalVariablesSorter 开销的简单方法是检查每条使用局部变量的指令并记住第一个空闲索引。这要求源代码已经具有有效的堆栈映射帧,以便在单个访问过程中正确处理后向分支。对于面向 Java 7 或更高版本的代码,无论如何这是强制性的。

    由于报告调用的代码没有分支并且之后不需要临时变量,我们甚至不需要昂贵的堆栈映射帧重新计算,因为在分支合并点,只需要原始变量.只有最大堆栈和局部变量需要重新计算。

    要调用报告方法,我们可以使用 holder 对象,例如 apangin’s answer,但也可以使用 java.lang.invoke package,它允许我们生成具有精确方法的 varargs 收集器动态描述符。

    以下代码将通过单个ldc指令获取MethodHandlePrintStream.printf(String,Object...),然后是bindingSystem.out作为第一个参数,然后绑定一个适合当前数量的常量String参数(以及非static 方法的接收者),然后调整调用.asVarargsCollector(Object[].class).asType(targetType) 的句柄。 targetType 是方法类型描述符,带有用于非静态方法调用的附加第一个参数类型。这个MethodType 也加载了一条ldc 指令。然后,可以通过调用invokeExact 使用与堆栈上的实际调用相同的参数来使用句柄。

    仅对于构造函数调用,接收者对象被省略,因为我们不允许在对象初始化之前使用它。

    {?store           n + i } for each argumentᵢ
     ldc              MethodHandle invokeVirtual java/io/PrintStream.printf(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
     getstatic        java/lang/System.out Ljava/io/PrintStream;
     invokevirtual    java/lang/invoke/MethodHandle.bindTo(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
     ldc              String containing <method name> and as many %s place holders as needed
     invokevirtual    java/lang/invoke/MethodHandle.bindTo(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
     ldc              class [Ljava/lang/Object;
     invokevirtual    java/lang/invoke/MethodHandle.asVarargsCollector(Ljava/lang/Class;)Ljava/lang/invoke/MethodHandle;
     ldc              MethodType («actual argument types»)Ljava/io/PrintStream;
     invokevirtual    java/lang/invoke/MethodHandle.asType(Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
    {?load            n + i } for each argumentᵢ
     invokevirtual    java/lang/invoke/MethodHandle.invokeExact(«actual argument types»)Ljava/io/PrintStream;
     pop              // remove the PrintStream return by printf
    {?load            n + i } for each argumentᵢ
     invoke...        original method
    

    转换代码及示例

    public class LogMethodCalls {
        public static void main(String[] args) throws IOException, IllegalAccessException {
            MethodHandles.lookup().defineClass(instrument(LogMethodCalls.class
                .getResourceAsStream("LogMethodCalls$ToInstrument.class")));
            runInstrumented();
        }
        private static void runInstrumented() {
            new ToInstrument().run();
        }
        static class ToInstrument implements Runnable {
            @Override
            public void run() {
                double min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
                for(double i: List.of(4, 2, 9, 6)) {
                    min = Math.min(min, i);
                    max = Math.max(max, i);
                }
                System.out.printf("min %.0f, max %.0f%n", min, max);
            }
        }
        static byte[] instrument(InputStream is) throws IOException {
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
    
            cr.accept(new ClassVisitor(Opcodes.ASM7, cw) {
                @Override
                public MethodVisitor visitMethod(int access, String name,
                        String descriptor, String signature, String[] exceptions) {
                    return new LogInjector(
                        super.visitMethod(access, name, descriptor, signature, exceptions),
                        access, descriptor);
                }
            }, ClassReader.EXPAND_FRAMES);
    
            return cw.toByteArray();
        }
    
        static class LogInjector extends MethodVisitor {
            static final String PS_T = "java/io/PrintStream", PS_S = "L" + PS_T + ";";
            static final String PRINTF_DESC="(Ljava/lang/String;[Ljava/lang/Object;)"+PS_S;
            static final String MH_T="java/lang/invoke/MethodHandle", MH_S="L" + MH_T + ";";
    
            private int firstUnusedVar;
            public LogInjector(MethodVisitor mv, int acc, String desc) {
                super(Opcodes.ASM7, mv);
                int vars = Type.getArgumentsAndReturnSizes(desc) >> 2;
                if((acc & Opcodes.ACC_STATIC) != 0) vars--;
                firstUnusedVar = vars;
            }
            @Override
            public void visitFrame(int type,
                int numLocal, Object[] local, int numStack, Object[] stack) {
                super.visitFrame(type, numLocal, local, numStack, stack);
                firstUnusedVar = Math.max(firstUnusedVar, numLocal);
            }
            @Override
            public void visitVarInsn(int opcode, int var) {
                super.visitVarInsn(opcode, var);
                if(opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) var++;
                if(var >= firstUnusedVar) firstUnusedVar = var + 1;
            }
            @Override
            public void visitMethodInsn(int opcode,
                String owner, String name, String descriptor, boolean isInterface) {
                Type[] arg = Type.getArgumentTypes(descriptor);
    
                int[] vars = storeArguments(arg, opcode, name, owner);
    
                String reportDesc = getReportDescriptor(owner, descriptor, arg, vars);
    
                mv.visitLdcInsn(new Handle(Opcodes.H_INVOKEVIRTUAL,
                    PS_T, "printf", PRINTF_DESC, false));
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", PS_S);
                bindTo();
                mv.visitLdcInsn(messageFormat(opcode, owner, name, arg));
                bindTo();
                mv.visitLdcInsn(Type.getObjectType("[Ljava/lang/Object;"));
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH_T,
                    "asVarargsCollector", "(Ljava/lang/Class;)"+MH_S, false);
                mv.visitLdcInsn(Type.getMethodType(reportDesc));
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH_T,
                    "asType", "(Ljava/lang/invoke/MethodType;)"+MH_S, false);
                pushArguments(arg, vars);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                    MH_T, "invokeExact", reportDesc, false);
                mv.visitInsn(Opcodes.POP);
    
                pushArguments(arg, vars);
                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            }
    
            String getReportDescriptor(
                String owner, String descriptor, Type[] arg, int[] vars) {
                StringBuilder sb = new StringBuilder(owner.length()+descriptor.length()+2);
                sb.append('(');
                if(arg.length != vars.length) {
                    if(owner.charAt(0) == '[') sb.append(owner);
                    else sb.append('L').append(owner).append(';');
                }
                sb.append(descriptor, 1, descriptor.lastIndexOf(')')+1);
                return sb.append(PS_S).toString();
            }
    
            int[] storeArguments(Type[] arg, int opcode, String name, String owner) {
                int nArg = arg.length;
                boolean withThis = opcode != Opcodes.INVOKESTATIC && !name.equals("<init>");
                if(withThis) nArg++;
                int[] vars = new int[nArg];
                int slot = firstUnusedVar;
                for(int varIx = nArg-1, argIx = arg.length-1; argIx >= 0; varIx--,argIx--) {
                    Type t = arg[argIx];
                    mv.visitVarInsn(t.getOpcode(Opcodes.ISTORE), vars[varIx] = slot);
                    slot += t.getSize();
                }
                if(withThis)
                    mv.visitVarInsn(Opcodes.ASTORE, vars[0] = slot);
                return vars;
            }
            private void bindTo() {
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH_T,
                    "bindTo", "(Ljava/lang/Object;)"+MH_S, false);
            }
            private void pushArguments(Type[] arg, int[] vars) {
                int vIx = 0;
                if(arg.length != vars.length)
                    mv.visitVarInsn(Opcodes.ALOAD, vars[vIx++]);
                for(Type t: arg)
                    mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), vars[vIx++]);
            }
            private String messageFormat(int opcode, String owner, String name, Type[] arg){
                StringBuilder sb = new StringBuilder();
                switch(opcode) {
                    case Opcodes.INVOKESPECIAL:
                        if(name.equals("<init>")) {
                            name = Type.getObjectType(owner).getClassName();
                            break;
                        }
                        // else no break
                    case Opcodes.INVOKEINTERFACE: // no break
                    case Opcodes.INVOKEVIRTUAL:
                        sb.append("[%s].");
                        break;
                    case Opcodes.INVOKESTATIC:
                        sb.append(Type.getObjectType(owner).getClassName()).append('.');
                        break;
                }
                sb.append(name);
                if(arg.length == 0) sb.append("()%n");
                else {
                    sb.append('(');
                    for(int i = arg.length; i > 1; i--) sb.append("%s, ");
                    sb.append("%s)%n");
                }
                return sb.toString();
            }
        }
    }
    

    示例调用使用 Java 9 并依赖于延迟加载的 JVM,因此它可以在实际使用之前(重新)定义类。它可能会被实际的 Instrumentation 场景所取代,因为它与实际的转换逻辑无关。在我的设置中,示例打印

    java.lang.Object()
    java.lang.Integer.valueOf(4)
    java.lang.Integer.valueOf(2)
    java.lang.Integer.valueOf(9)
    java.lang.Integer.valueOf(6)
    java.util.List.of(4, 2, 9, 6)
    [[4, 2, 9, 6]].iterator()
    [java.util.ImmutableCollections$ListItr@5ce65a89].hasNext()
    [java.util.ImmutableCollections$ListItr@5ce65a89].next()
    [4].intValue()
    java.lang.Math.min(2.147483647E9, 4.0)
    java.lang.Math.max(-2.147483648E9, 4.0)
    [java.util.ImmutableCollections$ListItr@5ce65a89].hasNext()
    [java.util.ImmutableCollections$ListItr@5ce65a89].next()
    [2].intValue()
    java.lang.Math.min(4.0, 2.0)
    java.lang.Math.max(4.0, 2.0)
    [java.util.ImmutableCollections$ListItr@5ce65a89].hasNext()
    [java.util.ImmutableCollections$ListItr@5ce65a89].next()
    [9].intValue()
    java.lang.Math.min(2.0, 9.0)
    java.lang.Math.max(4.0, 9.0)
    [java.util.ImmutableCollections$ListItr@5ce65a89].hasNext()
    [java.util.ImmutableCollections$ListItr@5ce65a89].next()
    [6].intValue()
    java.lang.Math.min(2.0, 6.0)
    java.lang.Math.max(9.0, 6.0)
    [java.util.ImmutableCollections$ListItr@5ce65a89].hasNext()
    java.lang.Double.valueOf(2.0)
    java.lang.Double.valueOf(9.0)
    [java.io.PrintStream@6e5e91e4].printf(min %.0f, max %.0f%n, [Ljava.lang.Object;@2cdf8d8a)
    min 2, max 9
    

    请注意,您的用例可能更简单。如果您的日志记录方法是static 方法,不需要PrintStream,则不需要绑定它。当它没有返回值时,你也不需要弹出它。当它接受可变参数时,它会更简单,包括格式字符串或方法名称。然后,我们可以像普通参数一样传递字符串,而不是绑定它,并且由于方法句柄现在没有修改,当目标方法是 varargs 时,它已经是一个 varargs 收集器 方法:

    static void yourLog(Object... arg) {
        String name = (String) arg[0]; // or format string
        arg = Arrays.copyOfRange(arg, 1, arg.length);
        …
    }
    
    @Override
    public void visitMethodInsn(int opcode,
        String owner, String name, String descriptor, boolean isInterface) {
    
        Type[] arg = Type.getArgumentTypes(descriptor);
        int[] vars = storeArguments(arg, opcode, name, owner);
        String reportDesc = getReportDescriptor(owner, descriptor, arg, vars);
        mv.visitLdcInsn(new Handle(Opcodes.H_INVOKESTATIC,
            YOUR_TARGET_TYPE, "yourLog", "([Ljava/lang/Object;)V", false));
        mv.visitLdcInsn(Type.getMethodType(reportDesc));
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH_T,
            "asType", "(Ljava/lang/invoke/MethodType;)"+MH_S, false);
        mv.visitLdcInsn(messageFormat(opcode, owner, name, arg));
        pushArguments(arg, vars);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH_T, "invokeExact", reportDesc, false);
    
        pushArguments(arg, vars);
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
    
    String getReportDescriptor(String owner, String descriptor, Type[] arg, int[] vars) {
        StringBuilder sb = new StringBuilder(owner.length()+descriptor.length()+2);
        sb.append("(Ljava/lang/String;");
        if(arg.length != vars.length) {
            if(owner.charAt(0) == '[') sb.append(owner);
            else sb.append('L').append(owner).append(';');
        }
        sb.append(descriptor, 1, descriptor.lastIndexOf(')')+1);
        return sb.append('V').toString();
    }
    

    【讨论】:

    • 喜欢你的回答。就我自己而言,我更喜欢用一个 invokedynamic 指令替换东西——这使我能够“在更高级别”上做事——处理 MethodHandles 的纯 java 代码。
    • “没有办法将它们存储到新的局部变量中” - 不一定。如果参数被收集到数组中,它们也可以从数组中恢复。
    • @apangin 那么您需要将数组保存在局部变量中。或者,您也许可以为数组引用做一个dup_x1/dup_x2,然后推送索引,然后是swap,如果需要,然后是装箱,然后是aastore,然后,对每个重复它参数值。好的,那么,有一种方法可以解决它,但并不是真的可行。不使用局部变量提取数组也不好玩……
    • @JohannesKuhn 是的,invokedynamic 和适当的引导方法也很好。当我尝试使用已经存在的方法时,我的第一种方法有点复杂。我暂时想到了StringConcatFactory,当记录性能很重要时,这可能会很有趣……
    • @apangin 然后,您在另一种方法中隐藏了局部变量。我的陈述更侧重于不可能在没有其他存储的情况下仅复制操作数堆栈上的序列,换句话说,您不能像dup 等对任意数量的值的一个或两个值所做的那样。但有替代品总是好的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-06
    • 1970-01-01
    • 1970-01-01
    • 2018-10-14
    • 2016-05-13
    • 2020-06-02
    • 2021-01-29
    相关资源
    最近更新 更多