转载:https://github.com/jacoco/jacoco/wiki/CharacterRangeTable
This page discusses a not yet available feature!
The CharacterRangeTable is a non-standard (not defined in The Java Virtual Machine Specification) optional variable-length attribute in the attributes table of a Code attribute.
Compilers
Can be produced by javac from OracleJDK 1.5 - 1.9 and IBM JDK 1.5 - 1.8 with help of non-standard undocumented option "-Xjcov", which is ignored by Eclipse Compiler.
Support of this attribute is unlikely to disappear soon:
-
OpenJDK JCov can use it for some scenarios
-
javap starting from OpenJDK 7 decodes content of this attribute
-
it receives fixes in recent versions of OpenJDK - JDK-8059453
-
JDK-8020204
Format
See com/sun/tools/classfile/CharacterRangeTable_attribute.java
CharacterRangeTable_attribute {
u2 start_pc;
u2 end_pc;
u4 character_range_start;
u4 character_range_end;
u2 flags;
}
Each character_range encodes line and column as following:
line = character_range >> 10
column = character_range & 0x03ff
How can be used
Condition coverage
Ranges with flags CRT_BRANCH_TRUE (0x0080) and CRT_BRANCH_FALSE (0x0100) are of particular interest, because could be used to map bytecode branches back to conditions in source code.
Logical and
package com.test19;
public class Test05 {
void example(int a, int b) {
if (a == 1 && b == 1)
nop();
}
void nop(){};
}
生成的class文件如下:
public class com.test19.Test05
SourceFile: "Test05.java"
Utf8 1533252190610Utf8 1533252242529minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Methodref #3.#21 // com/test19/Test05.nop:()V
#3 = Class #22 // com/test19/Test05
#4 = Class #23 // java/lang/Object
#5 = Utf8
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 CharacterRangeTable
#10 = Utf8 example
#11 = Utf8 (II)V
#12 = Utf8 StackMapTable
#13 = Utf8 nop
#14 = Utf8 SourceFile
#15 = Utf8 Test05.java
#16 = Utf8 SourceID
#17 = Utf8 1533252190610
#18 = Utf8 CompilationID
#19 = Utf8 1533252242529
#20 = NameAndType #5:#6 // "":()V
#21 = NameAndType #13:#6 // nop:()V
#22 = Utf8 com/test19/Test05
#23 = Utf8 java/lang/Object
{
public com.test19.Test05();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object." ":()V
4: return
LineNumberTable:
line 3: 0
CharacterRangeTable:
void example(int, int);
flags:
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iconst_1
2: if_icmpne 14
5: iload_2
6: iconst_1
7: if_icmpne 14
10: aload_0
11: invokevirtual #2 // Method nop:()V
14: return
LineNumberTable:
line 5: 0
line 6: 10
line 7: 14
CharacterRangeTable:
0, 1, 1407, 140d, 8 // 0, 1, 5:07, 5:13, flow-controller
2, 4, 1407, 140d, 100 // 2, 4, 5:07, 5:13, branch-false
5, 6, 1411, 1417, 10 // 5, 6, 5:17, 5:23, flow-target
0, 6, 1407, 1417, 8 // 0, 6, 5:07, 5:23, flow-controller
7, 9, 1407, 1417, 100 // 7, 9, 5:07, 5:23, branch-false
10, 13, 1804, 180a, 11 // 10, 13, 6:04, 6:10, statement, flow-target
0, 13, 1403, 180a, 1 // 0, 13, 5:03, 6:10, statement
0, 14, 101d, 1c02, 2 // 0, 14, 4:29, 7:02, block
StackMapTable: number_of_entries = 1
frame_type = 14 /* same */
void nop();
flags:
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 8: 0
CharacterRangeTable:
0, 0, 200c, 200d, 2 // 0, 0, 8:12, 8:13, block
}
if branch 2 was taken, then condition 3:09-3:15 (a == 1) evaluates to false,
if branch 7 was taken, then condition 3:09-3:25 (a == 1 && b == 1) evaluates to false.
Logical or
package com.test19;
public class Test05 {
void example(int a, int b) {
if (a == 1 || b == 1) {
nop();
}
}
void nop() {}
}
public class com.test19.Test05
SourceFile: "Test05.java"
Utf8 1533253036302Utf8 1533253190932minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Methodref #3.#21 // com/test19/Test05.nop:()V
#3 = Class #22 // com/test19/Test05
#4 = Class #23 // java/lang/Object
#5 = Utf8
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 CharacterRangeTable
#10 = Utf8 example
#11 = Utf8 (II)V
#12 = Utf8 StackMapTable
#13 = Utf8 nop
#14 = Utf8 SourceFile
#15 = Utf8 Test05.java
#16 = Utf8 SourceID
#17 = Utf8 1533253036302
#18 = Utf8 CompilationID
#19 = Utf8 1533253190932
#20 = NameAndType #5:#6 // "":()V
#21 = NameAndType #13:#6 // nop:()V
#22 = Utf8 com/test19/Test05
#23 = Utf8 java/lang/Object
{
public com.test19.Test05();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object." ":()V
4: return
LineNumberTable:
line 3: 0
CharacterRangeTable:
void example(int, int);
flags:
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iconst_1
2: if_icmpeq 10
5: iload_2
6: iconst_1
7: if_icmpne 14
10: aload_0
11: invokevirtual #2 // Method nop:()V
14: return
LineNumberTable:
line 5: 0
line 6: 10
line 8: 14
CharacterRangeTable:
0, 1, 1407, 140d, 8 // 0, 1, 5:07, 5:13, flow-controller
2, 4, 1407, 140d, 80 // 2, 4, 5:07, 5:13, branch-true
5, 6, 1411, 1417, 10 // 5, 6, 5:17, 5:23, flow-target
0, 6, 1407, 1417, 8 // 0, 6, 5:07, 5:23, flow-controller
7, 9, 1407, 1417, 100 // 7, 9, 5:07, 5:23, branch-false
10, 13, 1804, 180a, 1 // 10, 13, 6:04, 6:10, statement
10, 13, 1419, 1c03, 13 // 10, 13, 5:25, 7:03, statement, block, flow-target
0, 13, 1403, 1c03, 1 // 0, 13, 5:03, 7:03, statement
0, 14, 101d, 2002, 2 // 0, 14, 4:29, 8:02, block
StackMapTable: number_of_entries = 2
frame_type = 10 /* same */
frame_type = 3 /* same */
void nop();
flags:
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 9: 0
CharacterRangeTable:
0, 0, 240d, 240e, 2 // 0, 0, 9:13, 9:14, blo ck
}
if branch 2 was taken, then condition 3:09-3:15 (a == 1) evaluates to true,
if branch 7 was taken, then condition 3:09-3:25 (a == 1 || b == 1) evaluates to false.
Conditional operator
package com.test19;
public class Test05 {
void example(int a, int b) {
nop(a == 1 ? 2 : 3);
}
void nop(int x) {}
}
public class com.test19.Test05
SourceFile: "Test05.java"
Utf8 1533253525422Utf8 1533253540376minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#22 // java/lang/Object."":()V
#2 = Methodref #3.#23 // com/test19/Test05.nop:(I)V
#3 = Class #24 // com/test19/Test05
#4 = Class #25 // java/lang/Object
#5 = Utf8
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 CharacterRangeTable
#10 = Utf8 example
#11 = Utf8 (II)V
#12 = Utf8 StackMapTable
#13 = Class #24 // com/test19/Test05
#14 = Utf8 nop
#15 = Utf8 (I)V
#16 = Utf8 SourceFile
#17 = Utf8 Test05.java
#18 = Utf8 SourceID
#19 = Utf8 1533253525422
#20 = Utf8 CompilationID
#21 = Utf8 1533253540376
#22 = NameAndType #5:#6 // "":()V
#23 = NameAndType #14:#15 // nop:(I)V
#24 = Utf8 com/test19/Test05
#25 = Utf8 java/lang/Object
{
public com.test19.Test05();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
CharacterRangeTable:
void example(int, int);
flags:
Code:
stack=3, locals=3, args_size=3
0: aload_0
1: iload_1
2: iconst_1
3: if_icmpne 10
6: iconst_2
7: goto 11
10: iconst_3
11: invokevirtual #2 // Method nop:(I)V
14: return
LineNumberTable:
line 5: 0
line 6: 14
CharacterRangeTable:
1, 2, 1407, 140d, 8 // 1, 2, 5:07, 5:13, flow-controller
3, 5, 1407, 140d, 100 // 3, 5, 5:07, 5:13, branch-false
6, 6, 1410, 1411, 10 // 6, 6, 5:16, 5:17, flow-target
10, 10, 1414, 1415, 10 // 10, 10, 5:20, 5:21, flow-target
0, 13, 1403, 1417, 1 // 0, 13, 5:03, 5:23, statement
0, 14, 101d, 1802, 2 // 0, 14, 4:29, 6:02, block
StackMapTable: number_of_entries = 2
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class com/test19/Test05 ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class com/test19/Test05, int, int ]
stack = [ class com/test19/Test05, int ]
void nop(int);
flags:
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 7: 0
CharacterRangeTable:
0, 0, 1c12, 1c13, 2 // 0, 0, 7:18, 7:19, block
}
if branch 2 was taken, then condition 3:09-3:15 (a == 1) evaluates to false.
Filtering
Ranges with flag CRT_BLOCK (0x0002) could be used to filter synthetic compiler constructs.
Duplicated finally blocks
package com.test19;
public class Test05 {
void example() {
try {
nop();
} finally {
nop();
}
}
void nop() {}
}
public class com.test19.Test05
SourceFile: "Test05.java"
Utf8 1533253682139Utf8 1533253727858minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = Methodref #3.#21 // com/test19/Test05.nop:()V
#3 = Class #22 // com/test19/Test05
#4 = Class #23 // java/lang/Object
#5 = Utf8
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 CharacterRangeTable
#10 = Utf8 example
#11 = Utf8 StackMapTable
#12 = Class #24 // java/lang/Throwable
#13 = Utf8 nop
#14 = Utf8 SourceFile
#15 = Utf8 Test05.java
#16 = Utf8 SourceID
#17 = Utf8 1533253682139
#18 = Utf8 CompilationID
#19 = Utf8 1533253727858
#20 = NameAndType #5:#6 // "":()V
#21 = NameAndType #13:#6 // nop:()V
#22 = Utf8 com/test19/Test05
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/Throwable
{
public com.test19.Test05();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
CharacterRangeTable:
void example();
flags:
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokevirtual #2 // Method nop:()V
4: aload_0
5: invokevirtual #2 // Method nop:()V
8: goto 18
11: astore_1
12: aload_0
13: invokevirtual #2 // Method nop:()V
16: aload_1
17: athrow
18: return
Exception table:
from to target type
0 4 11 any
11 12 11 any
LineNumberTable:
line 6: 0
line 8: 4
line 9: 8
line 8: 11
line 10: 18
CharacterRangeTable:
0, 3, 1804, 180a, 1 // 0, 3, 6:04, 6:10, statement
0, 3, 1407, 1c03, 2 // 0, 3, 5:07, 7:03, block
4, 7, 2004, 200a, 1 // 4, 7, 8:04, 8:10, statement
4, 7, 1c0d, 2403, 2 // 4, 7, 7:13, 9:03, block
12, 15, 2004, 200a, 1 // 12, 15, 8:04, 8:10, statement
12, 15, 1c0d, 2403, 2 // 12, 15, 7:13, 9:03, block
0, 17, 1403, 2403, 1 // 0, 17, 5:03, 9:03, statement
0, 18, 1011, 2802, 2 // 0, 18, 4:17, 10:02, block
StackMapTable: number_of_entries = 2
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 6 /* same */
void nop();
flags:
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 12: 0
CharacterRangeTable:
0, 0, 300d, 300e, 2 // 0, 0, 12:13, 12:14, block
}
same source code corresponds to instructions 3-5 and 10-12.