I am new to ASM and I want some help related to bytecode transformation.
Question: I would like to add try/catch block for the entire method in bytecode through ASM and want to run the method with out using java -noverify option. I can able to add try/catch block for the entire method but when I tried to execute the method I am getting 'java.lang.VerifyError'. If I use java -noverify option then it will run. Please help me.
Below are the details.
public class Example {
public static void hello() {
System.out.println("Hello world");
}
}
I want to transform the above code as below introducing try/catch blocks, with ASM bytecode instrumentation.
public class Example {
public static void hello() {
try
{
System.out.println("Hello world");
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Below code add try/catch block but fails to execute the code with out java -noverify option.
public class InstrumentExample {
/**
* Our custom method modifier method visitor class. It delegate all calls to
* the super class. Do our logic of adding try/catch block
*
*/
public static class ModifierMethodWriter extends MethodVisitor {
// methodName to make sure adding try catch block for the specific
// method.
private String methodName;
// below label variables are for adding try/catch blocks in instrumented
// code.
private Label lTryBlockStart;
private Label lTryBlockEnd;
private Label lCatchBlockStart;
private Label lCatchBlockEnd;
/**
* constructor for accepting methodVisitor object and methodName
*
* @param api: the ASM API version implemented by this visitor
* @param mv: MethodVisitor obj
* @param methodName : methodName to make sure adding try catch block for the specific method.
*/
public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) {
super(api, mv);
this.methodName = methodName;
}
// We want to add try/catch block for the entire code in the method
// so adding the try/catch when the method is started visiting the code.
@Override
public void visitCode() {
super.visitCode();
// adding try/catch block only if the method is hello()
if (methodName.equals("hello")) {
lTryBlockStart = new Label();
lTryBlockEnd = new Label();
lCatchBlockStart = new Label();
lCatchBlockEnd = new Label();
// set up try-catch block for RuntimeException
visitTryCatchBlock(lTryBlockStart, lTryBlockEnd,
lCatchBlockStart, "java/lang/Exception");
// started the try block
visitLabel(lTryBlockStart);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// closing the try block and opening the catch block if the method
// is hello()
if (methodName.equals("hello")) {
// closing the try block
visitLabel(lTryBlockEnd);
// when here, no exception was thrown, so skip exception handler
visitJumpInsn(GOTO, lCatchBlockEnd);
// exception handler starts here, with RuntimeException stored
// on stack
visitLabel(lCatchBlockStart);
// store the RuntimeException in local variable
visitVarInsn(ASTORE, 2);
// here we could for example do e.printStackTrace()
visitVarInsn(ALOAD, 2); // load it
visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception",
"printStackTrace", "()V", false);
// exception handler ends here:
visitLabel(lCatchBlockEnd);
}
super.visitMaxs(maxStack, maxLocals);
}
}
/**
* Our class modifier class visitor. It delegate all calls to the super
* class Only makes sure that it returns our MethodVisitor for every method
*
*/
public static class ModifierClassWriter extends ClassVisitor {
private int api;
public ModifierClassWriter(int api, ClassWriter cv) {
super(api, cv);
this.api = api;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
// Our custom MethodWriter
ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name);
return mvw;
}
}
public static void main(String[] args) throws IOException {
DataOutputStream dout = null;
try {
// loading the class
InputStream in = InstrumentExample.class
.getResourceAsStream("Example.class");
ClassReader classReader = new ClassReader(in);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// Wrap the ClassWriter with our custom ClassVisitor
ModifierClassWriter mcw = new ModifierClassWriter(ASM4, cw);
ClassVisitor cv = new CheckClassAdapter(mcw);
classReader.accept(cv, 0);
byte[] byteArray = cw.toByteArray();
dout = new DataOutputStream(new FileOutputStream(new File("Example.class")));
dout.write(byteArray);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (dout != null)
dout.close();
}
}
}
For debugging I have used CheckClassAdapter and I got below verification problem.
Message:org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off end of the code
at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
at com.mfr.instrumentation.selenium.work.InstrumentExample.main(InstrumentExample.java:166)
hello()V
00000 ? : L0
00001 ? : GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
00002 ? : LDC "Hello world"
00003 ? : INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
00004 ? : RETURN
00005 ? : L1
00006 ? : GOTO L2
00007 ? : L3
00008 ? : ASTORE 2
00009 ? : ALOAD 2
00010 ? : INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
00011 ? : L2
TRYCATCHBLOCK L0 L1 L3 java/lang/Exception
I failed to understand the above verification message.
You need to traverse your class and use a modified MethodVisitor
in the process. If you wrap your entire method in a try
-catch
construct. You can do so by inserting the construct by intercepting the callbacks for the start and the end of the call block. These methods are visitCode
and visitEnd
which you could intercept like this:
class MyMethodVisitor extends MethodVisitor {
// constructor omitted
private final Label start = new Label(),
end = new Label(),
handler = new Label();
@Override
public void visitCode() {
super.visitCode();
visitTryCatchBlock(start,
end,
handler,
"java/lang/Exception");
visitLabel(start);
}
@Override
public void visitEnd() {
visitJumpInsn(GOTO, end);
visitLabel(handler);
visitMethodInsn(INVOKEVIRTUAL,
"java/lang/RuntimeException",
"printStackTrace",
"()V");
visitInsn(RETURN);
visitLabel(lCatchBlockEnd);
super.visitEnd();
}
}
However, note that this example does not include stack map frames which you need to add if you produce byte code for Java 7+.
Do however note that this solution will register a dominant handler at the beginning of your method's exception table which override all other try-catch-finally blocks in your method that are already present!
Note: In newer versions of ASM, the code for the handler would need to be written in the method visitMaxs(int, int) instead:
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// visit the corresponding instructions
super.visitMaxs(maxStack, maxLocals);
}
This is because labels and instructions can only be visited before visitMaxs
, and visitMaxs
before visitEnd
, therefore producing code in visitEnd
will result in an error.
The above exception is related to computing stackmap frames. ASM has provided mechanism to provide stackmap frames itself. We need to use the parameter flag as COMPUTE_FRAMES in ClassWriter constructor.
Ex: ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
public static final int COMPUTE_FRAMES Flag to automatically compute the stack map frames of methods from scratch. If this flag is set, then the calls to the MethodVisitor.visitFrame(int, int, java.lang.Object[], int, java.lang.Object[]) method are ignored, and the stack map frames are recomputed from the methods bytecode. The arguments of the visitMaxs method are also ignored and recomputed from the bytecode. In other words, computeFrames implies computeMaxs.
from ASM ClassWriter API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With