如何轻松创建Java字节码相关的回归测试



我在我的应用程序(使用烟尘处理字节码)中发现了一个只出现在特定字节码指令上的错误。

我想为那个特定的情况创建一个测试。然而,我不能可靠地编写测试代码,它将编译成预期的字节码,这将触发错误。

这是我试图触发的错误:

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_icmpne是编译器的自然行为(只是我的意见)要使(eclipse)编译器使用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汇编语言从头编写示例代码。

一旦您生成了测试用例,您需要捕获并将它们存储为类文件。不要依赖于动态地重新生成它们。

相关内容

  • 没有找到相关文章

最新更新