Подтвердить что ты не робот

Попробуйте с ресурсами ввести недостижимый байт-код

Возможно ли, что 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. Есть ли ситуация, когда это может произойти?

====== EDIT ====== Спасибо за ответы. Позвольте мне дать еще одну улику, чтобы показать, что я действительно не понимаю обработку исключений: Рассмотрим следующую процедуру.

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

Означает ли это, что этот парень может зацикливаться навсегда?

4b9b3361

Ответ 1

Тот факт, что каждый throwable является экземпляром java.lang.Throwable, подразумевается в разных местах байт-кода Java/JVM. Даже если обработчики для любого были предназначены для представления чего-то, возможно, вне иерархии типов Throwable, эта идея не удалась, поскольку сегодняшние файлы классов должны иметь StackMapTable для методов, содержащих обработчики исключений, и что StackMapTable будет ссылаться на любой, экземпляр java.lang.Throwable 1.

Даже с помощью верификатора старого типа, обработчик, который повторно бросает броски, неявно содержит утверждение о том, что любой throwable является экземпляром java.lang.Throwable, так как единственный объект athrow разрешен.

http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow

Объектref должен иметь тип 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 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, объявленного catch 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

Итак, первое, что нужно сделать, - ввести третью переменную, чтобы удерживать ту же брошюру. Теперь AutoCloseable проверяется на null.

    50: aload_0
    51: ifnull        84

Теперь значение переменной # 1 проверяется на 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 никогда не использует 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

Таким образом, нижняя строка заключается в том, что дополнительный обработчик исключений для любого отвечает за мертвый код только из четырех команд: # 54, # 55, # 78 и # 79, в то время как по другим причинам существует еще более мертвый код (# 17 - # 32), плюс странный код "throw-and-catch" (# 44 - # 48), который также может быть артефактом идеи обрабатывать иначе, чем Throwable. Кроме того, один обработчик исключений имеет неправильный диапазон, охватывающий сам, который может быть связан с "записью Strange exception table, созданной javac от Sun" как в комментариях.


В качестве побочной заметки Eclipse создает более простой код, занимающий только 60 байт, а не 87 для последовательности команд, имеющий только два ожидающих обработчика исключений и три локальные переменные вместо пяти. И в этом более компактном коде он обрабатывает возможный случай, что исключение, созданное телом, может быть таким же, как и один бросок close, и в этом случае 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