Декомпиляция для каждого цикла - PullRequest
4 голосов
/ 13 апреля 2019

Декомпиляция файла .class следующего цикла for-each дает интересные результаты.

Источник - Main.java:

public class Main {
    public static void main(String[] args) {
        String[] names = new String[3];
        int var3 = 3;

        for (String name : names) {
            System.out.println(name);
        }
    }
}

Результат - Main.class:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        String[] names = new String[3];
        int var3 = true;
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            System.out.println(name);
        }

    }
}

Файл был декомпилирован с помощью IntelliJ IDEA.

  • Почему true был назначен неиспользованному int?
  • Почему была объявлена ​​переменная var3?

Это ошибка от имени декомпилятора?

1 Ответ

3 голосов
/ 18 апреля 2019

На уровне байт-кода нет формальных объявлений локальных переменных, по крайней мере, не так, как известно из исходного кода. У метода есть объявление максимального количества локальных переменных, существующих одновременно, или «слотов», которые нужно зарезервировать для них. Локальная переменная появляется, когда ей присваивается фактическое значение (по индексу «слота»), и она существует как минимум до последнего чтения этого значения.

С помощью этих операций невозможно определить, когда заканчивается область действия переменной или имеют ли две переменные с непересекающимися областями общий слот (по сравнению с несколькими назначениями одной и той же переменной). Хорошо, если у них есть полностью несовместимые типы, их назначения дают подсказку.

Чтобы помочь в отладке, есть необязательный атрибут кода, предоставляющий подсказки о объявленных локальных переменных и их области действия, но он не обязателен для заполнения и не влияет на способ выполнения JVM байт-кода. Но здесь, кажется, атрибут присутствовал и использовался декомпилятором.

Когда я компилирую ваш пример кода с javac -g, я получаю

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
  stack=2, locals=7, args_size=1
     0: iconst_3
     1: anewarray     #2        // class java/lang/String
     4: astore_1
     5: iconst_3
     6: istore_2
     7: aload_1
     8: astore_3
     9: aload_3
    10: arraylength
    11: istore        4
    13: iconst_0
    14: istore        5
    16: iload         5
    18: iload         4
    20: if_icmpge     43
    23: aload_3
    24: iload         5
    26: aaload
    27: astore        6
    29: getstatic     #3        // Field java/lang/System.out:Ljava/io/PrintStream;
    32: aload         6
    34: invokevirtual #4        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    37: iinc          5, 1
    40: goto          16
    43: return
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
       29       8     6  name   Ljava/lang/String;
        0      44     0  args   [Ljava/lang/String;
        5      39     1 names   [Ljava/lang/String;
        7      37     2  var3   I

Объявленные переменные args (параметр метода), names, var3 и name были назначены индексам переменных 0, 1, 2 и 6, в таком порядке.

Существуют синтетические переменные без объявления,

  • по индексу 3 для хранения ссылки на массив, который цикл повторяет
  • по индексу 4 для хранения длины массива
  • по индексу 5 для хранения индексной переменной int, которая будет увеличиваться в цикле

Кажется, у декомпилятора есть простая стратегия для работы с переменными, не содержащимися в LocalVariableTable. Он генерирует имя, состоящее из префикса "var" и индекса в кадре стека. Таким образом, он генерировал имена var3, var4 и var5 для синтетических переменных, описанных выше, и не заботился о том, что между этими сгенерированными именами и явно объявленными именами произошло столкновение имен, то есть var3.

Теперь неясно, почему декомпилятор генерирует присваивание true для переменной int, но полезно знать, что в байт-коде Java нет выделенных boolean инструкций по обработке, а скорее значений boolean обрабатываются так же, как int значения. Требуется соответствующая метаинформация, например объявления переменных, чтобы понять, когда значения должны интерпретироваться как boolean значений. Возможно, описанное выше столкновение имен привело к тому, что декомпилятор впоследствии перепутал типы переменных, чтобы в конечном итоге считать тип значения не равным int, и затем обратился к нему как к boolean. Но это всего лишь предположение; здесь также может быть совершенно не связанная ошибка.

...