2

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

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

这是我触发错误的尝试:

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

4

3 回答 3

2

如果你想要特定的字节码指令,显而易见且最可靠的方法是直接用字节码编写。

我已经编写了一个开源汇编器,可以在这里找到。对于简单的情况,您可以使用 Jasmin 之类的东西,这可能会有更好的文档记录。我也有一个反汇编器,所以如果你只需要微调,你可以编译一个 Java 类,反汇编,进行调整,然后重新组装。

于 2013-05-16T03:31:27.773 回答
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
于 2013-05-15T13:33:25.067 回答
1

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

您的替代方案是:

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

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

于 2013-05-15T14:53:29.940 回答