Bug in max stack computation with certain jsrs
Certain jsr constructs break ASM's automatic maximum stack size
computation (passing the COMPUTE_MAXS flag to the ClassWriter). An
example of the style of bytecode causing the failure is as follows:
public static Method run:"([Ljava/lang/String;Ljava/io/PrintStream;)I"
stack 4 locals 4
{
iconst_0;
jsr L1;
L1: // returnAddress
astore 0;
ireturn;
}
If a class containing this method is read in and written out with
ASM's ClassReader and ClassWriter, the maximum stack size will
incorrectly be computed as 1 instead of 2. The reason is as follows.
The jsr instruction is visited during the writing process and an edge
with stack size two is correctly added between the first and second
basic block. The jsr instruction, not being an unconditional goto,
causes parsing to continue at the fall-through bytecode, which is a
Label corresponding to L1. visitLabel is then called and because
"compute == MAXS" is true the current block is terminated and an edge
drawn between the current block and the new block starting at L1. This
edge however uses the "current" stack size which does not correspond
to that containing the return address from the jsr (as it correctly
should not, if control were to actually fall through). This means that
there are two edges between the first and second basic block with
differing stack size. It happens that during class file reconstruction
the second of these is traversed and because this causes the PUSHED
flag to be set on the second basic block we never see the correct
stack size for the second basic block. This causes a VerifyError when
the reconstructed class is loaded.
Because of the "Gosling property" of Java bytecode it is not possible
to reach a given bytecode with more than one expression stack size.
Therefore if we already have a given Label as a successor of the
current basic block, that must be due to explicit control flow in this
basic block, and we should take that edge's stack size as the correct
stack size between the current block and the successor rather than
assuming that control falls through to the successor (which it might
not; in situations like jsr/ret we could only determine this by
tracing back to see which jsrs a given ret could possibly return to,
which is not currently done).
A patch for this bug is attached. The fix has been tested fairly well
and appears to address this problem without introducing any new ones.