3

我一直在試圖理解通過與ASM跳躍玩耍地圖幀的Java堆棧是如何工作的。我創建了一個簡單的方法來嘗試一些東西出來:(與喀拉喀託拆解):ClassWriter COMPUTE_FRAMES在ASM

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調用具有完全相同的堆棧,因此我決定在L35之前的ASM中添加一個ICONST_1; IFEQ L10序列。

當我dissassembled(與喀拉喀託再次),我發現結果很奇怪。 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 

如我的預期。

此外,這個類也將無法通過驗證爲一個不能Top打電話StringBuilder#<init>。根據ASM手冊,Top引用了一個未初始化的值,但它似乎並未在代碼中初始化,包括跳轉位置和之前的代碼。我不明白跳躍有什麼問題。

是不是有什麼毛病我插入,不知怎的,使該類不可能計算幀的跳躍?這可能是ASM的ClassWriter的一個bug嗎?

回答

2

未初始化的情況是特殊的。考慮一下,當你參考時,你已經有兩次對棧上同一個實例的引用,你可能會執行更多的棧操作或將引用轉移到局部變量,然後從那裏複製到其他變量或再次推入。儘管如此,在你以任何方式使用它之前,參考的目標應該被初始化一次。爲了驗證這一點,必須跟蹤對象的身份,以便對其執行invokespecial <init>時,對同一對象的這些引用將從未初始化變爲初始化

Java編程語言不使用所有的可能性,但對於像
new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c)))法人代碼,它不應該了,履帶哪些Foo實例已初始化,並且沒有,當分支而成。

所以每個未初始化實例堆棧幀條目綁定到創建它的new指令。所有條目在傳輸或複製時都保留引用(可以像處理remembering the byte code offset of the new instruction一樣簡單)。只有在調用invokespecial <init>之後,所有指向同一new指令的引用纔會轉向聲明類的普通實例,並可隨後與其他類型兼容條目合併。

這意味着一個分支,就像你試圖實現的一樣,是不可能的。兩個未初始化實例相同類型的條目,但由不同的new指令創建的條目不兼容。不兼容的類型合併爲Top條目,這基本上是不可用的條目。如果您不嘗試在分支目標上使用該條目,那麼它甚至可能是正確的代碼,因此ASM在沒有抱怨的情況下將它們合併到Top時沒有出錯。

注意,這也意味着任何可能導致具有相同new指令創建了多個未初始化的實例堆棧幀的循環方式,是不允許的。

+0

謝謝,每個未初始化的值綁定到創建它的NEW語句似乎是這裏的關鍵。 – konsolas

0

new java/lang/StringBuilder不會創建有效的StringBuilder,而是在堆棧映射框架中使用TOP捐贈的單元化對象。例如:在構造對象期間添加跳轉指令時使用該值,例如:

new Foo(a ? b : c); 

它被翻譯成幾個goto語句。

當在對象上調用構造函數時,該對象首先被視爲StringBuilder,即invokespecial Method java/lang/StringBuilder <init>()V。在JVM不支持在不同的位置作爲覈實initalizing此對象只能看TOP類型不反映所希望的類型的實際遮陽其是未初始化StringBuilder。你可能會爭辯說JVM應該支持這個,但是這將需要更大的數組來包含堆棧映射幀,以反映類型和初始化狀態,這可能不會證明這種甚至沒有被Java語言使用的能力。

要明確這一點,請考慮以下情況:

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

如果JVM允許未經檢查的初始化上TOP類型,但你顯然不應該被允許調用一個Foo一個Bar構造這將是有效的。

+0

看來你自己也困惑不解。如果未初始化的對象總是由'Top'類型表示,而這在合併點處不可用,那麼您自己的示例「new Foo(a?b:c)」應該如何工作? – Holger

+0

在這種情況下,驗證者應用堆棧映射框架之外的檢查,但如果在「new」指令和構造函數調用之間存在跳轉目標,則驗證是不可能的。這種額外的驗證也在方法調用中完成,例如, 'Foo foo = this; foo.protectedMethod()'不能在方法的包之外工作。 –

+0

當你有像'new Foo(a?b:c);'這樣的代碼時,''new'指令和構造函數調用之間有一個分支目標。在字節碼級別上,甚至可以在中間有循環。 – Holger

相關問題