2014-09-02 81 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