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

标签 bytecode java-bytecode-asm

What I am trying to do?

我正在尝试在特定方法的开头和结尾添加 try/catch 块。
Why am I overriding asm.ClassWriter.getCommonSuperClass(String class1,String class2)?

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

根据以下回复编辑问题

我在这里的理解是:

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

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

对于从线程调用的方法:
public void execute()throws IOException{
//some code

}

下面的代码应该添加 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();
    }

最佳答案

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

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

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

  • 您不必加载这些类。该方法特意提供了两个字符串并期望得到一个结果字符串。如果您有可用的元信息,允许您根据名称确定常见的父类(super class)型,则可以使用它
  • 当您使用 Instrumentation 在所有加载的类中搜索名称时,您可能会错过该类,因为它可能尚未加载。更糟糕的是,在更复杂的场景中,当同名的类被不同的 ClassLoader 加载时,您可能会得到错误的类。 s


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

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

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

    所以让我们假设我们想用一个异常处理程序覆盖整个方法,该处理程序将捕获异常,打印其堆栈跟踪并返回(假设 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);
    

    逻辑是
    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
    );
    

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


    visitVarInsn(ALOAD, 1);
    visitInsn(ATHROW);
    

    并且关于堆栈映射帧的逻辑没有改变。

    关于bytecode - 如何覆盖类文件 (asm.ClassWriter.getCommonSuperClass)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35692336/

    相关文章:

    java - Java 字节码如何在 Java FX 自包含应用程序包中执行?

    java - asm 中的静态初始化程序

    java - 如何更改闭源类以操纵其中的方法

    java - ASM 库计算的堆栈大小错误

    java - 使用 ASM 或 Javassist 提高字段获取和设置性能

    java - 通过使用 BCEL 解析 Java 字节码来确定 LCOM4(方法中缺乏内聚)

    Java 字节码缺少局部变量表

    java - ASM 字节码操作 : Measuring method execution speed

    JAVA ASM : Why does modification cause nested exception?

    java - ASM 动态子类创建 - NoClassDefFoundError BeanInfo