gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 14:01:42 24 4
gpt4 key购买 nike

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

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

最佳答案

TL; DR :已通过JDK-11解决;答案的最后是一个JDK-11的javac输出示例,用于比较。

Java字节码/JVM的各个位置都暗含了每个throwable都是java.lang.Throwable实例的事实。即使用于任何处理程序的意图是表示可能超出Throwable类型层次结构的某种东西,该想法也会失败,因为当今的类文件必须为包含异常处理程序的方法添加一个StackMapTable,并且StackMapTable会将任何throw引用为java.lang.Throwable 1的实例。

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

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.



简短的回答:不,不可能出现除 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(),然后检查 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是否为 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的分支,因此,如果上面的代码无论如何都不是无效代码,我们可能会开始怀疑为什么要在随后会被忽略的 addSuppressed上调用 Throwable

现在遵循如果变量#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

因此,第一件事就是引入第三个变量来保持相同的throwable。现在,在 AutoCloseable中检查 null
    50: aload_0
51: ifnull 84

现在检查变量#1的throwable的 null。仅当假设的可抛出对象不是 null时,它才可以是 Throwable。但是请注意,在这种情况下,整个代码将被验证程序拒绝,因为 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并重新引发主要异常。它引入了另一个局部变量,即使在适当的地方也指示 javac indeed 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

因此,以下两条指令仅在catch any可能暗示 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个指令序列,只有两个预期的异常处理程序和三个局部变量而不是五个。在更紧凑的代码中,它处理了可能的情况,即主体引发的异常可能与 close引发的异常相同,在这种情况下,不得调用 addSuppressedjavac生成的代码与此无关。
     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

从JDK-11开始, javac将示例编译为
Code:
0: invokestatic #2 // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: invokestatic #3 // Method bar:()V
7: aload_0
8: ifnull 42
11: aload_0
12: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
17: goto 42
20: astore_1
21: aload_0
22: ifnull 40
25: aload_0
26: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
31: goto 40
34: astore_2
35: aload_1
36: aload_2
37: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
40: aload_1
41: athrow
42: return
Exception table:
from to target type
4 7 20 Class java/lang/Throwable
25 31 34 Class java/lang/Throwable

现在,它的冗余度甚至比ECJ编译版本还小。它仍然不会检查可抛出对象是否相同,但是我宁愿添加另一个覆盖 addSuppressed调用指令并以 40为目标的重新抛出代码的异常处理程序条目,而不是为此情况插入预检查项。然后,它的代码仍将少于替代方法。

关于java - 尝试使用资源引入无法访问的字节码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25615417/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com