尝试用资源引入不可达的字节码 [英] try with resources introduce unreachable bytecode

查看:162
本文介绍了尝试用资源引入不可达的字节码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

javac是否可能为以下过程生成不可达到的字节码?

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

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

  43 48 86类java / lang / Throwable 
43 48 95任何

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

现在的问题是,如果捕获类型any而不是Throwable的异常,一些代码才可以访问。有什么可以实际发生的情况吗?



====== EDIT ======
感谢您的答案。让我再给出一个证据来证明我真的不明白异常处理:
考虑以下过程:

  Object constraintsLock; 
private String [] constraints;
private String约束;
public void fp01(){
//将此约束添加到我们的Web应用程序的集合
synchronized(constraintsLock){
String results [] =
new String [constraints.length + 1]; (int i = 0; i< constraints.length; i ++)
results [i] = constraints [i];

results [constraints.length] = constraint;
constraints = results;
}
}

如果你看着你的字节码: p>

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

和异常表

 code>异常表:
从目标类型
7 62 65任何
65 69 65任何

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

解决方案

throwable是一个 java.lang.Throwable 的实例,暗示在Java字节代码/ JVM的各个位置。即使任何的处理程序意图表示可能在 Throwable 类型层次结构之外的某些东西,那么这个想法就会失败,因为今天的类文件必须有一个 StackMapTable 用于包含异常处理程序的方法,并且 StackMapTable 将引用任何 throwable作为 java.lang.Throwable 1



即使使用旧的推断验证器,一个处理程序重新抛出一个可抛出的隐含地包含这样的断言:任何 throwable是 java.lang.Throwable 的实例,因为这是唯一的对象 athrow 被允许抛出。



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


objectref 必须是引用类型,必须参考一个对象,它是一个类 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 编译)



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

 异常表:
从目标键入
17 23 26类java / lang / Throwable
6 9 44类java / lang / Throwable
6 9 49任何
58 64 67类java / lang / Throwable
44 50 49任何

现在的说明:



开始很简单,使用两个局部变量,一个用于保存 AutoCloseable (索引0),另一个用于可能的throwable(索引1,初始化与 null )。 dummy() bar()被调用,然后 AutoCloseable 被检查 null 以查看是否必须关闭。

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

如果 AutoCloseable 不是 null 并且第一个奇怪的事情发生,那个肯定是 null 的throwable被检查 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 //方法java / lang / Throwable.addSuppressed:(Ljava / lang / Throwable;)V
32:goto 86

请注意,死亡代码的最后一条指令是 goto 86 ,一个分支到 return 所以如果上面的代码没有死代码,我们可以开始想知道为什么要调用 addSuppressed Throwable 之后被忽略。



现在遵循如果变量#1执行的代码是 null (读,永远)。它简单地调用关闭并分支到 return 指令,而不是捕获任何异常,所以$ $ c抛出的异常$ c> close()传播给呼叫者:

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

现在我们输入第二个异常处理程序,覆盖 try的正文,语句,声明为catch $ 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 

所以第一件事是引入一个第三个变量来容纳同样的throwable。现在,检查 AutoCloseable null

  50:aload_0 
51:ifnull 84

检查变量#1的throwable 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 $ b $最后但并非最不重要的是,我们有一个异常处理程序处理异常抛出的关闭当一个挂起的异常存在将调用 addSuppressed 并重新抛出主要异常。它引入了另一个局部变量,表示 javac 确实从不使用交换

  67:astore 4 
69 :aload_1
70:aload 4
72:invokevirtual#6 //方法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)还有更多的死亡代码,另外还有一个奇怪的throw-and-catch(#44 - #48)代码,也可能是一个工件的想法来处理任何不同于 Throwable 。此外,一个异常处理程序的范围覆盖本身,这可能与 Sun的javac 生成的奇怪异常表项作为在评论






作为备注,Eclipse产生的只是60字节而不是87对于指令序列,只有两个预期的异常处理程序和三个局部变量,而不是5个。在这个更紧凑的代码中,它处理了可能的情况,即身体抛出的异常可能与关闭的一次抛出相同,在这种情况下不能调用addSuppressed

  0:aconst_null 
1:astore_0
2:aconst_null
3:astore_1
4:invokestatic#18 //方法dummy :()Ljava / lang / AutoCloseable;
7:astore_2
8:invokestatic#22 //方法栏:()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 //方法java / lang / Throwable.addSuppressed:(Ljava / lang / Throwable;)V
57:aload_0
58:athrow
59:return

 异常表:
从目标类型
8 11 24任何
4 37 37任何


Is it possible that javac generates unreachable bytecode for the following procedure?

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();
    }
}

When I look into the exception table of the bytecode (javap -v) there are the following entries that look strange:

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

and

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

Now the problem is that some code is only reachable if the exceptions of type "any" rather than Throwable are caught. Is there any situation where this can actually happen?

====== EDIT ====== Thanks for the answers so far. Let me give another piece of evidence to show that I really don't understand exception handling: Consider the following procedure

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;
    }   
}

If you look in the bytecode you have:

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

and the exception table

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

Does that mean that this guy can loop forever?

解决方案

The fact that every throwable is an instance of java.lang.Throwable is implied at various places of the Java byte code/ JVM. Even if handlers for any were meant to represent something possibly outside the Throwable type hierarchy, that idea fails as today’s class files must have a StackMapTable for methods containing exception handlers and that StackMapTable will refer to the any throwable as an instance of java.lang.Throwable1.

Even with the old type inferring verifier, a handler which re-throws a throwable implicitly contains the assertion that any throwable is an instance of java.lang.Throwable as that’s the only object athrow is allowed to throw.

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

The objectref must be of type reference and must refer to an object that is an instance of class Throwable or of a subclass of Throwable.

Short answer: no, it is impossible to have a situation where something other than an instance of java.lang.Throwable (or a subclass) can be thrown or caught.

I tried to create a minimal example of a try-with-resource statement to analyse the output of javac. The result clearly shows that the structure is an artifact of how javac works internally but can’t be intentional.

The example looks like this:

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

(I compiled with jdk1.8.0_20)

I put the exception handler table at the beginning of the resulting byte code so it’s easier to refer to the location while looking at the instruction sequence:

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

Now to the instructions:

The beginning is straightforward, two local variables are used, one to hold the AutoCloseable (index 0), the other for the possible throwable (index 1, initialized with null). dummy() and bar() are invoked, then the AutoCloseable is checked for null to see whether it must be closed.

     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

We get here if the AutoCloseable is not null and the first weird thing happens, the throwable which is definitely null is checked for null

    13: aload_1
    14: ifnull        35

The following code will close the AutoCloseable, guarded by the first exception handler from the table above which will invoke addSuppressed. Since at this point, variable #1 is null this is dead-code:

    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

Note that the last instruction of the dead code is goto 86, a branch to a return so if the code above was not dead code anyway, we could start wondering why bother invoking addSuppressed on a Throwable that is ignored right afterwards.

Now follows the code that is executed if variable #1 is null (read, always). It simply invokes close and branches to the return instruction, not catching any exception, so an exception thrown by close() propagates to the caller:

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

Now we enter the second exception handler, covering the body of the try statement, declared to catch Throwable, read all exceptions. It stores the Throwable into the variable #1, as expected, but also stores it into the obsolete variable #2. Then it re-throws the Throwable.

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

The following code is the target of two exception handlers. First, its the target of the superfluous any exception handler that covers the same range as the Throwable handler, hence, as you suspected, this handler doesn’t do anything. Further, it is the target of the fourth exception handler, catching anything and covering the exception handler above so we catch the re-thrown exception of instruction #48 right one instruction later. To make things even more funny, the exception handler covers more than the handler above; ending at #50, exclusive, it even covers the first instruction of itself:

    49: astore_3

So the first thing is to introduce a third variable to hold the same throwable. Now the the AutoCloseable is checked for null.

    50: aload_0
    51: ifnull        84

Now the throwable of variable #1 is checked for null. It can be null only if the hypothetical throwable not being a Throwable exists. But note that the entire code would be rejected by the verifier in that case as the StackMapTable declares all variables and operand stack entries holding the any throwable to be assignment compatible to 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

Last but not least we have the exception handler which handles exception thrown by close when a pending exception exists which will invoke addSuppressed and re-throws the primary exception. It introduces another local variables which indicates that javac indeed never uses swap even where appropriate.

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

So the following two instructions are only invoked if catch any could imply something other than java.lang.Throwable which is not the case. The code path joins at #84 with the regular case.

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

    86: return

So the bottom line is that the additional exception handler for any is responsible for dead code of four instructions only, #54, #55, #78 and #79 while there is even more dead code for other reasons (#17 - #32), plus a strange "throw-and-catch" (#44 - #48) code which might also be an artifact of the idea to handle any differently than Throwable. Further, one exception handler has a wrong range covering itself which might be related to "Strange exception table entry produced by Sun's javac" as suggested in the comments.


As a side-note, Eclipse produces more straightforward code taking only 60 bytes rather than 87 for the instruction sequence, having the two expected exception handlers only and three local variables instead of five. And within that more compact code it handles the possible case that the exception thrown by the body might be the same as the one throw by close in which case addSuppressed must not be called. The javac generated code does not care for this.

     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

这篇关于尝试用资源引入不可达的字节码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆