在 try-finally 块中嵌入方法的现有代码 (2)

nra*_*ner 5 java instrumentation bytecode bytecode-manipulation java-bytecode-asm

前段时间,我在将方法的现有代码嵌入 try-finally 块中询问如何使用 ASM 将方法的主体包装在 try-finally 块中。解决方案是访问 in 方法体开头的 try 块的标签,visitCode()并在访问具有返回操作码 in 的指令时完成 try-finally 块visitInsn()。我知道如果一个方法没有返回指令,则该解决方案将不起作用,如果该方法总是以异常情况离开则适用。

不过,我发现前一种解决方案有时也不适用于带有返回指令的方法。如果一个方法有多个返回指令,它将不起作用。原因是它生成了无效的字节码,因为在方法的开头添加了一个 try-finally 块,但完成了不止一个 try-finally 块。

通常(但可能取决于 javac 编译器),字节码方法包含单个返回指令,并且所有返回路径通过跳转到该指令结束。但是,使用 Eclipse 编译以下代码将导致带有两个返回指令的字节码:

public boolean isEven(int x) {
  return x % 2 == 0;
}
Run Code Online (Sandbox Code Playgroud)

用Eclipse编译的字节码:

   0: iload_1
   1: iconst_2
   2: irem
   3: ifne          8
   6: iconst_1
   7: ireturn       // javac compilation: goto 9
   8: iconst_0
   9: ireturn
Run Code Online (Sandbox Code Playgroud)

因此,我想知道包装方法代码的整个代码的正确方法是什么。

Hol*_*ger 5

你必须回溯Java编译器在编译时所做的事情try \xe2\x80\xa6 finally \xe2\x80\xa6,这意味着将你的finally操作复制到受保护(源)代码块将被保留的每个点(即返回指令)并安装多个受保护(生成的字节代码)区域(因为它们应该\ xe2\x80\x99t 覆盖了你的finally操作),但它们可能都指向同一个异常处理程序。或者,您可以转换代码,将所有返回指令替换为 \xe2\x80\x9cafter\xe2\x80\x9d 操作的一个实例的分支,后跟唯一的返回指令。

\n\n

那\xe2\x80\x99s 并不是微不足道的。因此,如果您不需要热代码替换(通常不支持向加载的类添加方法),避免这一切的最简单方法是将原始方法重命名为不冲突的名称其他人(您可以使用普通源代码中不允许的字符)并使用旧名称和签名创建一个新方法,该方法由一个简单的try \xe2\x80\xa6 finally \xe2\x80\xa6构造组成,其中包含对重命名方法的调用。

\n\n

例如更改public void desired()private void desired$instrumented()并添加新的

\n\n
public void desired() {\n    //some logging X\n\n    try {\n        desired$instrumented();\n    }\n    finally {\n        //some logging Y\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,由于调试信息保留在重命名的方法中,因此如果重命名的方法中引发异常,堆栈跟踪将继续报告正确的行号。如果您通过添加一个不可见字符来重命名它(请记住,您在字节码级别有更多的自由),它会非常顺利。

\n