如何覆盖类文件 (asm.ClassWriter.getCommonSuperClass)?

AKS*_*AKS 4 bytecode java-bytecode-asm

What I am trying to do?
Run Code Online (Sandbox Code Playgroud)

我正在尝试在特定方法的开头和结尾添加 try/catch 块。

Why am I overriding asm.ClassWriter.getCommonSuperClass(String class1,String class2)?
Run Code Online (Sandbox Code Playgroud)

我正在使用标志 COMPUTE_FRAMES,因此,正在调用 asm.ClassWriter.getCommonSuperClass() 这个类,它试图使用 class.ForName() 再次加载一些类,并说 classNotFoundException。我在某处阅读以覆盖此方法并确保它加载了这两个类。我得到了 Instrumentation 对象并得到了所有加载的类,但仍有一些类没有加载,这个方法抛出 NullPointer 异常..

Any suggesstions how to override it? 
Run Code Online (Sandbox Code Playgroud)

根据以下回复编辑问题

我在这里的理解是:

1. 如果我想为方法内容添加 try/catch 块,则无需使用 COMPUTE_FRAMES 而不是 COMPUTE_MAXS。

2. 如果我只想为方法内容添加 try/catch 块,(仅假设 jdk8)那么我只需要编写 try/catch 块 ASM 部分,其余部分应该就位。

对于从线程调用的方法:

public void execute()throws IOException{
//some code

}
Run Code Online (Sandbox Code Playgroud)

下面的代码应该添加 try/catch 块并且不应该给出任何 java 验证错误?:

private Label startFinally = new Label();
  public void visitMaxs(int maxStack, int maxLocals) {
       Label endFinally = new Label();
       visitTryCatchBlock(startFinally, endFinally, endFinally, "java/lang/Exception");
       visitLabel(endFinally);
       visitFrame(F_NEW, 0, null, 1, new Object[]{"java/lang/Exception"});
       visitVarInsn(ASTORE, 1);
       visitVarInsn(ALOAD, 1);
       visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
       visitInsn(RETURN);
}

  public void visitCode() {     

        mv.visitLabel(startFinally);  
        super.visitCode();
    }
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 6

当您开始不得不处理时,getCommonSuperClass您就进入了堆栈映射框架旨在解决的问题。与其让验证器执行这种常见的超类搜索,不如让从编译器可用的信息派生的帧告诉要假设的类型,并且验证正确性比执行此搜索更便宜。

但是,当然,如果您使用像 ASM 这样的框架并让它在没有编译器可用信息的情况下方便地从头开始计算所有这些帧,您最终会执行这种昂贵的操作,甚至必须在类型不可用的情况下协助 ASM到一个简单的Class.forName调用(或者您想避免加载类)。

你应该注意两件重要的事情:

  • 您不必加载这些类。该方法特意提供了两个字符串并期望得到一个结果字符串。如果您有可用的元信息,允许您根据名称确定常见的超类型,则可以使用它

  • 当您使用 Instrumentation 在所有加载的类中搜索名称时,您可能会错过该类,因为它可能尚未加载。更糟糕的是,在更复杂的场景中,当不同的ClassLoaders加载了同名的类时,您可能会得到错误的类


这时候就应该考虑,坚持使用已知信息生成正确框架的初衷是否是一种选择。当您检测堆栈映射帧是强制性的版本类时,除了异常处理程序所需的帧之外的所有帧都已经存在。对于没有框架的旧类文件,您无论如何都不需要计算它们。

当您将aClassReader与 a 链接时,ClassWriter它不仅会复制成员和指令,还会复制堆栈映射帧,除非您指定COMPUTE_FRAMES哪个会导致 ASM 丢弃所有访问过的帧并从头开始重新计算它们。所以首先要做的是改COMPUTE_FRAMESCOMPUTE_MAXS,然后visitFrame根据需要插入调用。

为了用一个异常处理程序覆盖整个方法,我们只需要一帧,用于处理程序的入口(假设处理程序本身内部没有分支)。我们已经知道操作数栈由对异常本身的唯一引用组成——异常处理程序总是如此。由于受保护的代码跨越整个方法,在方法内部引入的额外局部变量不可用,所以只有this(如果方法不是static)和参数可用——除非方法的代码将它们重用于其他目的(普通的Java代码通常不't)。但好消息是,除非您想使用它们,否则您不必处理它们。

所以让我们假设我们想用一个异常处理程序覆盖整个方法,该处理程序将捕获异常,打印其堆栈跟踪并返回(假设void)。那么,我们就不需要任何局部变量了,整个代码在完全访问原代码后应用,看起来像:

Label endFinally = new Label();
visitTryCatchBlock(startFinally, endFinally, endFinally, "java/lang/Exception");
visitLabel(endFinally);
visitFrame(F_NEW, 0, null, 1, new Object[]{"java/lang/Exception"});
visitVarInsn(ASTORE, 1);
visitVarInsn(ALOAD, 1);
visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
visitInsn(RETURN);
Run Code Online (Sandbox Code Playgroud)

逻辑是

visitFrame(F_NEW,                          // not derived from a previous frame
    0, null,                               // no local variables
    1, new Object[]{"java/lang/Exception"} // one exception on the stack
);
Run Code Online (Sandbox Code Playgroud)

当然,堆栈上的异常类型必须visitTryCatchBlock与其类型匹配或者是它的超类型。请注意,我们将进入处理程序引入的局部变量无关紧要。如果你想重新抛出而不是返回,只需替换

visitInsn(RETURN);
Run Code Online (Sandbox Code Playgroud)

visitVarInsn(ALOAD, 1);
visitInsn(ATHROW);
Run Code Online (Sandbox Code Playgroud)

并且关于堆栈映射帧的逻辑不会改变。