簡體   English   中英

ASM 中的 ClassWriter COMPUTE_FRAMES

[英]ClassWriter COMPUTE_FRAMES in ASM

我一直試圖通過玩轉 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 L10 ICONST_1; IFEQ L10序列就在帶有 ASM 的 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

正如我所料。

此外,這個類也不會通過驗證,因為不能在Top上調用StringBuilder#<init> 根據ASM手冊, Top指的是一個未初始化的值,但在代碼中似乎沒有未初始化,無論是跳轉位置還是之前的代碼。 我不明白跳躍有什么問題。

我插入的跳轉是否有問題,以某種方式使類無法計算幀? 這可能是 ASM 的 ClassWriter 的錯誤嗎?

未初始化的實例是特殊的。 考慮到,當您dup引用時,您已經在堆棧上有兩個對同一實例的引用,您可能會執行更多堆棧操作或將引用轉移到局部變量,然后從那里將其復制到其他變量或再次推送. 盡管如此,在您以任何方式使用它之前,引用的目標應該被初始化一次。 為了驗證這一點,必須跟蹤對象的身份,以便在對同一對象執行invokespecial <init>時,所有這些對同一對象的引用將從未初始化變為已初始化

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

因此,每個未初始化的實例堆棧幀條目都與創建它的new指令相關聯。 所有條目在傳輸或復制時都保留引用(可以像記住new指令的字節碼偏移一樣輕松處理)。 只有在invokespecial <init>調用了invokespecial <init>之后,指向同一條new指令的所有引用才轉向聲明類的普通實例,並且隨后可以與其他類型兼容的條目合並。

這意味着像您試圖實現的分支是不可能的。 相同類型但由不同new指令創建的兩個未初始化實例條目是不兼容的。 並且不兼容的類型被合並到一個Top條目,這基本上是一個不可用的條目。 它甚至可能是正確的代碼,如果您不嘗試在分支目標上使用該條目,那么 ASM 在將它們合並到Top時不會做任何錯誤而不會抱怨。

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

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 

如果 JVM 允許對TOP類型進行未經檢查的初始化,這將是有效的,但顯然不應允許您在Foo上調用Bar構造函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM