ASM ByteCode:在 visitMethodInsn 之前加上另一个调用

ASM ByteCode: prepending visitMethodInsn with another invocation

假设我想在调用某个感兴趣的方法之前调用一个(日志记录)方法。这意味着在侦听 visitMethodInsn 时,堆栈中已经填充了感兴趣的方法的参数。

是否可以将当前堆栈存储在某个地方,调用日志并重新填充堆栈?我是否遗漏了任何明显的堆栈突变运算符?或者我真的需要:

  • 临时将堆栈存储在变量中
  • 在我的(可能不适用的)日志记录调用之后缓冲所有堆栈突变调用,直到方法调用重播缓冲区?

例子:给定原码

public static void main() 
{
  doSomethingUnrelated();

  methodOfInterest(); // line 5 for example

  doSomethingUnrelated();
}

生成的代码应该是这样的:

public static void main() 
{
  doSomethingUnrelated();

  Logger.log("methodOfInterest", "main", 5); 
  methodOfInterest();

  doSomethingUnrelated();
}

public Logger 
{
  public static void log(String method, String callee, int line) 
  { ... }
}

上下文:我的实际 ASM MethodVisitor 如下所示:

class UsageClassMethodVisitor extends MethodVisitor implements Opcodes
{
    private final String fileName;
    private final String visitedClass;
    private final String visitedMethod;
    private int lineNumber;

    UsageClassMethodVisitor(MethodVisitor mv, String fileName, String visitedClass, String visitedMethod, boolean isStatic)
    {
        super(Opcodes.ASM5, mv);
        this.fileName = fileName;
        this.visitedClass = visitedClass;
        this.visitedMethod = visitedMethod;
    }

    @Override
    public void visitLineNumber(int i, Label label) {
        lineNumber = i;
        super.visitLineNumber(i, label);
    }

    @Override
    public void visitMethodInsn(int access, String ownerClass, String method, String signature, boolean isInterface) {
        if(ownerClass.contains("org/example/package/")) {
            System.out.printf("prepending to visitMethodInsn(%s, %s, %s, %b) @ %s.%s:%d\n",
                    ownerClass, method, signature, isInterface,
                    visitedClass, visitedMethod, lineNumber);
            super.visitLdcInsn(fileName);
            super.visitLdcInsn(visitedClass);
            super.visitLdcInsn(visitedMethod);
            super.visitLdcInsn(lineNumber);
            super.visitMethodInsn(Opcodes.INVOKESTATIC,
                    Hook.ACCESS_OWNER_NAME,
                    Hook.ACCESS_METHOD_NAME,
                    Hook.ACCESS_METHOD_DESC, false);
        }
        super.visitMethodInsn(access, ownerClass, method, signature, isInterface);
    }
}

但显然这会引发错误,因为调用时堆栈的长度为 8 而不是 4:

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    org.using.package.Main.main([Ljava/lang/String;)V @13: invokestatic
  Reason:
    Type integer (current frame, stack[8]) is not assignable to 'java/lang/String'
  Current Frame:
bci: @13
flags: { }
locals: { '[Ljava/lang/String;' }
stack: { long, long_2nd, long, long_2nd, 'java/util/concurrent/TimeUnit', 'java/lang/String', 'java/lang/String', 'java/lang/String', integer }

没有理由将堆栈存储在任何地方,因为,堆栈很好,一个堆栈。您只需推送日志参数,调用日志记录函数,弹出结果,原始堆栈仍然存在。