4

私は比較的単純なことをしようとしていると思います。メソッド doSomething(int) の次の Java バイトコードを例にとります。

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 のパラメーターと同じように処理されますよね? つまり、invokevirtual の場合と同じように、invokedynamic の前にパラメーターをスタックに配置しますか?

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

}

あなたの助けと時間をありがとう!

4

1 に答える 1

5

これは非常に興味深い質問だと言わざるを得ません。私の答えが少し長い場合は申し訳ありませんが、この質問にたどり着いたあなたや他の人を助けるために、できる限り多くの(私が思うに)役立つ情報を提供しようとしました.

質問1

次は正しいですか: javassist の CtMethod.instrument() メソッドまたは CtMethod.insertAt() メソッドを使用できません。これらのメソッドは、有効な Java 式を含む文字列を想定しており、Java 構文で invokedynamic を記述できないためです。

あなたは正しいです。

CtMethod.insertAt()は、バイトコード オペコードではなく、Java コードでのみ機能します。 一方、 CtMethod.instrument()を使用すると、バイトコードを処理したり、 ExprEditorおよびCodeConverterを使用して非常に限定的な方法で変更したりできます。しかし、私が言ったように、変更できることは非常に限られています。

質問2

invokestatic へのパラメーターは、invokevirtual または invokestatic のパラメーターと同じように処理されますよね? つまり、invokevirtual の場合と同じように、invokedynamic の前にパラメーターをスタックに配置しますか?

ここであなたが本当に求めていることを完全に理解したかどうかはわかりません(最初の文でinvokestaticを繰り返しました)。あなたが求めているのは、間違っている場合は修正してください-invokedynamicのパラメーターがinvokevirtualおよびinvokestaticと同じ方法で処理されるかどうかです。invokedynamic のinvokevirtualinvokestaticを簡単に切り替えることができるようにします。この質問に答えている間だと思います...

最初に注意しなければならないことは、スタックを処理する場合、 invokevirtualinvokestaticはそれ自体が異なるということです。Invokevirtualは、 invokestaticのように必要な引数をスタックにプッシュするだけでなく、メソッド呼び出しをリンクできるようにオブジェクト参照もプッシュします。


サイドノート

あなたはおそらくこれをすでに知っているでしょうが、他の誰かがこの質問にたどり着き、なぜinvokestaticinvokevirtualがスタックを異なる方法で処理するのか疑問に思っている場合に備えて、この追加情報を追加しています.

  • invokestaticオペコードは、クラス内の静的メソッドを呼び出すために使用されます。これは、コンパイル時に、JVM がメソッド呼び出しのリンクを行う方法を正確に認識していることを意味します。

  • 一方、invokedynamicオペコードは、オブジェクト インスタンスへのメソッド呼び出しがある場合に使用されます。コンパイル時にはメソッド呼び出しをリンクする場所を知る方法がないため、JVM が正しいオブジェクト参照を認識している場合にのみ、実行時にリンクできます。


オペコードがどのように機能するか疑問がある場合の私のアドバイスは、JVM 仕様のJVM 命令セットに関する章を確認することです (リンクは、これを書いている時点での現在のバージョンである JVM 7 用です)。

ここで話している 3 つのオペコードを確認するために、これを実行しました。

両方のopcode ( invokestaticinvokedynamic ) には、同じオペランド スタック定義があります。

..., [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 エラーが発生します。これは、機能するか機能しないかのいずれかのケースの1つだと思います。

ある種の奇妙なバグが隠されている可能性があり、その厄介な瞬間があなたをあまり期待していないときにあなたを悩ませるのを待っているシナリオは考えられません(私が考えられないと言ったことは、実際にはそうではないという意味ではありません存在)。JVM がクラッシュすることなく、クラスをロードしてそのメソッドを呼び出すことができる限り、問題はないと言っても過言ではありません。

これを行う簡単な方法 (ヘルパー メソッドなど) はありますか?

私はそうは思わない。Javassist はバイトコードの変更に大いに役立ちますが、それはより高いレベルの API を操作している場合です (Java コードを書いてそのコードを挿入したり、CtMethods、Ctclasses などを移動/コピーしたりする場合など)。すべてのバイトコードを処理しなければならない低レベル API を使用する場合、ほとんどの場合、自分で処理する必要があります。

あなたが探していたような答えではないかもしれませんが、私はこの主題に光を当てたことを願っています.

于 2013-03-17T00:06:16.793 に答える