Преобразование Dalvik с использованием неправильного кода операции вызова

У меня возникла проблема с конвертером dalvik dex и кодом операции, который он использует для вызова методов. По сути, у меня есть метод private final, определенный в моем классе, и при его вызове вместо генерации кода операции invoke-direct dx генерирует invoke-super. Поскольку это закрытый метод, метод не существует в суперклассе, поэтому я получаю нарушение VFY на устройстве. Я смог отследить точный сценарий, который вызывает это, и, похоже, это происходит, когда:

  1. инструментирование классов с помощью JaCoCo, и
  2. классы, скомпилированные с помощью --target 1.6

Если эти два условия соблюдены, результирующий класс dex имеет invoke-super вместо invoke-direct. Если я отключу JaCoCo ИЛИ если я скомпилирую с --target 1.5, он использует правильный код операции invoke-direct.

Глядя на дизассемблированный код класса javap, я вижу, что заставляет dx принимать super вместо direct:

Не инструментировано, скомпилировано для версии 1.6:

$ javap -d com.example.ClassName | grep waitForConnectivity
159:    invokespecial   #115; //Method waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
147ad8: 7010 6042 0200           |001e: invoke-direct {v2}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4260

Инструментально, скомпилировано для версии 1.5 (--target 1.5):

$ javap -d com.example.ClassName | grep waitForConnectivity
235:    invokespecial   #115; //Method waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
149d4c: 7010 9242 0400           |0018: invoke-direct {v4}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4292

Инструментально, скомпилировано для версии 1.6:

$ javap -d com.example.ClassName | grep waitForConnectivity
235:    invokespecial   #115; //Method com/example/ClassName.waitForConnectivity:()V
$ dexdump -d classes.dex | grep waitForConnectivity
149d4c: 6f10 9242 0400           |0018: invoke-super {v4}, Lcom/example/ClassName;.waitForConnectivity:()V // method@4292

Таким образом, разница заключается в том, что скомпилированный файл .class содержит скомпилированный байт-код Java, который ссылается на полное имя класса класса this (обратите внимание на "//Method waitForConnectivity:()V" и "//Method com/example/ClassName.waitForConnectivity:()V"). Похоже, что dx автоматически предполагает, что если имя метода полностью определено, оно должно использовать invoke-super, но если оно не уточнено, оно использует invoke-direct.

Мои вопросы:

  1. Это ошибка в Android dx или ошибка в JaCoCo?
  2. Как этого избежать, чтобы классы с инструментами JaCoCo могли правильно работать в моих автоматизированных тестовых сборках?

Мой текущий обходной путь состоит в том, чтобы иметь профиль Maven «jacoco», и там я переопределяю свойство ${java.version}, чтобы изменить его со значения по умолчанию «1.6» на «1.5». Есть ли лучшее решение?


person Joe    schedule 11.07.2013    source источник


Ответы (1)


Одно из правил, которое dx использует для определения того, следует ли выдавать invoke-super или invoke-direct, заключается в том, считает ли он, что вызов метода выполняется в том же классе, что и тот, который выполняет вызов. См. RopperMachine.java в источнике, около строки 912, включенной сюда для справки:

        case ByteOps.INVOKESPECIAL: {
            /*
             * Determine whether the opcode should be
             * INVOKE_DIRECT or INVOKE_SUPER. See vmspec-2 section 6
             * on "invokespecial" as well as section 4.8.2 (7th
             * bullet point) for the gory details.
             */
            CstMethodRef ref = (CstMethodRef) cst;
            if (ref.isInstanceInit() ||
                (ref.getDefiningClass() == method.getDefiningClass()) ||
                !method.getAccSuper()) {
                return RegOps.INVOKE_DIRECT;
            }
            return RegOps.INVOKE_SUPER;

Было бы интересно увидеть более полный дамп неправильно преобразованного класса. Я думаю, что, вероятно, дело в том, что то, что вы видите из javap, не совсем полная картина реальности. Обратите внимание, что dx имеет встроенный дампер файлов .class, который предоставляет гораздо больше деталей, чем javap. Вызовите его как dx --dump --bytes path/to/Name.class.

person danfuzz    schedule 19.07.2013
comment
Таким образом, это будет означать, что ref.getDefiningClass() != method.getDefiningClass(). Сегодня постараюсь сделать для вас более полный дамп. Но проблема, по-видимому, легко воспроизводима с помощью автономного инструментария JaCoCo и компилятора Java 1.6. Интересно, что это не всегда ломалось так, поэтому вы, вероятно, правы, что происходит что-то еще. Я просто не знаю, когда это началось. - person Joe; 19.07.2013
comment
Я назначаю награду, потому что вы выполнили заслуживающую доверия часть требования о награде. Хотя это еще не решено или не отслеживается до причины/причины, на данный момент кажется, что виновником, скорее всего, является инструментарий JaCoCo, хотя я чувствую, что dx все еще должен быть в состоянии распознать, что определяющий класс для вызванного метода равен в определяющий класс вызывающей стороны, что приводит к INVOKE_DIRECT, как и ожидалось. Потребуется дальнейшее расследование, как было предложено. Возможно, мы сможем продолжить это в чате позже, когда я найду время вернуться к этому. - person Joe; 22.07.2013
comment
Спасибо! Как я уже сказал, dx --dump, скорее всего, поможет определить точную природу различий. Я не собираюсь утверждать, что dx безупречен (даже если гордость заставляет меня хотеть этого), но да, JaCoCo почти наверняка делает что-то как минимум подозрительное. - person danfuzz; 22.07.2013
comment
Вы случайно не знаете, где найти vmspec-2, упомянутый в комментарии к коду? Спасибо! - person Jiahao Cai; 04.07.2020