Generate Invokedynamic with Javassist

邮差的信 提交于 2019-12-10 10:24:32

问题


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!


回答1:


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.



来源:https://stackoverflow.com/questions/15409377/generate-invokedynamic-with-javassist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!