Что вы можете бросить в Java?

Традиционная мудрость говорит, что вы можете бросать только объекты, которые расширяют Throwable в Java, но можно ли отключить верификатор байт-кода и заставить Java компилировать и запускать код, который создает произвольные объекты - или даже примитивы?

Я просмотрел JVM athrow, и он поместит первый objref в стек операнда; но проверит ли он, что эта ссылка указывает на Throwable во время выполнения?

Ответы

Ответ 1

Это зависит от вашей реализации JVM. Согласно спецификации Java VM это поведение undefined, если объект не является Throwable.

Объектref должен иметь ссылку на тип и должен ссылаться на объект, являющийся экземпляром класса Throwable или подкласса Throwable.

В раздел 6.1, "Значение" Обязательно":

Если какое-либо ограничение ( "обязательно" или "не должно" ) в описании команды не выполняется во время выполнения, поведение виртуальной машины Java составляет undefined.

Я написал тестовую программу, используя ассемблер Jasmin, который делает эквивалент throw new Object(). Виртуальная машина Java HotSpot 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" #

Конечно, отключить верификатор байт-кода опасно. Собственно ВМ написано, чтобы предположить, что проверка байт-кода была выполнена и, следовательно, не имеет операндов инструкции typecheck. Остерегайтесь: поведение undefined, которое вы вызываете при обходе проверки байт-кода, очень похоже на поведение undefined в программах на C; что-то вообще может произойти, включая демонов, вылетающих из вашего носа.

Ответ 2

Как упоминалось в ответе Джона, вы можете отключить проверку (добавление класса в 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!

Ответ 3

[...] отключить верификатор байт-кода [...]

Проверка байт-кода является частью спецификации JVM, поэтому, если вы отключите ее (или перепутаете с JVM другими способами), вы можете, в зависимости от реализации, сделать что угодно (включая метательные примитивы и т.д.), я бы предположил.

Цитата из спецификации JVM:

Объектref должен иметь ссылку на тип, а должен ссылаться на объект, являющийся экземпляром класса Throwable или подкласса Throwable.

I.e., ваш вопрос может быть истолкован как "Если JVM отклоняется от спецификации, может ли он делать странные вещи, такие как бросание примитивов", и ответ, конечно, да.