Chr*_*oph 4 java code-generation invokedynamic javassist
我正在尝试做一些相对简单的事情,我想。以 doSomething(int) 方法的以下 Java 字节码为例:
public java.lang.String doSomething(int i);
0 iload_1 [i]
1 invokestatic MyHelper.doSomething(int) : Java.lang.String
4 areturn
Run Code Online (Sandbox Code Playgroud)
这个字节码几乎只将调用转发给静态助手。
我现在想要做的是使用 Javassist 将 invokestatic 替换为 invokedynamic。我知道如何使用 ASM 做到这一点,因此只是出于纯粹的好奇心假设我想知道它是如何工作的。以下是我的一些问题:
1) 以下是否正确:我不能使用 javassist CtMethod.instrument() 或 CtMethod.insertAt() 方法,因为这些方法需要包含有效 Java 表达式的字符串,而我不能用 Java 语法编写调用动态?
2) invokestatic 的参数就像 invokevirtual 或 invokestatic 的参数一样处理,对吗?我的意思是,您在调用动态之前将参数放入堆栈,就像对 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());
}
Run Code Online (Sandbox Code Playgroud)
非常感谢您的帮助和时间!
我必须说,这是一个非常有趣的问题。如果我的回答有点长,我很抱歉,但我已尽力为您提供尽可能多(我认为是)有用的信息,以帮助您和其他遇到此问题的人。
以下是否正确:我无法使用 javassist CtMethod.instrument() 或 CtMethod.insertAt() 方法,因为这些方法需要包含有效 Java 表达式的字符串,而我无法用 Java 语法编写调用动态?
你是对的。
CtMethod.insertAt()只能用于 Java 代码,不能用于字节码操作码。 另一方面,CtMethod.instrument()允许您处理字节码,甚至可以使用ExprEditor和CodeConverter以非常有限的方式修改它。但是正如我所说,它们允许您更改的内容非常有限,并且对于您尝试实现的内容,这两个修饰符都无济于事。
invokestatic 的参数就像 invokevirtual 或 invokestatic 的参数一样处理,对吗?我的意思是,您在调用动态之前将参数放入堆栈,就像对 invokevirtual 所做的一样?
我不知道我是否完全理解你在这里真正要问的是什么(你在第一句话中重复了 invokestatic)。我想你问什么-纠正我,如果我错了-是,如果参数invokedynamic是相同的方式处理,因为他们在invokevirtual和invokestatic。使你能够简单地切换invokevirtual和invokestatic一个invokedynamic。我会假设它是在回答这个问题时......
你必须要小心,第一件事是,invokevirtual和invokestatic本身不同的处理栈时。Invokevirtual除了像invokestatic一样将所需的参数推入堆栈之外,它还推入对象引用,以便可以链接方法调用。
边注
您可能已经知道这一点,但我在这个问题上,以防别人的土地刚刚添加该附加信息,并想知道为什么invokestatic和invokevirtual手柄不同的堆栈。
invokestatic操作码用于调用类中的静态方法,这意味着在编译时 JVM 确切地知道如何进行方法调用链接。
另一方面,当有对对象实例的方法调用时,会使用invokedynamic操作码。由于在编译时无法知道将方法调用链接到何处,因此只有在 JVM 知道正确的对象引用时才能在运行时链接它。
我有此疑惑,当在操作码是如何工作的建议是检查有关章节JVM指令集的JVM规范(链接是为JVM 7,写这在当前的版本)。
我刚刚这样做是为了检查我们在这里谈论的 3 个操作码。
两个操作码invokestatic和invokedynamic具有相同的操作数堆栈定义:
..., [arg1, [arg2 ...]] ?
...
Run Code Online (Sandbox Code Playgroud)
正如我之前所说的invokevirtual有一个不同的操作数堆栈定义,它是:
..., objectref, [arg1, [arg2 ...]] ?
...
Run Code Online (Sandbox Code Playgroud)
我的第一个假设这里(我必须警告你,我还不潜水那么多到invokedynamic操作码)是你无法改变invokevirtual为invokedynamic为你做它在这样一个简单的方法invokestatic。我这么说是因为invokedynamic不期望堆栈中的任何对象引用。
为了更好地理解这种情况,我的建议是用 Java 编写一个示例,使用java.lang.invoke包,这将允许您创建使用调用动态操作码的Java 字节码。在编译类之后,使用命令检查生成的字节码javap -l -c -v -p。
是否有使用 Javassist 创建调用动态字节码的示例代码(或者你能想出一些)吗?
不是我所知道的。我也用谷歌搜索了一下(你可能也已经这样做了),但我什么也没找到。我认为您的帖子将为 javassist 提供第一个代码示例:)
所以基本上你必须自己管理整个常量池条目。没关系,但我担心我做对了,以后会引入奇怪的问题
只要您使用ConstPool类来管理您的常量池,javassist 就会为您处理一切而不会产生问题。
此外,如果您创建了一个损坏的常量池,通常(最有可能总是)会发生的是,一旦您尝试加载类或调用修改后的方法,就会出现 ClassFormatException 错误。我会说这是它要么有效要么无效的情况之一。
我想不出这样的场景,当你不那么期待时,可能会隐藏某种奇怪的错误,等待那个讨厌的时刻困扰着你(注意我说我想不到,并不意味着他们没有存在)。我什至敢说,只要您可以加载类并调用它的方法而不会让 JVM 崩溃,就可以说是相当安全的。
有没有更简单的方法来做到这一点(辅助方法左右)?
我不这么认为。Javassist 在字节码修改方面对您有很大帮助,但是当您使用更高级别的 API 时(例如,编写 Java 代码并注入该代码或移动/复制 CtMethods、Ctclasses 等)。当您使用必须处理所有字节码的低级 API 时,您几乎是靠自己的。
我知道这可能不是您正在寻找的答案,但我希望我已经对这个主题有所了解。
| 归档时间: |
|
| 查看次数: |
1133 次 |
| 最近记录: |