4

ASM でジャンプをいじって、スタック マップ フレームが Java でどのように機能するかを理解しようとしています。いくつかのことを試すための簡単な方法を作成しました: (Krakatau で逆アセンブル):

    L0:     ldc 'hello' 
    L2:     astore_1 
    L3:     getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L6:     new java/lang/StringBuilder 
    L9:     dup 
    L10:    invokespecial Method java/lang/StringBuilder <init> ()V 
    L13:    ldc 'concat1' 
    L15:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L18:    aload_1 
    L19:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L22:    invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String; 
    L25:    invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L28:    getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L31:    new java/lang/StringBuilder 
    L34:    dup 
    L35:    invokespecial Method java/lang/StringBuilder <init> ()V 
    L38:    ldc 'concat2' 
    L40:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L43:    aload_1 
    L44:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L47:    invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String; 
    L50:    invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L53:    return 

StringBuilderいくつかの文字列を変数と結合するための を作成するだけです。

L35 の invokespecial 呼び出しは、L10 の invokespecial 呼び出しとまったく同じスタックを持っているため、ICONST_1; IFEQ L10ASM を使用して L35 の直前にシーケンスを追加することにしました。

分解してみると (再び Krakatau で)、結果が非​​常に奇妙であることがわかりました。ASM は、L10 のスタック フレームを次のように計算しました。

.stack full
    locals Object [Ljava/lang/String; Object java/lang/String 
    stack Object java/io/PrintStream Top Top 
.end stack

それ以外の

    stack Object java/io/PrintStream Object java/lang/StringBuilder Object java/lang/StringBuilder

私が期待していたように。

さらに、 を呼び出すことができないため、このクラスも検証に合格しませStringBuilder#<init>Top。ASMのマニュアルによるとTop、初期化されていない値を参照していますが、ジャンプ位置と前のコードの両方から、コードで初期化されていないようです。ジャンプの何が問題なのかわかりません。

挿入したジャンプに何か問題があり、クラスがフレームを計算できなくなりますか? これはおそらく ASM の ClassWriter のバグですか?

4

2 に答える 2

7

初期化されていないインスタンスは特別です。dup参照する場合、スタック上の同じインスタンスへの参照が既に 2 つあり、さらにスタック操作を実行するか、参照をローカル変数に転送し、そこから他の変数にコピーするか、再度プッシュする可能性があることを考慮してください。それでも、参照のターゲットは、何らかの方法で使用する前に一度だけ初期化する必要があります。これを確認するには、オブジェクトの ID を追跡する必要があります。これにより、オブジェクトに対してを実行すると、同じオブジェクトへのこれらすべての参照が初期化されていない状態から初期化invokespecial <init>された状態に変わります。

Java プログラミング言語はすべての可能性を使用するわけではありませんが、 のような正当なコードの場合、分岐が行われたときに、どのインスタンスが初期化され、
new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c)))どのインスタンスが初期化されていないかを追跡する必要があります。Foo

したがって、各未初期化インスタンススタック フレーム エントリは、newそれを作成した命令に関連付けられます。すべてのエントリは、転送またはコピーされるときに参照を保持します (これは、命令のバイト コード オフセットを覚えておくのnewと同じくらい簡単に処理できます)。が呼び出された後でのみinvokespecial <init>、同じ命令を指すすべての参照newが宣言クラスの通常のインスタンスに変わり、その後、他の型互換エントリとマージできます。

これは、達成しようとしているような分岐が不可能であることを意味します。同じタイプの 2 つのUninitialized Instanceエントリは異なるnew命令で作成されているため、互換性がありません。また、互換性のない型はTop、基本的に使用できないエントリであるエントリにマージされます。Topブランチ ターゲットでそのエントリを使用しようとしない場合は、正しいコードでさえある可能性があるため、ASM は文句を言わずにそれらをマージするときに何も悪いことをしていません。

newこれは、同じ命令によって作成された複数の初期化されていないインスタンスを持つスタック フレームにつながる可能性のある、あらゆる種類のループが許可されないことも意味することに注意してください。

于 2016-12-19T16:48:18.267 に答える
0

new java/lang/StringBuilderは有効なStringBuilderオブジェクトを作成するのではなく、スタックTOPマップ フレームで提供されるユニット化されたオブジェクトを作成します。この値は、オブジェクトの構築中にジャンプ命令が追加されたときに使用されます。次に例を示します。

new Foo(a ? b : c);

これは、いくつかの goto ステートメントに変換されます。

StringBuilderコンストラクターがオブジェクトで呼び出されると、オブジェクトは最初に と見なされますinvokespecial Method java/lang/StringBuilder <init> ()V。JVM は、このオブジェクトを別の場所で初期化することをサポートしていません。検証者はTOP、必要な型の実際のシェーディングを反映していない型しか確認できないためですStringBuilder。JVM がこれをサポートするべきだと主張することもできますが、これには型と初期化状態の両方を反映するためのスタック マップ フレームを含む大きな配列が必要になり、Java 言語でも使用されていないこの能力をおそらく正当化することはできません。

これを明確にするために、次のケースを考えてみましょう。

new Foo
dup 
.stack full
    locals 
    stack Top Top 
.end stack
invokespecial Bar <init> ()V 

TOPこれは、JVM が型の未チェックの初期化を許可している場合に有効ですがBarFoo.

于 2016-12-19T07:36:22.950 に答える