确定catch块结束ASM的位置

时间:2015-01-08 20:52:43

标签: java bytecode java-bytecode-asm bytecode-manipulation

在ASM中,我试图确定try-catch块的标签。

目前我有:

public void printTryCatchLabels(MethodNode method) {

    if (method.tryCatchBlocks != null) {
        for (int i = 0; i < method.tryCatchBlocks.size(); ++i) {

            Label start = method.tryCatchBlocks.get(i).start.getLabel();
            Label end = method.tryCatchBlocks.get(i).end.getLabel();
            Label catch_start = method.tryCatchBlocks.get(i).handler.getLabel();

            System.out.println("try{      " + start.toString());
            System.out.println("}         " + end.toString());
            System.out.println("catch {   " + catch_start.toString());
            System.out.println("}         "  /*where does the catch block end?*/);

        }
    }

}

我试图确定标签在捕获区块末尾的位置,但我不知道如何。我为什么需要它?因为我想&#34;删除&#34; try-catch来自字节码的块。

例如,我想改变:

public void test() {
    try {
        System.out.println("1");
    } catch(Exception e) {
        //optionally rethrow e.
    }
    System.out.println("2");
}

为:

public void test() {
    System.out.println("1");
    System.out.println("2");
}

所以要删除它,我想我可以获取标签并删除catch-start和catch-end之间的所有指令,然后删除所有标签。

有什么想法吗?

4 个答案:

答案 0 :(得分:1)

简而言之,您必须执行执行流程分析。在您的示例中:

public void test() {
    try {                         // (1) try start
        System.out.println("1");
    }                             // (2) try end
    catch(Exception e) {          
        //optionally rethrow e.   // (3) catch start
    }                             // (4) catch end
    System.out.println("2");      // (5) continue execution
}

图形上看起来像这样:

---(1)-+--(2)---------------------+
       |                          +--(5 execution path merged)
       +--(3 branched here)--(4)--+

因此,您需要构建代码块的图形,然后删除与(3)和(4)相关的节点。目前,ASM不提供执行流分析工具,但有些用户报告说他们在ASM的树包之上构建了这样的工具。

答案 1 :(得分:1)

我建议您阅读JVM Spec §3.12. Throwing and Handling Exceptions。它包含一个非常简单的示例,但仍然表现出您的想法的问题:

  

try-catch构造的编译很简单。例如:

void catchOne() {
    try {
        tryItOut();
    } catch (TestExc e) {
        handleExc(e);
    }
}
     

编译为:

     
Method void catchOne()
0   aload_0             // Beginning of try block
1   invokevirtual #6    // Method Example.tryItOut()V
4   return              // End of try block; normal return
5   astore_1            // Store thrown value in local var 1
6   aload_0             // Push this
7   aload_1             // Push thrown value
8   invokevirtual #5    // Invoke handler method: 
                        // Example.handleExc(LTestExc;)V
11  return              // Return after handling TestExc
Exception table:
From    To      Target      Type
0       4       5           Class TestExc

这里,catch块以return指令结束,因此不与原始代码流连接。但是,这不是必需的行为。相反,编译后的代码可以有一个分支到最后return指令代替4 return指令,即

Method void catchOne()
0:  aload_0
1:  invokevirtual #6   // Method tryItOut:()V
4:  goto          13
7:  astore_1
8:  aload_0
9:  aload_1
10: invokevirtual #5   // Method handleExc:(LTestExc;)V
13: return
Exception table:
From    To      Target      Type
   0     4           7      Class TestExc

(例如,至少有一个Eclipse版本正是以这种方式编译了这个例子)

但也可能反之亦然,指示4指示代替最后return指令。

Method void catchOne()
0   aload_0
1   invokevirtual #6    // Method Example.tryItOut()V
4   return
5   astore_1
6   aload_0
7   aload_1
8   invokevirtual #5   // Method Example.handleExc(LTestExc;)V
11  goto 4
Exception table:
From    To      Target      Type
0       4       5           Class TestExc

因此,您已经有三种可能性来编译这个不包含任何条件的简单示例。与循环或if指令相关联的条件分支不一定指向条件代码块之后的指令。如果该代码块后面跟着另一个流控制指令,则条件分支(同样适用于switch目标)可能会使分支短路。

因此很难确定哪个代码属于catch块。在字节代码级别上,它甚至不必是连续的块,而是可以与其他代码交错。

此时我们甚至没有谈论compiling finallysynchronized或更新的try(…) with resources声明。它们最终都会创建在字节码级别上看起来像catch块的异常处理程序。


由于异常处理程序中的分支指令在从异常中恢复时可能会以处理程序之外的代码为目标,因此遍历异常处理程序的代码图对此没有帮助,因为正确处理分支指令需要有关分支目标的信息。你真的想要聚会。

因此,处理此任务的唯一方法是执行反向。您必须从方法的开头遍历代码图以进行非异常执行,并将每个遇到的指令视为不属于异常处理程序。对于剥离异常处理程序的简单任务,这已经足够了,因为您只需保留所有遇到的指令并删除所有其他指令。

答案 2 :(得分:1)

在相对常见的情况下,捕获块的末端很容易检测到。我在这里假设我们正在使用Java编译器。

  • 当try块(在开始和结束标签之间)以GOTO(joinLabel)结尾时。如果该块没有引发异常或始终不返回,也不中断/继续执行周围的循环,则它将以GOTO结尾,该指针指向最后一个处理程序的末尾。
  • 与不是最后一个块的catch块相同,它们将跳过跳转到处理程序,然后使用GOTO来帮助您识别最后一个处理程序的结尾。因此,如果try块没有这样的GOTO,您可能会在其他处理程序中找到一个。
      通过比较TRYCATCH指令的处理程序标签与相同的开始和结束标签,可以检测到这些非最后捕获块。下一个处理程序的开始标签充当上一个处理程序的(唯一)结束。

答案 3 :(得分:0)

在字节码级别,除了处理之外基本上是goto。代码不必是结构化的,甚至根本没有明确定义的catch块。即使你只处理正常编译的Java代码,一旦你考虑了在catch块中尝试资源或复杂的控制流结构的可能性,它仍然非常复杂。

如果您只想删除与“catch块”关联的代码,我建议您只需删除相关的异常处理程序条目,然后执行死代码消除传递。您可以在某处找到现有的DCE传递(例如,Soot),或者您可以编写自己的传递。