Декомпилятор Java 8 / Fernflower: ошибка или функция

OpenJDK 1.8.0_191

Я скомпилировал и декомпилировал фрагмент кода ниже, используя Fernflower.

public class Decompile {
    public static void main(String[] args) {
        final int VAL = 20;
        System.out.println(VAL);
    }
}

Результат:

public class Decompile {

   public static void main(String[] args) {
      boolean VAL = true;
      System.out.println(20);
   }
}

Я в замешательстве, как VAL стало логическим значением?

Обновлено:

В Intellij IDEA декомпилированный код выглядит так:

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

public class Decompile {
    public Decompile() {
    }

    public static void main(String[] args) {
        int VAL = true;
        System.out.println(20);
    }
}

Пробовали ли вы другие декомпиляторы для сравнения?

scrutari 08.03.2019 20:09

@scrutari да, JDCore, CFR и Procyon показывают VAL как int

voismager 08.03.2019 20:10

VAL — это константа, которая встраивается, поэтому, хотя может быть запись о существовании переменной, она, вероятно, не знает, как определить, что это было.

Peter Lawrey 08.03.2019 20:11

P.S. если вам нравится работать на уровне байт-кода, стоит обратить внимание на ASM, так как он является основой для многих других библиотек для работы с байт-кодом. asm.ow2.io/asm4-guide.pdf

LppEdd 08.03.2019 20:36
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
1 055
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это работает так, как compiler сделать некоторые optimization во время генерации byte code. Поскольку ВАЛ = 20; является окончательным и неизменяемым, поэтому он может поставить 20 вместо VAL без impacting функциональности во втором утверждении. Теперь у decompiler есть только byte code, и когда он начинает читать байтовый код, он находит 20 как встроенный в second line. Байт-код, сгенерированный кодом, как показано ниже:

   0: bipush        20
   2: istore_1
   3: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
   6: bipush        20
   8: invokevirtual #26                 // Method java/io/PrintStream.println:(I)V
Ответ принят как подходящий

Байт-код

L0
 LINENUMBER 5 L0
 BIPUSH 20
 ISTORE 1
L1
 LINENUMBER 6 L1
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 BIPUSH 20
 INVOKEVIRTUAL java/io/PrintStream.println (I)V

Как видите, BIPUSH помещает 20 в стек, затем ISTORE принимает значение и сохраняет его в локальной переменной.

Это Fernflower проблема.


Для вашего интереса вывод для версии байт-кода 55

int VAL = true;
System.out.println(20);

Вы можете видеть, что декомпиляторы могут ошибаться :)

Основная проблема заключается в том, что байт-код Java не имеет понятия логических значений, байтов, символов или шорт (кроме сигнатур типов). Вместо этого все локальные переменные с этими типами компилируются в целые числа. Логические значения true и false компилируются в 1 и 0 соответственно.

Это означает, что декомпилятор должен угадать, должна ли данная локальная переменная быть логического или целочисленного типа. В этом случае значение 20 хранится в переменной, которая никогда не будет храниться в переменной логического типа в Java-коде, поэтому декомпилятору должно быть легко догадаться, что это целочисленный тип, исходя из контекста. Но похоже, что логический угадчик Фернфлауэра не такой сложный.

Как бы то ни было, это по своей сути трудная проблема. Особенно, если учесть, что байт-код, отличный от Java, не должен следовать тем же шаблонам, что и Java. Для байт-кода вполне допустимо использовать одну и ту же переменную как в целочисленном, так и в логическом контексте. Декомпилятор Кракатау имеет довольно сложный шаг вывода для определения того, должны ли переменные быть логическими или нет, но в подобных ситуациях он все равно будет ошибаться.

Другие вопросы по теме