尝试用资源引入无法访问的字节码
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(); } }
当我查看字节码的exception表(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的exception,则只能访问某些代码。 是否有任何可能发生这种情况的情况?
======编辑======感谢目前为止的答案。 让我给出另一个证据来表明我真的不理解exception处理:考虑以下过程
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表
Exception table: from to target type 7 62 65 any 65 69 65 any
这是否意味着这个人可以永远循环?
每个throwable都是java.lang.Throwable
实例的事实隐含在Java字节代码/ JVM的不同位置。 即使任何处理程序用于表示可能在Throwable
类型层次结构之外的东西,该想法也会失败,因为今天的类文件必须具有包含exception处理程序的方法的StackMapTable
,并且StackMapTable
将引用任何 throwable作为java.lang.Throwable
的实例java.lang.Throwable
1
即使使用旧类型推断validation程序,重新抛出throwable的处理程序也隐含地包含断言,即任何 throwable都是java.lang.Throwable
的实例,因为它是唯一允许抛出的对象。
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处理程序表放在结果字节代码的开头,这样在查看指令序列时更容易引用该位置:
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()
,然后检查AutoCloseable
是否为null
以查看是否必须关闭它。
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
如果AutoCloseable
不为null
并且第一个奇怪的事情发生,我们到达这里,检查null
的throwable是否为null
13: aload_1 14: ifnull 35
以下代码将关闭AutoCloseable
, addSuppressed
的第一个exception处理程序保护,该处理程序将调用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
(read,always),则执行代码。 它只是调用close
和branch到return
指令,而不是捕获任何exception,因此close()
抛出的exception传播给调用者:
35: aload_0 36: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 41: goto 86
现在我们进入第二个exception处理程序,覆盖try
语句的主体,声明捕获Throwable
,读取所有exception。 它按预期将Throwable
存储到变量#1中,但也将它存储到过时的变量#2中。 然后它重新抛出了Throwable
。
44: astore_2 45: aload_2 46: astore_1 47: aload_2 48: athrow
以下代码是两个exception处理程序的目标。 首先,它是多余的任何exception处理程序的目标,它覆盖与Throwable
处理程序相同的范围,因此,正如您所怀疑的,此处理程序不执行任何操作。 此外,它是第四个exception处理程序的目标,捕获任何内容并覆盖上面的exception处理程序,因此我们稍后在一条指令后捕获指令#48的重新抛出的exception。 为了使事情更有趣,exception处理程序涵盖的不仅仅是上面的处理程序; 结束于#50,独家,它甚至涵盖了自己的第一条指令:
49: astore_3
所以首先要引入第三个变量来保存相同的throwable。 现在检查AutoCloseable
是否为null
。
50: aload_0 51: ifnull 84
现在检查变量#1的throwable为null
。 只有当假设的throwable不是Throwable
时,它才可以为null
。 但请注意,在这种情况下,validation程序将拒绝整个代码,因为StackMapTable
声明所有变量和操作数堆栈条目保持任何throwable与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
最后但并非最不重要的是,我们有一个exception处理程序,它处理一个挂起exception时抛出exception抛出的exception,它将调用addSuppressed
并重新抛出主exception。 它引入了另一个局部变量,表明javac
确实从未在适当的时候使用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
因此,如果catch任何可能暗示除java.lang.Throwable
其他内容,则仅调用以下两条指令。 代码路径在#84处与常规案例连接。
78: aload_0 79: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V 84: aload_3 85: athrow 86: return
所以最重要的是, 任何一个额外的exception处理程序只负责四个指令的死代码,#54,#55,#78和#79,而其他原因还有更多的死代码(#17 – #32)加上一个奇怪的“抛出 – 捕获”(#44 – #48)代码,它也可能是一个想法的工件,可以处理与Throwable
不同的任何东西。 此外,一个exception处理程序具有覆盖自身的错误范围,这可能与评论中建议 的 “ 由Sun的javac生成的奇怪exception表条目 ”相关。
作为旁注,Eclipse生成更简单的代码,仅占用60个字节而不是87个指令序列,只有两个预期的exception处理程序和三个局部变量而不是五个。 在更紧凑的代码中,它处理可能的情况,即正文抛出的exception可能与close
抛出的exception相同,在这种情况下不得调用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