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

时间:2016-02-29 05:07:42

标签: 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 Exception ..

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();
    }

1 个答案:

答案 0 :(得分:4)

当你开始处理getCommonSuperClass时,你正在进入堆栈地图框架旨在解决的问题。从编译器可用的信息中得出的帧不应该让验证者进行这种常见的超类搜索,而应该告诉我们假定哪种类型,并且验证正确性比执行此搜索更便宜。

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

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

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

  • 当您使用Instrumentation在所有已加载的类中搜索名称时,您可能会错过该类,因为它可能尚未加载。更糟糕的是,当一个具有相同名称的类被不同的ClassLoader

  • 加载时,您可能会在更复杂的情况下得到错误的类

此时你应该考虑是否坚持使用已知信息生成正确框架的原始意图是一种选择。在检测必须使用堆栈映射帧的版本的类时,除了异常处理程序所需的帧之外的所有帧都已存在。对于没有帧的旧类文件,无论如何都不需要计算它们。

当您使用ClassReader链接ClassWriter时,它不仅会复制成员和指令,还会复制堆栈映射帧,除非您指定导致ASM删除所有访问过的帧的COMPUTE_FRAMES并从头开始重新计算。因此,首先要做的是将COMPUTE_FRAMES更改回COMPUTE_MAXS,然后根据需要插入visitFrame次来电。

为了用一个异常处理程序覆盖整个方法,我们需要一个帧,用于处理程序的入口(假设处理程序本身内没有分支)。我们已经知道操作数堆栈包含对异常本身的唯一引用 - 异常处理程序总是如此。由于受保护的代码跨越整个方法,因此方法中没有引入其他局部变量,因此只有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的类型匹配,或者是它的超类型。请注意,我们将在进入处理程序后引入的局部变量无关紧要。如果你想重新投掷而不是返回,只需替换

visitInsn(RETURN);

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

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