尝试用资源引入不可达的字节码 [英] try with resources introduce unreachable bytecode
问题描述
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.Throwable
1.
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屋!