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

Поэтому я пытаюсь внести небольшие допустимые юридические изменения в скомпилированную Java-программу. Я декомпилирую его с помощью JD-GUI для Mac. По большей части декомпилированный код не содержит ошибок, но есть некоторые странные вещи, такие как необъявленные переменные, множественные идентичные объявления переменных и просто некоторые странные операторы, которые трудно скомпилировать. Некоторые странные утверждения в декомпилированном коде действительно озадачивают. В частности, у меня возникли проблемы с одним оператором switch:

    switch ($SWITCH_TABLE$PackageName$ClassName$InnerEnumName()[getPlatform().ordinal()])

Где PackageName.ClassName — это класс, в котором находится этот оператор, а InnerEnumName — это внутреннее перечисление внутри ClassName. Также обратите внимание, что getPlatform() — это метод в ClassName, который возвращает перечисление типа InnerEnumName.

Странная часть заключается в том, что когда я просто очистил этот класс от проблемных операторов, скомпилировал его и вставил обратно в программу, он начал работать, но имел несколько странных ошибок. Например, когда я изменил оператор switch на

    switch (getPlatform().ordinal())

он начал попадать в случай 3 (третий случай и случай для значения 3), когда он должен попасть в случай 4 (еще раз четвертый случай, а также случай для значения 4)


person Sam Bryant    schedule 03.08.2011    source источник
comment
Ответ Даниэля имеет смысл. Я не знал, что внутренние классы не поддерживаются JVM.   -  person Sam Bryant    schedule 04.08.2011
comment
Также я обнаружил, что в целом декомпиляция JD-GUI была довольно приличной. Я предполагаю, что все ошибки, которые он сделал, были по определенной причине, подобной этой, которая больше связана с моим пониманием того, как работает JVM и скомпилированный байт-код, а не с самим декомпилятором.   -  person Sam Bryant    schedule 04.08.2011


Ответы (3)


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

В случае имен $ это имена, сгенерированные внутри в процессе «подделки» внутренних классов (поскольку JVM на самом деле не поддерживает внутренние классы). Декомпилятор, по-видимому, выполняет несовершенную работу по выяснению того, что такое внутренние классы, и соответствующим образом именует их и объекты, созданные компилятором для подделки. Кто-то, знакомый с форматом байт-кода, вероятно, мог бы разобраться довольно быстро, но, как и все остальное, это нетривиально.

(В этом конкретном случае кажется, что компилятор по какой-то причине создал таблицу сопоставления из внутренних значений перечисления в некоторые другие значения, и когда вы «разделили» оператор, вы потеряли это сопоставление.)

[Я добавлю, что одна большая проблема, с которой сталкиваются декомпиляторы, заключается в том, что javac является такой движущейся целью. В частности, такие вещи, как реализация внутреннего класса, постоянно корректируются, поэтому то, что работало в одну неделю, может дать сбой на следующей, со следующей версией компилятора +.001.]

person Hot Licks    schedule 03.08.2011
comment
ну это бы объяснило. Прежде чем я прочитал это, я просто вручную переназначил операторы case, и, похоже, это сработало, но решение, созданное наугад, заставило меня нервничать, потому что я действительно не понимал, почему. Теперь я вижу, что мое решение действительно - person Sam Bryant; 04.08.2011
comment
Помните, что внутренний и внешний классы являются (почти) полностью независимыми сущностями класса в байт-коде, поэтому внешний класс не имеет блокировки, скажем, порядка значений перечисления, определенного во внутреннем классе. Итак, если внешний класс собирается выполнить switch (для чего требуются буквальные значения case), то он должен выполнить своего рода полудинамическое отображение. - person Hot Licks; 04.08.2011

Похоже, у JD-GUI (JD?) есть проблемы. Попробуйте найти лучший декомпилятор? Жаль, Джад древний - раньше был хороший.

person Mike    schedule 03.08.2011

Рискуя воскресить древний вопрос - за счет удаления косвенного массива по порядковому номеру значение исходного переключателя изменяется.

Я написал это здесь: http://www.benf.org/other/cfr/switch-on-enum.html

Выдающийся бит:

Первой функцией enum -> integer, которая приходит на ум, является .ordinal(). Однако с этим есть пара проблем:

Класс перечисления, который мы включаем, не является фиксированным — мы не можем взять копию целевых порядковых номеров для операторов case — кто-то может изменить определение перечисления! Кто-то может даже удалить поле, которое мы используем в качестве метки обращения.

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

Следовательно, массив, который вы вырезали, - это карта времени выполнения между порядковыми номерами в выражении enum и местоположением в вашем выражении switch.

Что действительно интересно, так это то, что это означает, что Javac создает дополнительный внутренний класс для каждого включения-перечисления — Fun!

person lab27    schedule 27.09.2013