Именование переменной Java ASM GeneratorAdapter

Я создаю простой класс и не могу ввести правильное имя переменной. Версия ASM: 5.2.

Вот код:

package com.test;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {

    public static void main(String[] args) throws Exception {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        String name = "com.test.Sub";
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.', '/'), null, "java/lang/Object", null);
        Method ctor = Method.getMethod("void <init>()");
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, null, null, cw);
        mg.visitCode();
        mg.loadThis();
        mg.invokeConstructor(Type.getType(Object.class), ctor);
        int var = mg.newLocal(Type.INT_TYPE);
        mg.push(42.42);
        mg.storeLocal(var);
        Label varLabel = mg.mark();
        mg.returnValue();
        Label endLabel = mg.mark();
        mg.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
        mg.endMethod();
        cw.visitEnd();
        byte[] bytes = cw.toByteArray();
        Files.write(Paths.get(name + ".class"), bytes);
    }

}

Я использую GeneratorAdapter для упрощения генерации кода. Поскольку GeneratorAdapter наследуется от LocalVariablesSorter, я предполагаю, что из него разрешено использовать метод newLocal(Type).

В испускаемом байт-коде нет ничего плохого, кроме имени переменной. Когда вызывается метод visitLocalVariable(), вместо того, чтобы присваивать имя переменной, он создает новое в байт-коде.

Выпущенный байт-код:

// class version 52.0 (52)
// access flags 0x1
public class com/test/Sub {
  // access flags 0x1
  public <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    LDC 42.42
    DSTORE 1
   L0
    RETURN
   L1
    LOCALVARIABLE x D L0 L1 3
    MAXSTACK = 2
    MAXLOCALS = 5
}

Я использую тот же индекс переменной, что и вызов newLocal() в visitLocalVariable(). Однако в сопоставленном байт-коде индексе 3 вместо 1. Если переменная имеет «более короткий» тип, такой как int, тогда индекс будет 2, а не 1, как следует.

По моим наблюдениям это происходит из-за следующего. LocalVariablesSorter поддерживает сопоставление старых индексов переменных с новыми. Он также переопределяет метод visitLocalVariable и перед делегированием вызова по цепочке посетителей вычисляет newIndex из сопоставления. newIndex вычисляется с помощью другого закрытого метода remap(). Этот метод проверяет, существует ли сопоставление для данной переменной, и если нет, то создается новое сопоставление. Проблема, как я вижу, заключается в том, что метод newLocal() ничего не добавляет к отображению.

Также я вижу из источников ASM, что storeInsn() в GeneratorAdapter делегатах visitVarInsn() вызывает цепочку вместо вызова реализации LocalVariablesSorter. Поскольку в реализации LocalVariablesSorter вызывается метод remap() для индекса переменной, и сопоставление обновляется.

Поэтому мой вопрос заключается в том, как использовать GeneratorAdapter, чтобы переменные правильно назывались в испускаемом байт-коде, или как объединить GeneratorAdapter с LocalVariablesSorter в цепочке, чтобы они правильно работали вместе?


person alllex    schedule 01.08.2017    source источник
comment
Итак, цель LocalVariablesSorter состоит в том, чтобы изменить поведение последующих вызовов visit…. Очевидно, что все операции, которые не должны изменяться, должны обходить LocalVariablesSorter, как это делают методы, представленные GeneratorAdapter.   -  person Holger    schedule 02.08.2017
comment
Тогда почему GeneratorAdapter наследуется от LocalVariablesSorter, если он не может обеспечить функциональность родителя?   -  person alllex    schedule 04.08.2017
comment
Не спрашивайте меня, я не тот, кто принял это решение. Но в чем проблема вызвать этот метод для целевого посетителя, а не для адаптера?   -  person Holger    schedule 06.08.2017
comment
Проблема в том, что никакими другими посетителями я не пользуюсь, по крайней мере явно. Я получить мой генератор прямо из ClassWriter. Если вы можете объяснить, как построить действующую цепочку посетителей метода, пожалуйста, сделайте это.   -  person alllex    schedule 07.08.2017


Ответы (1)


Поскольку GeneratorAdapter расширяет LocalVariablesSorter, целью которого является адаптация всех вызовов посетителей, все методы, являющиеся частью API посетителя, адаптируются, в отличие от специальных методов, представленных GeneratorAdapter. Этот дизайн позволяет вставлять новый код в существующий метод, при этом о старом коде сообщается через API посетителя.

Таким образом, метод visitLocalVariable, являющийся частью API посетителя, должен вызываться для цели MethodVisitor, минуя LocalVariablesSorter:

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
         name.replace('.', '/'), null, "java/lang/Object", null);
Method ctor = Method.getMethod("void <init>()");
MethodVisitor direct = cw.visitMethod(
         Opcodes.ACC_PUBLIC, ctor.getName(), ctor.getDescriptor(), null, null);
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, ctor, direct);
mg.visitCode();
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), ctor);
int var = mg.newLocal(Type.DOUBLE_TYPE);
mg.push(42.42);
mg.storeLocal(var);
Label varLabel = mg.mark();
mg.returnValue();
Label endLabel = mg.mark();
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
mg.endMethod();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);

Поскольку это может сбивать с толку, здесь альтернатива напрямую работает с целью MethodVisitor без какой-либо удобной оболочки, такой как GeneratorAdapter. Это не сложнее, хотя и требует немного больше знаний, тем не менее, это знания, которые разработчики должны иметь в любом случае, когда имеют дело с байт-кодом Java и файлами классов…

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
String name = "com.test.Sub";
String superClName = "java/lang/Object", ctorName = "<init>", ctorDesc = "()V";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name.replace('.','/'), null, superClName, null);
MethodVisitor direct = cw.visitMethod(Opcodes.ACC_PUBLIC, ctorName, ctorDesc, null, null);
direct.visitCode();
// "this" is alway 0 (zero) and for parameterless methods the next var location is 1 (one)
int thisVar = 0, var = 1;
direct.visitVarInsn(Opcodes.ALOAD, thisVar);
direct.visitMethodInsn(Opcodes.INVOKESPECIAL, superClName, ctorName, ctorDesc, false);
direct.visitLdcInsn(42.42);
Label varLabel = new Label(), endLabel = new Label();
direct.visitVarInsn(Opcodes.DSTORE, var);
direct.visitLabel(varLabel);
direct.visitInsn(Opcodes.RETURN);
direct.visitLabel(endLabel);
direct.visitLocalVariable("x", "D", null, varLabel, endLabel, var);
direct.visitMaxs(-1, -1);// no actual values, using COMPUTE_FRAMES
direct.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(name + ".class"), bytes);

Если вам неудобно использовать ()V для метода void без параметров напрямую, вы все равно можете использовать объект Method, как раньше, или Type.getMethodDescriptor(Type.VOID_TYPE)

person Holger    schedule 07.08.2017