使用Javassist生成Invokedynamic [英] Generate Invokedynamic with Javassist

查看:108
本文介绍了使用Javassist生成Invokedynamic的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为我正在尝试做相对简单的事情.以下面的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()允许您使用 JVM规范(链接是针对JVM 7的,当前版本是JVM 7).

我刚刚检查了我们在这里谈论的3个操作码.

两个操作码,调用静态 invokedynamic ,具有相同的操作数堆栈定义:

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

...

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

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

...

我在这里的第一个假设(并且我必须警告您,我还没有深入研究 invokedynamic 操作码)是您无法更改 invokevirtual 动态调用,就像您使用静态调用一样简单.我之所以这样说是因为 invokedynamic 不会在堆栈中引用任何对象.

我对更好地理解这种情况的建议是使用 ConstPool 类来管理您的常量池,javassist会为您处理所有事情,而不会造成问题.

此外,如果您创建了一个损坏的内容池,则通常(很可能总是)发生的情况是,一旦尝试加载该类或调用一个已修改的方法,就会出现ClassFormatException错误.我要说的是,这是行不通的情况之一.

我想不出一种情况,当您不那么期望时,可能会隐藏某种奇怪的错误,等待那令人讨厌的时刻困扰着您(注意,我说过我想不到的提示,并不意味着他们不存在).我什至会冒风险地说,只要您可以加载该类并调用其方法而不会导致JVM崩溃,您就可以了.

是否有更简单的方法(一种辅助方法)?

我不这么认为. Javassist在字节码修改方面为您提供了很多帮助,但是当您使用更高级别的API时(例如,编写Java代码并注入该代码或移动/复制CtMethods,Ctclasses等).当您使用必须处理所有字节码的低级API时,您几乎是一个人.

我知道您所寻找的可能不是一个答案,但我希望我对这个问题有所了解.

I am trying to do something relatively simple, I think. Take for example the following Java bytecode for a method doSomething(int):

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

This bytecode pretty much only forwards the call to a static helper.

What I want to do now is to replace the invokestatic with invokedynamic using Javassist. I know how to do this with ASM therefore just assume that I want to know how it works for reasons of pure curiosity. Here are some questions I have:

1) Is the following correct: I cannot ruse the javassist CtMethod.instrument() or CtMethod.insertAt() methods because those methods expect a string containing a valid Java expression and I cannot write an invokedynamic in Java syntax?

2) The parameters to an invokestatic are handled just like the parameters of an invokevirtual or invokestatic, right? What I mean is, you put the parameters onto the stack before the invokedynamic just like you would do it for a invokevirtual?

3) Is there example code (or could you come up with some) that uses Javassist to create an invokedynamic bytecode?

This is what I know so far: You can create a Bytecode object which has a method addInvokedynamic(). But this expects the index of a BootstrapMethod in a BootstrapMethodsAttribute. The BootstrapMethod in turn expects an index of a method handle info in the constant pool which wants a method reference and so on. So essentially you have to manage the whole constant pool entries yourself. Which is OK but I am concerned that I don't get it right and introduce weird issues later. Is there an easier way to do this (a helper method or so)? The code I have roughly looks something like this (I don't really "rewrite" the above invokestatic but :

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

}

Thanks a lot for your help and time!

解决方案

I must say that this is quite an interesting question you have here. I'm sorry if my answer is a bit long but I've tried to give you as much (what I think is) useful information as I could, in order to try to help you and others that land in this question.

Question 1

Is the following correct: I cannot ruse the javassist CtMethod.instrument() or CtMethod.insertAt() methods because those methods expect a string containing a valid Java expression and I cannot write an invokedynamic in Java syntax?

You are correct.

CtMethod.insertAt() can only works with Java code not bytecode opcodes. CtMethod.instrument(), on the other hand, allows you to handle bytecode and even modify it in a very limited way with ExprEditor and CodeConverter. But as I said, it's very limited what they allow you to change, and for what you are trying to achieve both modifiers cannot help you.

Question 2

The parameters to an invokestatic are handled just like the parameters of an invokevirtual or invokestatic, right? What I mean is, you put the parameters onto the stack before the invokedynamic just like you would do it for a invokevirtual?

I don't know if I fully understood what you really are asking here (you repeated invokestatic in your 1st sentence). I think what you are asking - and correct me if I'm wrong - is, if parameters in invokedynamic are handled the same way as they are in invokevirtual and invokestatic. Making you able to simply switch invokevirtual and invokestatic for an invokedynamic. I'll assume it is while answering this question...

First thing you have to be careful is that invokevirtual and invokestatic are themselves different when handling the stack. Invokevirtual besides pushing into the stack the arguments needed, as invokestatic does, it also pushes the object reference so the method call can be linked.


SideNote

You probably already know this, but I'm adding this additional information just in case someone else lands in this question and is wondering why invokestatic and invokevirtual handle differently the stack.

  • invokestatic opcode is used to invoke static methods in a class, this means that at compile time the JVM knows exactly how to do the method call linking.

  • On the other hand, invokedynamic opcode is used when there's a method call to an object instance. Since when compiling there's no way to know where to link the method call to, it can only be linked at runtime when the JVM knows the correct object reference.


My advice when having doubts in how the opcodes works is to check the chapter regarding JVM instruction set in the JVM specification (links are for JVM 7, the current version when writing this).

I've just done that to check the 3 opcodes we're talking here.

Both opcodes, invokestatic and invokedynamic, have the same operand stack definition:

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

...

As I had said previously invokevirtual has a different operand stack definition, which is:

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

...

My first assumption here (and I must warn you I didn't yet dive that much into the invokedynamic opcode) is that you can't change the invokevirtual for an invokedynamic in such a simple way as you do it with invokestatic. I'm saying this because invokedynamic is not expecting any object reference in the stack.

My advice for reaching a better understanding of this case would be to code an example in Java, using the java.lang.invoke package, which will allow you to create java bytecode that uses invokedynamic opcode. And after compiling the classes, inspecting the generated bytecode using the command javap -l -c -v -p.

Question 3

Is there example code (or could you come up with some) that uses Javassist to create an invokedynamic bytecode?

Not that I'm aware of. I've also googled a bit (as you probably already did too) and I haven't found anything. I think you're post will give the first code example for javassist :)

Some more notes

So essentially you have to manage the whole constant pool entries yourself. Which is OK but I am concerned that I don't get it right and introduce weird issues later

As long as you use the ConstPool class to manage your constant pool, javassist will handle everything for you without creating problems.

Besides, if you create a corrupted contant pool, most often (most probably always) what will happen is that you'll have a ClassFormatException error as soon as you try to load the class or invoke a modified method. I would say this is one of those cases where it either works or not.

I can't think of a scenario where some sort of weird bug could be hidden waiting for that nasty moment to haunt you when you less expect (notice that I said I can't think of, doesn't really mean they don't exist). I would even risk to say that it's fairly safe to say that as long as you can load the class and call it's methods without having the JVM crashing you'll be ok.

Is there an easier way to do this (a helper method or so)?

I don't think so. Javassist helps you a lot in bytecode modification but it's when you're working with the higher level API (as in, writing java code and injecting that code or moving/copying CtMethods,Ctclasses, ect). When you use the low level API where you have to handle all the bytecode, you're pretty much on your own.

I know it's probably not a spot on answer as you were looking for, but I hope I've shed some light over the subject.

这篇关于使用Javassist生成Invokedynamic的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆