【问题标题】:What can you throw in Java?你能在 Java 中抛出什么?
【发布时间】:2011-08-10 15:08:08
【问题描述】:

传统观点认为您只能在 Java 中抛出扩展 Throwable 的对象,但是否可以禁用字节码验证器并让 Java 编译和运行抛出任意对象甚至基元的代码?

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

【问题讨论】:

  • 有趣的问题 - 但你为什么希望能够抛出不可投掷的东西? (难道只是为了开bug?)
  • @RonK 主要是出于好奇;我想知道为什么 Java 强迫我们抛出比它们可能更大的占用空间的对象。注意我不打算在实际代码中使用它。
  • @RonK:有效的用例包括设计新的 java 方言或新的 JVM 语言。

标签: java exception throw


【解决方案1】:

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

字节码验证是 JVM 规范的一部分,因此如果您禁用此功能(或以其他方式篡改 JVM),您可以根据实现情况做任何我认为的事情(包括抛出原语等)。

引用自 JVM 规范:

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

也就是说,您的问题可以解释为“如果 JVM 偏离规范,它是否可以做一些奇怪的事情,例如抛出原始对象”,答案当然是肯定的。

【讨论】:

    【解决方案2】:

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

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

    section 6.1, "The Meaning of 'Must'":

    如果在运行时不满足指令描述中的某些约束(“必须”或“不得”),则 Java 虚拟机的行为未定义。

    我使用Jasmin assembler 编写了一个测试程序,它相当于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" #
    

    当然,禁用字节码验证器是危险的。编写 VM 本身以假定已执行字节码验证,因此不必对指令操作数进行类型检查。注意:绕过字节码验证时调用的未定义行为很像 C 程序中的未定义行为;任何事情都有可能发生,包括从你鼻子里飞出来的恶魔。

    【讨论】:

      【解决方案3】:

      正如约翰的回答中提到的,您可以禁用验证(将类放在 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!
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-10-24
        • 2011-11-22
        • 1970-01-01
        • 2013-02-07
        • 2013-04-12
        • 2021-06-18
        相关资源
        最近更新 更多