AnalyzerAdapter calculates wrong type for AALOAD in case of null
Original issue was reported in
https://groups.google.com/d/msg/jacoco/UC5rXh6CFAA/XYfrBnXZBwAJ
where you can also find steps of our analysis of the problem, below is
result of this analysis, credits to Marc Hoffmann for wording:
According to JVMS Chapter "4.10.1.9 Type Checking Instructions, AALOAD",
the resulting type on the operand stack is
1) the component type if the input type is an array
2) or null if the input type is null
This is how the JVM verifier is implemented -- even if the latter will
always lead to a NPE at runtime.
AnalyzerAdapter handles the second case wrongly.
STEPS TO REPRODUCE
1) Visit frame with operand stack [null, int]
2) Visit AALOAD
EXPECTED BEHAVIOR
Operand Stack is [null]
ACTUAL BEHAVIOR
Operand Stack is ["java/lang/Object"]
DETAILED ANALYSIS
Ome might think this situation can be easily produces by the following
snippet:
Object[] array = null;
Object element = array[0];
// some control structure enforcing a frame here
Java compilers (e.g. JDK or ECJ) insert a frame with local variables of
type
["[Ljava/lang/Object;", "java/lang/Object"]
If the same class files has been written by ASM with the COMPUTE_FRAME flag
ASM assumes "null" for the variable, so the frame is
[null, null]
Both frame veriants pass the JVM's bytecode verifier.
If AnalyzerAdapter is used to track the frame status for the first version
the correct frame types are calculated, but for the second version
[null, "java/lang/Object"]
is calculated which fails the JVM's verifier if inserted like this. A
typical error message is:
java.lang.VerifyError: Instruction type does not match stack map
Exception Details:
Location:
org/jacoco/dev/bytecode/AALOADnullTest$Target.run()V @39: aload_1
Reason:
Type 'java/lang/Object' (current frame, locals[3]) is not
assignable to null (stack map, locals[3])
Current Frame:
bci: @39
flags: { }
locals: { 'org/jacoco/dev/bytecode/AALOADnullTest$Target', '[Z',
null, 'java/lang/Object' }
stack: { }
Stackmap Frame:
bci: @39
flags: { }
locals: { 'org/jacoco/dev/bytecode/AALOADnullTest$Target', '[Z',
null, null }
stack: { }
We think that AnalyzerAdapter can be fixed by replacing
case Opcodes.AALOAD:
pop(1);
t1 = pop();
if (t1 instanceof String) {
pushDesc(((String) t1).substring(1));
} else {
push("java/lang/Object");
}
with
case Opcodes.AALOAD:
pop(1);
t1 = pop();
if (t1 instanceof String) {
pushDesc(((String) t1).substring(1));
} else {
push(Opcodes.NULL);
}