用Javassist生成Invokedynamic

我想,我正在尝试做一些相对简单的事情。 以一个方法doSomething(int)的下面的Java字节码为例:

public java.lang.String doSomething(int i);
0 iload_1 [i]
1 invokestatic MyHelper.doSomething(int) : Java.lang.String
4 areturn

这个字节码几乎只将呼叫转发给一个静态助手。

我现在想要做的是用invoke动态替换invokestatic使用Javassist。 我知道如何用ASM来做到这一点,因此我只是假设我想知道它是如何工作的,因为纯粹的好奇心。 以下是我的一些问题:

1)以下是否正确:我无法揣测javassist CtMethod.instrument()或CtMethod.insertAt()方法,因为这些方法需要一个包含有效Java表达式的字符串,而且我无法在Java语法中编写invokedynamic?

2)invokestatic参数的处理方式与invokevirtual或invokestatic的参数一样,对吗? 我的意思是,你把参数放在堆栈之前的invokedynamic就像你会为invokevirtual做的那样?

3)是否有使用Javassist创建invokedynamic字节码的示例代码(或者您能想出一些)?

这就是我目前所知道的:您可以创建一个具有addInvokedynamic()方法的Bytecode对象。 但是这需要BootstrapMethodsAttribute中BootstrapMethod的索引。 BootstrapMethod反过来期望常量池中需要方法引用等方法句柄信息的索引。 所以基本上你必须自己管理整个常量池条目。 哪一个是好的,但我担心我没有把握好,并在以后引入奇怪的问题。 有没有更简单的方法来做到这一点(辅助方法等)? 我的代码大致看起来像这样(我并不真正“重写”上面的invokestatic,但是:

void transform(CtMethod ctmethod, String originalCallDescriptor) {

    MethodInfo mInfo = ctmethod.getMethodInfo();
    ConstPool pool = ctmethod.getDeclaringClass().getClassFile().getConstPool();

    /* add info about the static bootstrap method to the constant pool*/
    int mRefIdx = /*somehow create a method reference entry*/
    int mHandleIdx = constPool.addMethodHandleInfo(ConstPool.REF_invokeStatic, mRefIdx);

    /* create bootstrap methods attribute; there can only be one per class file! */
    BootstrapMethodsAttribute.BootstrapMethod[] bms = new BootstrapMethodsAttribute.BootstrapMethod[] {
        new BootstrapMethodsAttribute.BootstrapMethod(mHandleIdx, new int[] {})
    };
    BootstrapMethodsAttribute bmsAttribute = new BootstrapMethodsAttribute(constPool, bms);
    mInfo.addAttribute(bmsAttribute);

    //... and then later, finally
    Bytecode bc = new Bytecode(constPool);
    ... push parameters ...
    bc.addInvokedynamic(0 /*index in bms array*/, mInfo.getName(), originalCallDescriptor);

    //replace the original method body with the one containing invokedynamic
    mInfo.removeCodeAttribute();
    mInfo.setCodeAttribute(bc.toCodeAttribute());

}

非常感谢您的帮助和时间!


我必须说,这是一个很有意思的问题。 如果我的答案有点长,我很抱歉,但我试图尽可能多地给你提供有用的信息,以帮助你和其他人解决这个问题。

问题1

以下是正确的:我不能破坏javassist CtMethod.instrument()或CtMethod.insertAt()方法,因为这些方法期望一个包含有效Java表达式的字符串,并且我不能在Java语法中编写invokedynamic?

你是对的。

CtMethod.insertAt()只能用于Java代码而不是字节码操作码。 另一方面, CtMethod.instrument()允许您使用ExprEditor和CodeConverter以非常有限的方式处理字节码,甚至对其进行修改。 但正如我所说,它允许你改变的东西是非常有限的,而你试图达到的两个修饰符都无法帮助你。

问题2

对invokestatic的参数的处理就像invokevirtual或invokestatic的参数一样,对吗? 我的意思是,你把参数放在堆栈之前的invokedynamic就像你会为invokevirtual做的那样?

我不知道我是否完全明白你在这里问的是什么(你在第一句中重复了一遍)。 我想你在问什么 - 并纠正我,如果我错了 - 是,如果invokedynamic中的参数的处理方式与它们在invokevirtual和invokestatic中的处理方式相同。 使您能够简单地为invokedynamic切换invokevirtual和invokestatic。 我会假设它正在回答这个问题。

首先你要小心的是invokevirtual和invokestatic在处理堆栈时本身是不同的。 除了将invokestatic所需的参数压入堆栈之外,还需要调用虚函数,它还会推送对象引用,以便可以链接方法调用。


边注

您可能已经知道这一点,但是我添加了这些附加信息,以防其他人登陆此问题,并想知道为什么invokestatic和invokevirtual处理不同的堆栈。

  • invokestatic操作码用于调用类中的静态方法,这意味着在编译时JVM知道如何执行方法调用链接。

  • 另一方面,当对对象实例进行方法调用时,使用invokedynamic操作码。 由于在编译时无法知道将方法调用连接到哪里,因此只能在JVM知道正确的对象引用时才能在运行时进行链接。


  • 当对操作码的工作方式产生怀疑时,我的建议是检查JVM规范中关于JVM指令集的章节(链接是针对JVM 7的,当前版本是在写这篇文章时)。

    我刚刚做了检查我们在这里说的3个操作码。

    invokestatic和invokedynamic这两个操作码具有相同的操作数堆栈定义:

    ..., [arg1, [arg2 ...]] →
    
    ...
    

    正如我之前所说的,invokevirtual具有不同的操作数堆栈定义,即:

    ..., objectref, [arg1, [arg2 ...]] →
    
    ...
    

    我在这里的第一个假设(我必须警告你,我还没有深入到invokedynamic操作码中),就是你不能像invokestatic那样以简单的方式更改invokedynamic的invokevirtual。 我这样说是因为invokedynamic不期望堆栈中有任何对象引用。

    为了更好地理解这种情况,我的建议是使用java.lang.invoke包在Java中编写一个示例,它将允许您创建使用invokedynamic操作码的java字节码。 编译完类后,使用命令javap -l -c -v -p检查生成的字节码。

    问题3

    有没有例子代码(或者你能想出一些)使用Javassist创建一个invokedynamic字节码?

    不是我所知道的。 我也搜索了一下(因为你可能已经做了),我还没有找到任何东西。 我认为你的文章会给出javassist的第一个代码示例:)

    更多的笔记

    所以基本上你必须自己管理整个常量池条目。 哪一个是好的,但我担心我没有把握好,并在以后引入奇怪的问题

    只要您使用ConstPool类来管理常量池,javassist就会为您处理所有事情,而不会产生问题。

    另外,如果你创建一个损坏的容器池,最常发生的情况是(最可能总是),一旦你尝试加载类或者调用一个修改过的方法,你会遇到ClassFormatException错误。 我想说这是其中的一种情况,它可以工作与否。

    我想不出一种可能会隐藏某种奇怪错误的情景,等待这个令人讨厌的时刻在你不那么期待的时候困扰你(注意我说我无法想到,并不意味着它们没有存在)。 我甚至可以冒险说,只要你可以加载类并调用它的方法而不会导致JVM崩溃,就可以安然无恙。

    有没有更简单的方法来做到这一点(辅助方法等)?

    我不这么认为。 Javassist在字节码修改方面可以帮助你很多,但是当你使用更高级别的API时(例如,编写Java代码,注入代码或移动/复制CtMethods,Ctclasses等)。 当你使用低级别的API来处理所有的字节码时,你几乎是靠自己的。

    我知道这可能不是你正在寻找的答案,但我希望我已经阐明了这个问题。

    链接地址: http://www.djcxy.com/p/63461.html

    上一篇: Generate Invokedynamic with Javassist

    下一篇: invokedynamic and implicit methods