Получение количества локальных переменных в методе

Итак, у меня есть несколько классов, в которые были вставлены "фиктивные вызовы методов"; т.е. статические методы в выделенном классе, которые имеют пустое тело.

Идея состоит в том, чтобы взять аргументы, которые были помещены в стек до вызова метода, сохранить их в локальных переменных, а затем заменить вызов метода фактической реализацией.

Чтобы увидеть, как обрабатываются местные жители, я бегу

A.java

package asmvisit;

public class A {
    long y;

    public long doSomething(int x, A a){
        if(a == null){
            this.y = (long)x;
            return -1L;
        }
        else{
            long old = y;
            this.y += (long)x;
            return old;
        }
    }
}

через текстификатор (код внизу поста).

Как вы можете видеть в выводе (также внизу поста), локальные переменные

    LOCALVARIABLE old J L4 L6 3
    LOCALVARIABLE this Lasmvisit/A; L0 L6 0
    LOCALVARIABLE x I L0 L6 1
    LOCALVARIABLE a Lasmvisit/A; L0 L6 2

посетить в самом конце метода.

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

Так что, как я понимаю, единственный безопасный способ добавить больше локальных переменных - это дважды запускать каждый метод:

  • однажды абсолютно ничего не делая, кроме подсчета количества посещений локальных переменных
  • после фактического изменения кода, отслеживания "сгенерированных" местных жителей, но с задержкой фактической генерации (то есть посещения локальных) до момента visitMaxsиспользование счетчика для отслеживания индексов, которые получат новые местные жители.

Есть ли более простая альтернатива, которая не требует двух проходов?

textifier

package asmvisit;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;

import java.io.PrintWriter;
import java.util.Arrays;

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println(String.format("\nvisitMethod: %d, %s, %s, %s, %s", access,name,desc,signature, Arrays.toString(exceptions)));

        Printer p = new Textifier(api) {
            @Override
            public void visitMethodEnd() {
                PrintWriter pw = new PrintWriter(System.out);
                print(pw); // print it after it has been visited
                pw.flush();
            }
        };

        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if(mv != null){
            return new TraceMethodVisitor(mv,p);
        }

        return mv;
    }
}

выход

visitMethod: 1, <init>, ()V, null, null
L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
L1
    LOCALVARIABLE this Lasmvisit/A; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

visitMethod: 1, doSomething, (ILasmvisit/A;)J, null, null
L0
    LINENUMBER 7 L0
    ALOAD 2
    IFNONNULL L1
L2
    LINENUMBER 8 L2
    ALOAD 0
    ILOAD 1
    I2L
    PUTFIELD asmvisit/A.y : J
L3
    LINENUMBER 9 L3
    LDC -1
    LRETURN
L1
    LINENUMBER 12 L1
FRAME SAME
    ALOAD 0
    GETFIELD asmvisit/A.y : J
    LSTORE 3
L4
    LINENUMBER 13 L4
    ALOAD 0
    DUP
    GETFIELD asmvisit/A.y : J
    ILOAD 1
    I2L
    LADD
    PUTFIELD asmvisit/A.y : J
L5
    LINENUMBER 14 L5
    LLOAD 3
    LRETURN
L6
    LOCALVARIABLE old J L4 L6 3
    LOCALVARIABLE this Lasmvisit/A; L0 L6 0
    LOCALVARIABLE x I L0 L6 1
    LOCALVARIABLE a Lasmvisit/A; L0 L6 2
    MAXSTACK = 5
    MAXLOCALS = 5

1 ответ

Решение

Локальные переменные, как сообщается visitLocalVariableявляются только отладочной информацией, хранящейся в LocalVariableTable атрибут и LocalVariableTypeTable атрибут Если эти атрибуты отсутствуют, о таких объявлениях не сообщается.

Кроме того, они не обязаны быть полными относительно переменных уровня байт-кода, то есть они не сообщают о второй переменной, занятой long а также double ценности. Они также могут не включать синтетические переменные, такие как введенные конструкцией for-each (с удерживанием скрытого итератора), конструкцией try-with-resource (с отложенными исключениями) или ожидающими значениями, как в
try { return expression; } finally { otherAction(); } строит.

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

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

Получить индексы, которые использовались до определенного момента, не так сложно, если вы можете жить только с поддержкой более новых файлов классов, имеющих StackMapTable атрибуты. Для этих классов вы должны заботиться только о двух мероприятиях. На отраслевые цели, visitFrame сообщит, какие переменные используются в данный момент. Использование этой информации проще при указании EXPAND_FRAMES к ClassReader, Другое событие, о котором следует позаботиться, - это действительные инструкции по использованию переменных (на самом деле, имеют значение только хранилища), которые сообщаются visitVarInsn, Сложив это, эскиз выглядит так

classReader.accept(new ClassVisitor(Opcodes.ASM5) {
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return new MyMethodVisitor(access, desc);
    }
}, ClassReader.EXPAND_FRAMES);
class MyMethodVisitor extends MethodVisitor {
    private int used, usedAfterInjection;

    public MyMethodVisitor(int acc, String signature) {
        super(Opcodes.ASM5);
        used = Type.getArgumentsAndReturnSizes(signature)>>2;
        if((acc&Opcodes.ACC_STATIC)!=0) used--; // no this
    }

    @Override
    public void visitFrame(
            int type, int nLocal, Object[] local, int nStack, Object[] stack) {
        if(type != Opcodes.F_NEW)
            throw new IllegalStateException("only expanded frames supported");
        int l = nLocal;
        for(int ix = 0; ix < nLocal; ix++)
            if(local[ix]==Opcodes.LONG || local[ix]==Opcodes.DOUBLE) l++;
        if(l > used) used = l;
        super.visitFrame(type, nLocal, local, nStack, stack);
    }

    @Override
    public void visitVarInsn(int opcode, int var) {
        int newMax = var+(opcode==Opcodes.LSTORE || opcode==Opcodes.DSTORE? 2: 1);
        if(newMax > used) used = newMax;
        super.visitVarInsn(opcode, var);
    }

    @Override
    public void visitMethodInsn(
            int opcode, String owner, String name, String desc, boolean itf) {
        if(!shouldReplace(owner, name, desc)) {
            super.visitMethodInsn(opcode, owner, name, desc, itf);
        }
        else {
            int numVars = (Type.getArgumentsAndReturnSizes(desc)>>2)-1;
            usedAfterInjection = used+numVars;
            /*
              use local vars between [used, usedAfterInjection]
            */
        }
    }
    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack, Math.max(used, usedAfterInjection));
    }
}

На что следует обратить внимание, это то, что при хранении long или же double значения в переменную, переменная в index + 1 должны также рассматриваться как используемые. Напротив, в рамках атрибута таблицы карты стека эти long а также double отображаются как отдельные записи, поэтому мы должны искать их и соответственно увеличивать количество используемых переменных.

Отслеживая used переменные, мы можем просто использовать переменные за пределами этого числа в visitMethodInsnКак сказано, просто сохраняя значения в этих индексах без необходимости сообщать их через visitLocalVariable, Также нет необходимости предпринимать какие-либо действия, чтобы объявить, что они выходят из области видимости впоследствии, последующий код может или не может перезаписать эти индексы.

затем visitMaxs должен сообщить об измененном размере, если он больше старого (если вы не используете COMPUTE_MAXS или же COMPUTE_FRAMES тем не мение).

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