Как преобразовать байт-коды для инициализации примитивных констант в блоке stati c с ASM? - PullRequest
0 голосов
/ 12 апреля 2020

Мне нужно преобразовать Java байт-код с ASM, чтобы инициализировать public static final поля внутри блока static {...} в классе. Например:

Входные данные:

public static final int CONSTANT = 10;

Выходные данные:

public static final int CONSTANT;

static {
    CONSTANT = 10;
}

Мне нужно это преобразование, потому что компилятор заменяет примитивные константы их фактическим значением в байт-коде, поэтому их использование становится недоступным для отслеживания. Это преобразование позволяет отследить использование констант.

1 Ответ

3 голосов
/ 14 апреля 2020

Для такого преобразования вы можете использовать обычную цепочку ClassReaderClassVisitor (трансформатор) → ClassWriter. Существует три основных шага:

  • Переопределить visitField для отслеживания всех полей с постоянным значением и вызова метода супер-посещения без константы, то есть с null, чтобы сохранить поле объявление, но удалите постоянное значение.

  • Переопределите visitMethod, чтобы заметить, существует ли уже существующий инициализатор класса (метод <clinit>). Если это так, верните специальный MethodVisitor, который будет вводить инициализацию поля в начале кода, и очистите карту, чтобы третий шаг стал недоступным.

  • Override visitEnd создать инициализатор класса, если были постоянные поля и не существовал инициализатор класса. Вновь созданный инициализатор класса должен выполнять те же назначения полей, поэтому стоит иметь общий код в методе injectFieldInit. Затем нам нужно только добавить обязательную инструкцию RETURN, которую нам не нужно добавлять для уже существующего инициализатора.

Этот код использует массив в качестве ключа карты, который здесь нет проблем, поскольку каждое поле отличается, поэтому тот факт, что массивы не имеют основанного на контенте метода equals, не имеет значения. Вместо этого мы могли бы использовать List<Map.Entry<…>> или список выделенных типов элементов для хранения всех необходимых значений, с тем же результатом, что и код не выполняет поиск, а только выполняет итерацию по обнаруженным полям один раз.

public static byte[] transform(byte[] classFile) {
    ClassReader cr = new ClassReader(classFile);
    ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
    ClassVisitor trans = new ClassVisitor(Opcodes.ASM5, cw) {
        private String currClassName;
        private Map<String[],Object> constants = new HashMap<>();
        @Override public void visit(int version, int acc, String name,
                                    String sig, String superName, String[] ifs) {
            currClassName = name;
            super.visit(version, acc, name, sig, superName, ifs);
        }
        @Override public FieldVisitor visitField(int acc, String name, String desc,
                                                 String sig, Object value) {
            if(value != null && (acc & Opcodes.ACC_STATIC) != 0)
                constants.put(new String[]{currClassName, name, desc}, value);
            return super.visitField(acc, name, desc, sig, null);
        }
        @Override public MethodVisitor visitMethod(int acc, String name, String desc,
                                                   String sig, String[] ex) {
            MethodVisitor mv = super.visitMethod(acc, name, desc, sig, ex);
            if(name.equals("<clinit>")) {
                mv = new MethodVisitor(Opcodes.ASM5, mv) {
                    @Override public void visitCode() {
                        super.visitCode();
                        injectFieldInit(this, constants);
                        constants.clear();
                    }
                };
            }
            return mv;
        }
        @Override public void visitEnd() {
            if(!constants.isEmpty()) {
                MethodVisitor mv = super.visitMethod(
                    Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
                mv.visitCode();
                injectFieldInit(mv, constants);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitMaxs(-1, -1);
                mv.visitEnd();
            }
            super.visitEnd();
        }
    };
    cr.accept(trans, 0);
    return cw.toByteArray();
}
static void injectFieldInit(MethodVisitor target, Map<String[], Object> constants) {
    for(Map.Entry<String[],Object> e: constants.entrySet()) {
        target.visitLdcInsn(e.getValue());
        String[] field = e.getKey();
        target.visitFieldInsn(Opcodes.PUTSTATIC, field[0], field[1], field[2]);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...