2014-09-02 85 views
14

是否有可能javac爲以下過程生成無法訪問的字節碼?嘗試使用資源引入無法訪問的字節碼

public void ex06(String name) throws Exception { 
    File config = new File(name); 
    try (FileOutputStream fos = new FileOutputStream(config); 
      PrintWriter writer = new PrintWriter(new OutputStreamWriter(
        fos , "rw"))) { 
     bar(); 
    } 
} 

當我看着字節碼(javap的-v)有以下條目看起來很奇怪的異常表:

43 48 86 Class java/lang/Throwable 
43 48 95 any 

21 135 170 Class java/lang/Throwable 
21 135 179 any 

現在的問題是如果捕獲類型爲「any」而非Throwable的異常,則只有某些代碼可以訪問。有沒有可能發生這種情況的情況?

======編輯====== 感謝迄今爲止的答案。我再舉一個證據表明,我真不明白,異常處理: 考慮以下程序

Object constraintsLock; 
private String[] constraints; 
private String constraint; 
public void fp01() { 
    // Add this constraint to the set for our web application 
    synchronized (constraintsLock) { 
     String results[] = 
      new String[constraints.length + 1]; 
     for (int i = 0; i < constraints.length; i++) 
      results[i] = constraints[i];    
     results[constraints.length] = constraint; 
     constraints = results; 
    } 
} 

如果在字節碼看你有:

65: astore  4 
    67: aload_1  
    68: monitorexit 
    69: aload   4 

和異常表

Exception table: 
    from to target type 
     7 62 65 any 
     65 69 65 any 

這是否意味着這傢伙可以永遠循環?

+0

請張貼完整的字節碼。 – 2014-09-02 03:33:52

+0

https://dl.dropboxusercontent.com/u/26793257/example.txt – 2014-09-02 04:13:31

+0

這裏是jimple代碼(由soot生成): https://dl.dropboxusercontent.com/u/26793257/example。 jimple.txt 我在那裏寫了兩條評論來指出相關的行。 – 2014-09-02 04:19:03

回答

15

事實上,每個throwable是java.lang.Throwable的一個實例隱含在Java字節代碼/ JVM的各個位置。即使任何處理器註定要可能代表Throwable類型層次之外的東西,這種想法在今天的類文件必須包含異常處理方法StackMapTableStackMapTable將參考Throwable作爲實例的任何失敗java.lang.Throwable1

即使舊類型推理驗證,該處理程序重新拋出一個拋出隱含包含任何拋出是java.lang.Throwable實例因爲這是被允許拋出的唯一對象athrow斷言。

http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow

objectref必須reference類型,並且必須引用的對象,則Throwable類或的Throwable一個子類的實例。

簡短的回答:不,這是不可能有地方比java.lang.Throwable(或子類)的實例其他的東西可以引發或捕獲的情況。

我試圖創建一個try-with-resource語句的最小示例來分析javac的輸出。結果清楚地表明,該結構是javac如何在內部工作的人造物,但不能是故意的。

的例子是這樣的:

public static void tryWithAuto() throws Exception { 
    try (AutoCloseable c=dummy()) { 
     bar(); 
    } 
} 
private static AutoCloseable dummy() { 
    return null; 
} 
private static void bar() { 
} 

(我jdk1.8.0_20編譯)

我把異常處理程序表在生成的字節代碼的開頭,以便它更容易參照位置,同時看指令序列:

Exception table: 
    from to target type 
    17 23 26 Class java/lang/Throwable 
     6  9 44 Class java/lang/Throwable 
     6  9 49 any 
    58 64 67 Class java/lang/Throwable 
    44 50 49 any 

我們指示:

開始很簡單,使用兩個局部變量,一個用於保存AutoCloseable(索引0),另一個用於可能的throwable(索引1,用null初始化)。調用dummy()bar(),然後檢查AutoCloseablenull以查看它是否必須關閉。

 0: invokestatic #2   // Method dummy:()Ljava/lang/AutoCloseable; 
    3: astore_0 
    4: aconst_null 
    5: astore_1 
    6: invokestatic #3   // Method bar:()V 
    9: aload_0 
    10: ifnull  86 

我們拿到這裏如果AutoCloseablenull和第一奇怪的事情發生時,拋出這絕對是null被檢查null

13: aload_1 
    14: ifnull  35 

下面的代碼將關閉AutoCloseable,由看守從上面的表中的第一個異常處理程序將調用addSuppressed。由於在這一點上,變#1 null這是死代碼:

17: aload_0 
    18: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    23: goto   86 
    26: astore_2 
    27: aload_1 
    28: aload_2 
    29: invokevirtual #6   // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V 
    32: goto   86 

注意,死代碼的最後一個指令是goto 86,跳轉至return因此,如果上面的代碼中並沒有死的代碼嗎,我們可能會開始想知道爲什麼在Throwable上調用addSuppressed之後會被忽略。

現在遵循如果變量#1是null(總是閱讀)執行的代碼。它只是調用close並分支到return指令,不獲取任何的異常,所以通過close()拋出的異常傳播給調用者:

35: aload_0 
    36: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    41: goto   86 

現在我們進入第二個異常處理程序,覆蓋了try語句體,宣佈趕上Throwable,閱讀所有例外。如預期的那樣,它將Throwable存儲到變量#1中,但也將其存儲到廢棄變量#2中。然後它重新拋出Throwable

44: astore_2 
    45: aload_2 
    46: astore_1 
    47: aload_2 
    48: athrow 

以下代碼是的異常處理程序的目標。首先,它的目標是多餘的任何異常處理程序,其覆蓋範圍與Throwable處理程序相同,因此,如您所懷疑的,此處理程序不會執行任何操作。此外,它是第四個異常處理程序的目標,捕獲任何東西並覆蓋上面的異常處理程序,因此我們在稍後的一條指令中捕獲指令#48的重新拋出的異常。爲了讓事情更有趣,異常處理程序不僅覆蓋了上面的處理程序,在#50結束,排斥,甚至涵蓋了自身的第一個指令:

49: astore_3 

所以,第一件事就是要引入第三個變量來持有相同拋出。現在檢查AutoCloseablenull

50: aload_0 
    51: ifnull  84 

現在變量#1的拋出被檢查null。只有在假設投擲項不存在Throwable的情況下,它纔可以是null。但要注意,整個代碼會在這種情況下,驗證者拒絕爲StackMapTable聲明所有變量和持有任何拋出操作數堆棧條目是分配兼容java.lang.Throwable

54: aload_1 
    55: ifnull  78 
    58: aload_0 
    59: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    64: goto   84 

最後但並非最不重要的,我們有異常處理程序,它處理異常時存在的將會調用addSuppressed並重新拋出主要異常的異常時由關閉拋出的異常。它引入了另一個局部變量,即使在適當的情況下也表示javacindeed never uses swap

67: astore  4 
    69: aload_1 
    70: aload   4 
    72: invokevirtual #6   // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V 
    75: goto   84 

因此,如果捕捉任何可能意味着比其他java.lang.Throwable東西是不是這種情況下兩個指令只調用。代碼路徑在#84處與常規情況結合。

78: aload_0 
    79: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    84: aload_3 
    85: athrow 

    86: return 

因此,底線是,任何額外的異常處理程序負責的只有四個指令,#54,#55,#78和#79死代碼,而甚至有更多的死碼其他原因(#17 - #32),再加上一個奇怪的「拋出並捕獲」(#44 - #48)代碼,這也可能是任何不同於Throwable的想法的人工產物。此外,一個異常處理程序的覆蓋範圍錯誤,可能與「Strange exception table entry produced by Sun's javac」爲suggested in the comments有關。


作爲邊注,Eclipse將產生更直接的代碼只服用60字節,而不是87的指令序列中,具有兩個預期的異常處理程序只和三個局部變量,而不是5。在更緊湊的代碼中,它處理了可能的情況,即由主體引發的異常可能與close所拋出的異常相同,在這種情況下addSuppressed一定不能被調用。 javac生成的代碼不關心這一點。

 0: aconst_null 
    1: astore_0 
    2: aconst_null 
    3: astore_1 
    4: invokestatic #18  // Method dummy:()Ljava/lang/AutoCloseable; 
    7: astore_2 
    8: invokestatic #22  // Method bar:()V 
    11: aload_2 
    12: ifnull  59 
    15: aload_2 
    16: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    21: goto   59 
    24: astore_0 
    25: aload_2 
    26: ifnull  35 
    29: aload_2 
    30: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 
    35: aload_0 
    36: athrow 
    37: astore_1 
    38: aload_0 
    39: ifnonnull  47 
    42: aload_1 
    43: astore_0 
    44: goto   57 
    47: aload_0 
    48: aload_1 
    49: if_acmpeq  57 
    52: aload_0 
    53: aload_1 
    54: invokevirtual #30  // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V 
    57: aload_0 
    58: athrow 
    59: return 

Exception table: 
    from to target type 
     8 11 24 any 
     4 37 37 any 
+1

偉大的分析。上週我試圖回答這個問題時,我得出了或多或少的相同結論。這似乎是一個〜錯誤〜。無論如何,+1和全部。謝謝! – 2014-09-11 14:37:52