我在我的应用程序(使用烟尘处理字节码)中发现了一个只出现在特定字节码指令上的错误。
我想为那个特定的情况创建一个测试。然而,我不能可靠地编写测试代码,它将编译成预期的字节码,这将触发错误。
这是我试图触发的错误:
public void updateRhsOnIfEq() {
int x = 15;
int y = AircraftControl.readSensor(0);
// FIXME != in bytecode instead of ==
if (x == y) {
AircraftControl.readSensor(y);
}
else {
AircraftControl.readSensor(x);
}
}
问题是,编译器通过反转比较和切换两个分支来改变分支逻辑。正如您在下面的字节码中看到的,它执行!=
比较而不是==
。然而,我正在测试的bug仅由==
触发。
public void updateRhsOnIfEq();
0 bipush 15
2 istore_1 [x]
3 iconst_0
4 invokestatic AircraftControl.readSensor(int) : int [17]
7 istore_2 [y]
8 iload_1 [x]
9 iload_2 [y]
10 if_icmpne 21 <============================== Should be if_icmpeq
13 iload_2 [y]
14 invokestatic AircraftControl.readSensor(int) : int [17]
17 pop
18 goto 26
21 iload_1 [x]
22 invokestatic AircraftControl.readSensor(int) : int [17]
25 pop
26 return
是否有一种方法来编写测试用例,需要容易地产生可预测的字节码?考虑到有不同的Java编译器,版本等,这是可能的吗?
如果需要特定的字节码指令,最明显且最可靠的方法是直接用字节码编写。
我在这里写了一个开源的汇编器。对于简单的情况,您可以使用Jasmin之类的东西,它可能会有更好的文档记录。我也有一个反汇编器,所以如果你只需要微小的调整,你可以只编译一个Java类,反汇编,调整,然后重新组装。
编译器不改变分支逻辑。
if_icmpeq
,只需更改您的代码如下:
if (x != y) {
AircraftControl.readSensor(x);
}
else {
AircraftControl.readSensor(y);
}
这个代码:public static void main(String[] args) {
int x = (int) System.currentTimeMillis(), y = (int) System
.currentTimeMillis();
if (x != y) {
System.out.println("x != y");
} else {
System.out.println("x == y");
}
}
结果: 0: invokestatic #16 // Method java/lang/System.currentTimeMillis:()J
3: l2i
4: istore_1
5: invokestatic #16 // Method java/lang/System.currentTimeMillis:()J
8: l2i
9: istore_2
10: iload_1
11: iload_2
12: if_icmpeq 26
15: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc #26 // String x != y
20: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: goto 34
26: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #34 // String x == y
31: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: return
处理如何生成测试用例的问题,您是正确的。不同的Java编译器和同一编译器的不同版本可能产生不同的字节码……随着时间的推移。
你的选择是:
- 如果编译器可以为您生成测试用例,那么使用它
- 或者您可以:
- 采用一个现有的编译器生成的测试用例,它与 类似
- 反汇编它以确保你知道字节在文件 中的位置
- 使用十六进制编辑器来调整指令;例如,将
if_icmpne
更改为if_icmpeq
- 或者您可以使用Java汇编语言从头编写示例代码。
一旦您生成了测试用例,您需要捕获并将它们存储为类文件。不要依赖于动态地重新生成它们。