Suppose I want to call a (logging) method before the invocation of some method of interest. This means that when listening for visitMethodInsn the stack is already populated with the arguments for the method(s) of interest.
Is it possible to then store the current stack somewhere, call the log and repopulate the stack? Am I missing any obvious stack mutation operators? Or do I really need to either:
Example: given the original code
public static void main()
{
doSomethingUnrelated();
methodOfInterest(); // line 5 for example
doSomethingUnrelated();
}
the resulting code should be like:
public static void main()
{
doSomethingUnrelated();
Logger.log("methodOfInterest", "main", 5);
methodOfInterest();
doSomethingUnrelated();
}
public Logger
{
public static void log(String method, String callee, int line)
{ ... }
}
Context: my actual ASM MethodVisitor looks like this:
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);
}
}
but obviously this is throwing an error as the stack is 8 long instead of 4 at the time of invocation:
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 }
There is no reason to store the stack anywhere, because, the stack is well, a stack. You can just push the log arguments, call the logging function, pop the result, and you'll have the original stack still in place.