在Java中你可以抛出什么?

62

传统智慧认为在Java中只能抛出扩展了Throwable的对象,但是是否可能禁用字节码验证器并使Java编译和运行代码抛出任意对象 - 甚至原始类型?

我查看了JVM的athrow,它将弹出操作数栈上的第一个objref;但是它会在运行时检查所述引用是否指向Throwable吗?


4
有趣的问题,但为什么你想要能够投掷一个本不可投掷的物品?(是为了开发漏洞吗?) - RonK
@RonK 这主要是出于好奇心;我想知道Java强制我们抛出比其占用空间更大的对象的原因。请注意,我并不打算在真正的代码中使用此功能。 - NullUserException
@RonK:有效的用例包括设计新的Java方言或新的JVM语言。 - charlie
3个回答

74

这取决于您的JVM实现。根据Java VM规范,如果对象不是Throwable,则未定义行为。

必须使用引用类型的 objectref 参考Throwable类或其子类的对象。

第6.1节,“'Must'的含义”中:

如果某个指令描述中的某个约束(“Must”或“must not”)在运行时未满足,则Java虚拟机的行为未定义。

我使用 Jasmin汇编器编写了一个测试程序,相当于throw new Object()。 Java HotSpot Server VM会抛出VerifyError

# cat Athrow.j 
.source Athrow.j
.class public Athrow
.super java/lang/Object

.method public <init>()V
    aload_0
    invokenonvirtual java/lang/Object/<init>()V
    return
.end method

.method public static main([Ljava/lang/String;)V
    .limit stack 2

    new java/lang/Object
    dup
    invokenonvirtual java/lang/Object/<init>()V
    athrow

    return
.end method

# java -jar jasmin.jar Athrow.j 
Generated: Athrow.class

# java Athrow
Exception in thread "main" java.lang.VerifyError: (class: Athrow, method: main signature: ([Ljava/lang/String;)V) Can only throw Throwable objects

禁用字节码验证器会导致athrow执行,并且当JVM尝试打印异常的详细信息时,它似乎会崩溃。比较这两个程序,第一个抛出一个Exception,第二个是上面抛出一个Object的测试程序。请注意它在打印过程中退出:

# java -Xverify:none examples/Uncaught
Exception in thread "main" java.lang.Exception
        at examples.Uncaught.main(Uncaught.j)
# java -Xverify:none Athrow
Exception in thread "main" #

当然,禁用字节码验证是危险的。虚拟机本身被写成假定已经执行了字节码验证,因此不必检查指令操作数的类型。请注意:当您规避字节码验证时所引发的未定义行为非常类似于C程序中的未定义行为; 任何事情都可能发生,包括恶魔从你的鼻子里飞出来。


8

正如John的回答中所提到的,您可以禁用验证(将类放在bootclasspath上也应该可以),成功加载和执行抛出非Throwable类的类。

令人惊讶的是,这并不一定会导致崩溃!

只要您不隐式或显式地调用Throwable方法,一切都会完美地运行:

.source ThrowObject.j
.class public ThrowObject
.super java/lang/Object

.method public <init>()V
    aload_0
    invokenonvirtual java/lang/Object/<init>()V
    return
.end method

.method public static main([Ljava/lang/String;)V

    new java/lang/Object
    dup
    invokenonvirtual java/lang/Object/<init>()V

  BeforeThrow:
    athrow

  AfterThrow:

    return

  CatchThrow:

    getstatic java/lang/System/out Ljava/io/PrintStream;
    ldc "Thrown and catched Object successfully!"
    invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V
    return

  .catch all from BeforeThrow to AfterThrow using CatchThrow
.end method

结果:

% java -Xverify:none ThrowObject
Thrown and catched Object successfully!

5

[...] 禁用字节码验证器 [...]

字节码验证是JVM规范的一部分,因此如果您禁用它(或以其他方式篡改JVM),则可以根据实现情况执行几乎任何操作(包括抛出原始类型等)。 我会假设。

JVM规范的引用:

objectref 必须是引用类型,并且必须引用Throwable类的实例或Throwable子类的对象

也就是说,您的问题可以解释为“如果JVM偏离规范,是否可以执行奇怪的操作,例如抛出基元类型”,答案当然是可以的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接