代码之家  ›  专栏  ›  技术社区  ›  OllieStanley

在Javassist中修改行号

  •  3
  • OllieStanley  · 技术社区  · 12 年前

    所以我最近一直在使用Javassist,遇到了一个我一直找不到答案的问题。CtMethod的insertAt方法允许你在特定的行号插入代码,但它会覆盖或保留该行吗?我如何让它做与默认情况相反的事情?我有一个应用程序,它在运行前使用Javassist修改源代码,基于XML文件中的“hook”。我想让它可以覆盖一行,或者把一行放在行的上方,而不是覆盖它。显然有一些很难做到的方法,但我宁愿使用正确的方法。

    1 回复  |  直到 12 年前
        1
  •  10
  •   pabrantes Kit Gerrits    12 年前

    简单的部分

    方法 insertAt(int lineNumber, String src) 存在于CtMethod对象中允许注入在 src公司 之前 给定行中的代码。

    例如,以以下(简单的)示例程序为例:

    public class TestSubject {
    
       public static void main(String[] args) {
         TestSubject testSubject = new TestSubject();
         testSubject.print();
       }
    
       private void print() {
        System.out.println("One"); // line 9
        System.out.println("Two"); // line 10
        System.out.println("Three"); // line 11
       }
    }
    

    通过简单编码(请记住,方法变量必须是的CtMethod表示 打印 方法):

       // notice that I said line 10, which is where the sysout of "two" is
       method.insertAt(10, true, "System.out.println(\"one and an half\");");
    

    将在类中注入一条新的sysout指令。新类的输出将是:

     one
     one and an half
     two 
     three
    

    最难的部分

    Javassist并没有提供一种简单的方法来删除一行代码,所以如果你真的想替换它,你别无选择,只能破解它。

    怎么做?好吧,让我把你介绍给你的新朋友(如果你还不知道的话) CodeAttribute 对象

    CodeAttribute对象负责保存表示方法流的字节码。除此之外,该代码属性还有另一个名为 LineNumberAttribute 这有助于将行号映射到字节码数组中。所以总结这个对象就有了你需要的一切!

    下面例子中的想法很简单。将字节码数组中的字节与应删除的行建立关系,并用无操作代码替换这些字节。

    再一次,方法是方法的CtMethod表示 打印

        // let's erase the sysout "Two"
        int lineNumberToReplace = 10;
        // Access the code attribute
        CodeAttribute codeAttribute = method.getMethodInfo().getCodeAttribute();
    
        // Access the LineNumberAttribute
        LineNumberAttribute lineNumberAttribute = (LineNumberAttribute)      codeAttribute.getAttribute(LineNumberAttribute.tag);
    
        // Index in bytecode array where the instruction starts
        int startPc = lineNumberAttribute.toStartPc(lineNumberToReplace);
    
        // Index in the bytecode array where the following instruction starts
        int endPc = lineNumberAttribute.toStartPc(lineNumberToReplace+1);
    
        System.out.println("Modifying from " + startPc + " to " + endPc);
    
        // Let's now get the bytecode array
        byte[] code = codeAttribute.getCode();
        for (int i = startPc; i < endPc; i++) {
          // change byte to a no operation code
           code[i] = CodeAttribute.NOP;
        }
    

    在中运行此修改 起初的 TestSubject类,将导致一个具有以下输出的注入类:

     one
     three
    

    总结

    当您需要添加一行并保留现有行时,只需要使用中给出的示例 容易的部分 如果要替换该行,则必须首先使用中给出的示例删除现有行 坚硬的部分 然后使用第一示例注入新的行。

    还要记住,在我假设您已经熟悉javassist的基本知识的示例中,只显示了有趣的部分,而不是全部交易。这就是为什么,例如,在示例中没有ctClass.writeFile……你仍然需要这样做,我只是忽略了它,因为我希望你应该知道你必须这样做。

    如果您在代码示例中需要任何额外的帮助,只需询问即可。我很乐意帮忙。