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

时间:2013-05-15 13:26:19

标签: java javac bytecode soot

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

我想为该特定案例创建一个测试。但是,我无法可靠地编写测试代码,这将编译为预期的字节码,然后会触发错误。

这是我尝试触发错误:

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);
        }
    }

问题是,编译器通过反转比较并切换两个分支来更改分支逻辑。正如您在下面的字节码中看到的那样,它会进行!=比较,而不是==。但是,我正在测试的错误仅由==触发。

 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编译器,版本等,这是否可行?

3 个答案:

答案 0 :(得分:2)

编译器不会更改分支逻辑。 这是编译器在这种情况下使用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

答案 1 :(得分:2)

如果您需要特定的字节码指令,那么显而易见且最可靠的方法是直接在字节码中编写它。

我编写了一个可用的here开源汇编程序。对于简单的情况,你可以使用像Jasmin这样的东西,这可能会更好地记录下来。我也有一个反汇编程序,所以如果你只需要进行小的调整,你就可以编译一个Java类,反汇编,调整然后重新组装。

答案 2 :(得分:1)

解决如何生成测试用例的问题,你是对的。不同的Java编译器和同一编译器的不同版本可能会随着时间的推移产生不同的字节码。

您的替代方案是:

  • 如果编译器可以为您生成测试用例,那么使用它
  • 或者您可以:
    • 采用类似的现有编译器生成的测试用例
    • 反汇编以确保您知道文件中的字节位置
    • 使用十六进制编辑器调整说明;例如将if_icmpne更改为if_icmpeq
  • 或者您可以使用Java汇编语言从头开始编写示例代码。

生成测试用例后,您需要捕获并将它们存储为类文件。不要依赖于能够在运行中重新生成它们。