尝试用资源引入无法访问的字节码

Mar*_*häf 17 java jvm exception-handling javac try-catch

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我查看字节码的异常表(javap -v)时,有以下条目看起来很奇怪:

43    48    86   Class java/lang/Throwable
43    48    95   any
Run Code Online (Sandbox Code Playgroud)

21   135   170   Class java/lang/Throwable
21   135   179   any
Run Code Online (Sandbox Code Playgroud)

现在的问题是,如果捕获了类型为"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;
    }   
}
Run Code Online (Sandbox Code Playgroud)

如果你查看字节码:

    65: astore        4
    67: aload_1       
    68: monitorexit   
    69: aload         4
Run Code Online (Sandbox Code Playgroud)

和异常表

  Exception table:
     from    to  target type
         7    62    65   any
        65    69    65   any
Run Code Online (Sandbox Code Playgroud)

这是否意味着这个人可以永远循环?

Hol*_*ger 22

每个throwable都是一个实例的事实javac隐含在Java字节代码/ JVM的不同位置.即使任何处理程序用于表示可能在java.lang.Throwable类型层次结构之外的东西,该想法也会失败,因为今天的类文件必须具有Throwable包含异常处理程序的for方法,并且StackMapTable任何 throwable称为StackMapTable1的实例.

即使使用旧类型推断验证程序,重新抛出throwable的处理程序也隐含地包含断言,即任何 throwable都是一个实例,java.lang.Throwable因为它是唯一java.lang.Throwable允许抛出的对象.

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

所述objectref类型必须的athrow,并且必须是指一个对象,它是类的一个实例reference或子类的Throwable.

简短的回答:不,不可能出现一种情况,即Throwable可以抛出或捕获除(或子类)实例之外的其他东西.

我试图创建一个try-with-resource语句的最小示例来分析输出java.lang.Throwable.结果清楚地表明,该结构是javac内部工作但不能有意识的工件.

示例如下所示:

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

(我编译javac)

我将异常处理程序表放在结果字节代码的开头,这样在查看指令序列时更容易引用该位置:

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
Run Code Online (Sandbox Code Playgroud)

现在来说明:

开头是直截了当的,使用了两个局部变量,一个用于保存jdk1.8.0_20(索引0),另一个用于可能的throwable(索引1,初始化为AutoCloseable).null并且dummy()被调用,然后bar()检查AutoCloseable是否必须关闭它.

     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
Run Code Online (Sandbox Code Playgroud)

我们到达这里如果null不是,AutoCloseable并且第一个奇怪的事情发生了,肯定null会检查throwablenull

    13: aload_1
    14: ifnull        35
Run Code Online (Sandbox Code Playgroud)

下面的代码将关闭null,由上面的表中的第一个异常处理程序保护,它将调用AutoCloseable.从那时起,变量#1 addSuppressed就是死代码:

    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
Run Code Online (Sandbox Code Playgroud)

请注意,死代码的最后一条指令是null一个分支,goto 86所以如果上面的代码不是死代码,我们可以开始想知道为什么麻烦调用return一个addSuppressed被忽略的东西.

现在,如果变量#1是Throwable(read,always),则执行代码.它只是调用null并分支到close指令,而不是捕获任何异常,因此return传播给调用者抛出异常:

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

现在我们进入第二个异常处理程序,覆盖close()语句的主体,声明要捕获try,读取所有异常.它Throwable按预期存储到变量#1中,但也将其存储到过时的变量#2中.然后它重新抛出Throwable.

    44: astore_2
    45: aload_2
    46: astore_1
    47: aload_2
    48: athrow
Run Code Online (Sandbox Code Playgroud)

以下代码是两个异常处理程序的目标.首先,它是多余的任何异常处理程序的目标,它覆盖与处理程序相同的范围Throwable,因此,正如您所怀疑的,此处理程序不执行任何操作.此外,它是第四个异常处理程序的目标,捕获任何内容并覆盖上面的异常处理程序,因此我们稍后在一条指令后捕获指令#48的重新抛出的异常.为了使事情更有趣,异常处理程序涵盖的不仅仅是上面的处理程序; 结束于#50,独家,它甚至涵盖了自己的第一条指令:

    49: astore_3
Run Code Online (Sandbox Code Playgroud)

所以第一件事是引入第三个变量来保持相同的throwable.现在Throwable检查了AutoCloseable.

    50: aload_0
    51: ifnull        84
Run Code Online (Sandbox Code Playgroud)

现在检查变量#1的throwable null.它可以是null只有不是假设抛出一个null存在.但请注意,在这种情况下,验证程序将拒绝整个代码,因为Throwable声明所有变量和操作数堆栈条目保持任何throwable与赋值兼容StackMapTable

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

最后但并非最不重要的是,我们有一个异常处理程序,当存在一个挂起的异常时,它会处理由close抛出的异常,该异常将调用java.lang.Throwable并重新抛出主异常.它引入了另一个局部变量,表明即使在适当的地方也addSuppressed 确实没有使用javac.

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

因此,只有在捕获任何可能暗示除了swap不是这种情况之外的其他内容时,才会调用以下两条指令.代码路径在#84处与常规案例连接.

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

    86: return
Run Code Online (Sandbox Code Playgroud)

因此,底线是,对于附加的异常处理程序的任何负责的仅四个指令,#54,#55,#78和#79的死代码而有更死其他原因代码(#17 - #32) ,加上一个奇怪的"抛出 - 捕获"(#44 - #48)代码,这可能也是一个想法的工件,可以处理任何不同的java.lang.Throwable.此外,一个异常处理程序具有覆盖自身的错误范围,这可能与评论中建议 " 由Sun的javac生成的奇怪异常表条目 "相关.


作为旁注,Eclipse生成更简单的代码,只需60个字节而不是87个指令序列,只有两个预期的异常处理程序和三个局部变量而不是五个.在更紧凑的代码中,它处理可能的情况,即正文抛出的异常可能与抛出的异常相同,Throwable在这种情况下close不得调用.将addSuppressed生成的代码不关心这个.

     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
Run Code Online (Sandbox Code Playgroud)

 

  Exception table:
     from    to  target type
         8    11    24   any
         4    37    37   any
Run Code Online (Sandbox Code Playgroud)