我认为我正在尝试做一些相对简单的事情。以下面的Java字节码为方法doSomething(int):
public java.lang.String doSomething(int i);
0 iload_1 [i]
1 invokestatic MyHelper.doSomething(int) : Java.lang.String
4 areturn
该字节码几乎只将调用转发给静态助手。
我现在想要做的是使用Javassist将invokestatic替换为invokedynamic。我知道如何使用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进行处理一样吗?
我不知道我是否完全理解您在这里真正要问的内容(您在第一句话中反复调用了static)。我认为您要问的是-如果我错了,请纠正我-是,invokedynamic中的参数是否以与invokevirtual和invokestatic中相同的方式处理。使您能够简单地将invokevirtual和invokestatic切换为invokedynamic。我认为是在回答这个问题时...
首先要注意的是,在处理堆栈时,invokevirtual和invokestatic本身是不同的。 Invokevirtual除了将所需的参数压入堆栈之外(如invokestatic一样),还会压入对象引用,以便可以链接方法调用。
SideNote
您可能已经知道这一点,但是我添加了这些附加信息,以防其他人陷入此问题,并想知道为什么invokestatic和invokevirtual处理堆栈的方式不同。
当对操作码的工作方式有疑问时,我的建议是检查JVM instruction set中有关JVM specification的章节(链接是针对JVM 7的,当前版本是JVM 7)。
我刚刚检查了我们在这里讨论的3个操作码。
两种操作码invokestatic和invokedynamic具有相同的操作数堆栈定义:
..., [arg1, [arg2 ...]] →
...
如前所述,invokevirtual具有不同的操作数堆栈定义,即:
..., objectref, [arg1, [arg2 ...]] →
...
我在这里的第一个假设(并且我必须警告您,我还没有深入研究invokedynamic操作码)是您不能像使用invokestatic那样简单地将invokevirtual更改为invokedynamic。我之所以这样说,是因为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时,您几乎是一个人。
我知道您所寻找的可能不是一个答案,但我希望我对这个问题有所了解。