It is impossible to wrap whole constructors into try-finally blocks because try blocks cannot span a call to a super constructor. Although I couldn't find this limitation in the specification, I could find two tickets that discuss it: JDK-8172282, asm #317583.
If you don't care about constructors, you can wrap methods into other methods that catch the exceptions as written by Holger. It is an uncomplicated solution that might be fine in many scenarios. This answer, however, describes an alternative solution that does not require the generation of a second method.
The solution is roughly based on "Compiling finally" in the JVM specification. The solution uses the JSR instruction. The instruction is not supported since langue level 7. Therefore, we use the JSRInlinerAdapter
to replace the instructions afterwards.
We will start by creating our own MethodVisitor
. Note that we extend MethodNode
instead of MethodVisitor
. We do this to collect the whole method before we pass the information to the next visitor. More about that later.
public class MyMethodVisitor extends MethodNode {
The visitor needs three labels. The first label designates the begin of the original content and the begin of the try block. The second label designates the end of the original content and the end of the try block. It also designates the start of the exception handler. The last label designates the subroutine that represents the finally block.
private final Label originalContentBegin = new Label();
private final Label originalContentEnd = new Label();
private final Label finallySubroutine = new Label();
The constructor reuses the field mv
of MethodVisitor
. It is not used by MethodNode
. We could have created our own field as well. The constructor also creates the JSRInlinerAdapter
to replace the JSR instruction as mentioned above.
public MyMethodVisitor(
MethodVisitor methodVisitor,
int access, String name, String descriptor,
String signature, String[] exceptions)
{
super(Opcodes.ASM8, access, name, descriptor, signature, exceptions);
mv = new JSRInlinerAdapter(methodVisitor, access, name, descriptor, signature, exceptions);
}
Next, we declare the methods which generate the bytecode that shall be executed before and after the original code is executed.
protected void generateBefore() { /* Generate your code here */ }
protected void generateAfter() { /* Generate your code here */ }
According to the Javadoc of MethodVisitor
, ASM calls
visitCode()
before the content of the method is visited, and
visitMaxs(int,int)
after the content of the method is visited.
Before ASM visits the content of the method, we want to inject our own bytecode and visit our label which designates the begin of the original content.
@Override
public void visitCode() {
super.visitCode();
generateBefore();
super.visitLabel(originalContentBegin);
}
Whenever the original method returns, we want to call our code of the finally block.
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
super.visitJumpInsn(Opcodes.JSR, finallySubroutine);
}
super.visitInsn(opcode);
}
At the end of the method, we inject the exception handler for our try block and the subroutine that contains the finally block.
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitLabel(originalContentEnd);
super.visitJumpInsn(Opcodes.JSR, finallySubroutine);
super.visitInsn(Opcodes.ATHROW);
super.visitLabel(finallySubroutine);
super.visitVarInsn(Opcodes.ASTORE, 0);
generateAfter();
super.visitVarInsn(Opcodes.RET, 0);
super.visitMaxs(maxStack, maxLocals);
}
Finally, we have to create the try-catch block and forward the method to the next method visitor. We couldn't create the try-catch block earlier using the visitor pattern because of an unfavorable order of visitTryCatchBlock(…)
calls (see issue #317617). This This is why we extended MethodNode
instead of MethodVisitor
.
@Override
public void visitEnd() {
super.visitEnd();
tryCatchBlocks.add(new TryCatchBlockNode(
getLabelNode(originalContentBegin),
getLabelNode(originalContentEnd),
getLabelNode(originalContentEnd),
null));
accept(mv);
}
}
Since the transformation does not work with constructors, our method visitor can be used like this in a ClassVisitor
.
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals("<init>")) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
else {
return new MyMethodVisitor(
super.visitMethod(access, name, descriptor, signature, exceptions),
access, name, descriptor, signature, exceptions);
}
}
There is still some room for improvement.
You could avoid the JSR instruction and remove the JSRInlinerAdapter
. This might also provide some opportunities to reduce the size of the generated code because the JSRInlinerAdapter
might duplicate the code of the finally block multiple times.
Even so you cannot catch exceptions of the super constructor, you could add limited support for constructors that handles exceptions before and after the super constructor is called.
Anyway, this changes might also make the code much more complicated.