如何使用ASM从交换机中删除break语句



我使用ASM框架来操作一些java字节码。我只需要从开关指令中删除break语句。我的尝试删除了字节码中的goto指令,但不仅仅是这些与switch连接的指令(例如所有来自class…)。

你觉得怎么样?

在Java源代码中的BREAK语句和Java字节码中的任何内容之间没有显式链接。一些带有BREAK语句的语言结构可能会被编译成GOTO操作码,但我怀疑你能否在它们之间建立可靠的链接。

您唯一能做的就是捕获Java源代码中BREAK语句的行号(假设这些行没有任何其他语句),然后使用用行号信息编译的字节码可以找到这些行的操作码。

如果goto指令出现在两个开关字节码指令之一的上下文中,则属于switch语句。棘手的部分是确定它们是否代表breaklookupswitchtableswitch都有一个分支目标列表,如果在前面的指令goto指令,那么它可能代表break。这可以通过检查所有或至少大部分goto指令是否具有相同的目标来验证,该目标将是switch语句之后的下一条指令。如果您已经确定了switch语句后面指令的字节码位置,则可以将该位置的所有goto都视为break;

但是这样的启发式可能会严重失败。考虑下面的代码:

outer: for( … ) {
  …
  inner: for(…) {
    switch(…) {
      case 1: …
        continue inner; // jumps to the next iteration of inner
      case 2: …
        continue outer; // jumps to the next iteration of outer
      case 3: …
       // a break: formally jumps to the end of the switch but since
       // there is no follow-up statement, most compilers will optimize
       // this to a jump to the next inner iteration just like <continue>
        break;
      case 4:
       …
       // no break but nonetheless will be followed by a <goto>
    }
  }
}

一般来说,Java代码中的所有非异常、无条件分支都被编译为goto(或goto_w)。这包括break语句、continue语句、无条件循环和任意数量的控制流模式。您将无法在Java代码中的break语句到goto操作码之间派生出任何简单的映射。你可以通过做一些控制流分析来确定哪些跳跃像开关break一样,但它不会是完美的。

一个好的学习经验可能是从Java反编译器中分离源代码,因为反编译器必须重构switch语句,并弄清楚如何将跳转表示为breakcontinue等。Procyon和Krakatau都是开源的。我编写了前者,但是代码库很大且令人生畏,因此它可能不是最佳选择。

最新更新