What am I doing
我使用ASM和javaagent来检测类以报告其覆盖率(为什么我不使用jacoco?这与这个问题无关),基本逻辑是,每次调用visitLineNumber
时,我检测一些方法调用(就在访问下一条指令之前)以记录命中行号.
Problem description
通过这样一个简单的逻辑,一个类得到了ClassFormatError:
java.lang.ClassFormatError: StackMapTable format error: bad offset for Uninitialized in method org.apache.commons.math.ode.ContinuousOutputModelTest.buildInterpolator(D[DD)Lorg/apache/commons/math/ode/sampling/StepInterpolator;
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
...
插装前的字节码如下所示.堆栈映射帧所在的指令为@16(offset\u delta=16)和@17(offset\u delta=0).
private org.apache.commons.math.ode.sampling.StepInterpolator buildInterpolator(double, double[], double);
descriptor: (D[DD)Lorg/apache/commons/math/ode/sampling/StepInterpolator;
flags: ACC_PRIVATE
Code:
stack=7, locals=7, args_size=4
0: new #66 // class org/apache/commons/math/ode/sampling/DummyStepInterpolator
3: dup
4: aload_3
5: dload 4
7: dload_1
8: dcmpl
9: iflt 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokespecial #67 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator."<init>":([DZ)V
20: astore 6
22: aload 6
24: dload_1
25: invokevirtual #68 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.storeTime:(D)V
28: aload 6
30: invokevirtual #69 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.shift:()V
33: aload 6
35: dload 4
37: invokevirtual #68 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.storeTime:(D)V
40: aload 6
42: areturn
...
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 16
locals = [ class org/apache/commons/math/ode/ContinuousOutputModelTest, double, class "[D", double ]
stack = [ uninitialized 0, uninitialized 0, class "[D" ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class org/apache/commons/math/ode/ContinuousOutputModelTest, double, class "[D", double ]
stack = [ uninitialized 0, uninitialized 0, class "[D", int ]
插装后,字节码变为:
private org.apache.commons.math.ode.sampling.StepInterpolator buildInterpolator(double, double[], double);
descriptor: (D[DD)Lorg/apache/commons/math/ode/sampling/StepInterpolator;
flags: ACC_PRIVATE
Code:
stack=10, locals=7, args_size=4
0: ldc_w #264 // String org/apache/commons/math/ode/ContinuousOutputModelTest
3: ldc_w #344 // String buildInterpolator
6: ldc_w #345 // int 169
9: invokestatic #272 // Method org/test/cov/CoverageCollector.reportCoverage:(Ljava/lang/String;Ljava/lang/String;I)V
12: new #66 // class org/apache/commons/math/ode/sampling/DummyStepInterpolator
15: dup
16: aload_3
17: dload 4
19: dload_1
20: dcmpl
21: iflt 28
24: iconst_1
25: goto 29
28: iconst_0
29: invokespecial #67 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator."<init>":([DZ)V
32: astore 6
34: ldc_w #264 // String org/apache/commons/math/ode/ContinuousOutputModelTest
37: ldc_w #344 // String buildInterpolator
40: ldc_w #346 // int 170
43: invokestatic #272 // Method org/test/cov/CoverageCollector.reportCoverage:(Ljava/lang/String;Ljava/lang/String;I)V
46: aload 6
48: dload_1
49: invokevirtual #68 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.storeTime:(D)V
52: ldc_w #264 // String org/apache/commons/math/ode/ContinuousOutputModelTest
55: ldc_w #344 // String buildInterpolator
58: ldc_w #347 // int 171
61: invokestatic #272 // Method org/test/cov/CoverageCollector.reportCoverage:(Ljava/lang/String;Ljava/lang/String;I)V
64: aload 6
66: invokevirtual #69 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.shift:()V
69: ldc_w #264 // String org/apache/commons/math/ode/ContinuousOutputModelTest
72: ldc_w #344 // String buildInterpolator
75: ldc_w #348 // int 172
78: invokestatic #272 // Method org/test/cov/CoverageCollector.reportCoverage:(Ljava/lang/String;Ljava/lang/String;I)V
81: aload 6
83: dload 4
85: invokevirtual #68 // Method org/apache/commons/math/ode/sampling/DummyStepInterpolator.storeTime:(D)V
88: ldc_w #264 // String org/apache/commons/math/ode/ContinuousOutputModelTest
91: ldc_w #344 // String buildInterpolator
94: ldc_w #349 // int 173
97: invokestatic #272 // Method org/test/cov/CoverageCollector.reportCoverage:(Ljava/lang/String;Ljava/lang/String;I)V
100: aload 6
102: areturn
...
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 28
locals = [ class org/apache/commons/math/ode/ContinuousOutputModelTest, double, class "[D", double ]
stack = [ uninitialized 0, uninitialized 0, class "[D" ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class org/apache/commons/math/ode/ContinuousOutputModelTest, double, class "[D", double ]
stack = [ uninitialized 0, uninitialized 0, class "[D", int ]
我没有发现StackMapTable有任何问题.你知道为什么StackMapTable格式无效吗?
以下是我的检测代码:
class CoverageMethodVisitor extends MethodVisitor {
private String slashClassName;
private String methodName;
private int currentLine;
private boolean isJUnit3TestClass;
private boolean hasTestAnnotation;
private boolean isTestMethod;
private int classVersion;
private boolean isRightAfterLabel;
protected CoverageMethodVisitor(MethodVisitor methodVisitor, String className, String methodName, boolean isJUnit3TestClass, int classVersion) {
super(ASM_VERSION, methodVisitor);
this.slashClassName = className;
this.methodName = methodName;
this.isJUnit3TestClass = isJUnit3TestClass;
this.classVersion = classVersion;
}
private void instrumentReportCoverageInvocation() {
super.visitLdcInsn(slashClassName);
super.visitLdcInsn(methodName);
super.visitLdcInsn(currentLine);
super.visitMethodInsn(INVOKESTATIC, "org/test/cov/CoverageCollector",
"reportCoverage", "(Ljava/lang/String;Ljava/lang/String;I)V", false);
}
@Override
public void visitInsn(int opcode) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitInsn(opcode);
}
@Override
public void visitIntInsn(int opcode, int operand) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitIntInsn(opcode, operand);
}
@Override
public void visitVarInsn(int opcode, int varIndex) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitVarInsn(opcode, varIndex);
}
@Override
public void visitTypeInsn(int opcode, String type) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitTypeInsn(opcode, type);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLdcInsn(Object value) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitLdcInsn(value);
}
@Override
public void visitIincInsn(int varIndex, int increment) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitIincInsn(varIndex, increment);
}
@Override
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitTableSwitchInsn(min, max, dflt, labels);
}
@Override
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitLookupSwitchInsn(dflt, keys, labels);
}
@Override
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
if (isRightAfterLabel) instrumentReportCoverageInvocation(); isRightAfterLabel = false;
super.visitMultiANewArrayInsn(descriptor, numDimensions);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack+3, maxLocals);
}
/**
* Should not report line coverage immediately after the visitLineNumber. visitLineNumber is called right after
* visitLabel, but it is very possible that a stack map frame is after the label, if insert instructions right
* after the label, the original stack map frame will be messed up. So instead, insert instructions before the
* first instruction after the label. */
@Override
public void visitLineNumber(int line, Label start) {
super.visitLineNumber(line, start);
currentLine = line;
isRightAfterLabel = true;
}
}
/** */
中的 comments 实际上是我的猜测,不确定是否正确.
EDIT:
很抱歉,我误解了101的含义,因为java spec提到:
帧应用的字节码偏移量是通过将offset_delta+1添加到前一帧的字节码偏移量来计算的,除非前一帧是该方法的初始帧...
Summary
Reason of the error
bad offset for Uninitialized
中的Uninitialized
表示由NEW
指令(原始字节码中的第一条指令)生成的未初始化对象.由于原始堆栈映射帧需要使用NEW
指令之前的标签(比如L0
)来表示其locals
和stack
中的未初始化对象,并且插入指令的代码中的L0
不再表示NEW
指令,因此引发了此类错误.
Solution
由于插入指令的代码中的L0
不再代表NEW
指令,我们需要为该NEW
指令创建一个新标签,并在两个堆栈映射帧中用新标签替换旧标签L0
.如果指定了COMPUTE_FRAME
,这样的堆栈映射帧(re)计算将自动完成,但这里我们需要手动完成,因为COMPUTE_FRAME
不用于避免其他潜在问题.