Получение количества локальных переменных в методе
Итак, у меня есть несколько классов, в которые были вставлены "фиктивные вызовы методов"; т.е. статические методы в выделенном классе, которые имеют пустое тело.
Идея состоит в том, чтобы взять аргументы, которые были помещены в стек до вызова метода, сохранить их в локальных переменных, а затем заменить вызов метода фактической реализацией.
Чтобы увидеть, как обрабатываются местные жители, я бегу
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
тем не мение).