對于LocalVariablesSorter類來說,它的特點是“可以引入新的局部變量,并且能夠?qū)植孔兞恐匦屡判颉薄?/p>
LocalVariablesSorter類
class info
LocalVariablesSorter類繼承自MethodVisitor類。
- org.objectweb.asm.MethodVisitor
- org.objectweb.asm.commons.LocalVariablesSorter
- org.objectweb.asm.commons.GeneratorAdapter
- org.objectweb.asm.commons.AdviceAdapter
- org.objectweb.asm.commons.GeneratorAdapter
- org.objectweb.asm.commons.LocalVariablesSorter
public class LocalVariablesSorter extends MethodVisitor {
}
fields
LocalVariablesSorter類定義的字段有哪些。在理解LocalVariablesSorter類時,一個要記住的核心點:處理好”新變量“與”舊變量“的位置關(guān)系。換句話說,要給”新變量“在local variables當中找一個位置存儲,”舊變量“也要在local variables當中找一個位置存儲,它們的位置不能發(fā)生沖突。對于local variables當中某一個具體的位置,要么存儲的是”新變量“,要么存儲的是”舊變量“,不可能在同一個位置既存儲”新變量“,又存儲”舊變量“。
- remappedVariableIndices字段,是一個int[]數(shù)組,其中所有元素的初始值為0。
- remappedVariableIndices字段的作用:只關(guān)心“舊變量”,它記錄“舊變量”的新位置。
- remappedVariableIndices字段使用的算法,有點奇怪和特別。
- remappedLocalTypes字段,將“舊變量”和“新變量”整合到一起之后,記錄它們的類型信息。
- firstLocal字段,記錄“方法體”中“第一個變量”在local variables當中的索引值,由于帶有final標識,所以賦值之后,就不再發(fā)生變化了。
- nextLocal字段,記錄local variables中可以未分配變量的位置,無論是“新變量”,還是“舊變量”,它們都是由nextLocal字段來分配位置;分配變量之后,nextLocal字段值會發(fā)生變化,重新指向local variables中未分配變量的位置。
public class LocalVariablesSorter extends MethodVisitor {
// The mapping from old to new local variable indices.
// A local variable at index i of size 1 is remapped to 'mapping[2*i]',
// while a local variable at index i of size 2 is remapped to 'mapping[2*i+1]'.
private int[] remappedVariableIndices = new int[40];
// The local variable types after remapping.
private Object[] remappedLocalTypes = new Object[20];
protected final int firstLocal;
protected int nextLocal;
}
constructors
LocalVariablesSorter類定義的構(gòu)造方法有哪些。
public class LocalVariablesSorter extends MethodVisitor {
public LocalVariablesSorter(final int access, final String descriptor, final MethodVisitor methodVisitor) {
this(Opcodes.ASM9, access, descriptor, methodVisitor);
}
protected LocalVariablesSorter(final int api, final int access, final String descriptor,
final MethodVisitor methodVisitor) {
super(api, methodVisitor);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
for (Type argumentType : Type.getArgumentTypes(descriptor)) {
nextLocal += argumentType.getSize();
}
firstLocal = nextLocal;
}
}
methods
LocalVariablesSorter類定義的方法有哪些。LocalVariablesSorter類要處理好“新變量”與“舊變量”之間的關(guān)系。
- newLocal method
newLocal()方法就是為“新變量”來分配位置。
public class LocalVariablesSorter extends MethodVisitor {
public int newLocal(final Type type) {
Object localType;
switch (type.getSort()) {
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
localType = Opcodes.INTEGER;
break;
case Type.FLOAT:
localType = Opcodes.FLOAT;
break;
case Type.LONG:
localType = Opcodes.LONG;
break;
case Type.DOUBLE:
localType = Opcodes.DOUBLE;
break;
case Type.ARRAY:
localType = type.getDescriptor();
break;
case Type.OBJECT:
localType = type.getInternalName();
break;
default:
throw new AssertionError();
}
int local = newLocalMapping(type);
setLocalType(local, type);
setFrameLocal(local, localType);
return local;
}
protected int newLocalMapping(final Type type) {
int local = nextLocal;
nextLocal += type.getSize();
return local;
}
protected void setLocalType(final int local, final Type type) {
// The default implementation does nothing.
}
private void setFrameLocal(final int local, final Object type) {
int numLocals = remappedLocalTypes.length;
if (local >= numLocals) { // 這里是處理分配空間不足的情況
Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)];
System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals);
remappedLocalTypes = newRemappedLocalTypes;
}
remappedLocalTypes[local] = type; // 真正的處理邏輯只有這一句代碼
}
}
- local variables method
visitVarInsn()和visitIincInsn()方法就是為“舊變量”來重新分配位置,這兩個方法都會去調(diào)用remap(var, type)方法。
public class LocalVariablesSorter extends MethodVisitor {
@Override
public void visitVarInsn(final int opcode, final int var) {
Type varType;
switch (opcode) {
case Opcodes.LLOAD:
case Opcodes.LSTORE:
varType = Type.LONG_TYPE;
break;
case Opcodes.DLOAD:
case Opcodes.DSTORE:
varType = Type.DOUBLE_TYPE;
break;
case Opcodes.FLOAD:
case Opcodes.FSTORE:
varType = Type.FLOAT_TYPE;
break;
case Opcodes.ILOAD:
case Opcodes.ISTORE:
varType = Type.INT_TYPE;
break;
case Opcodes.ALOAD:
case Opcodes.ASTORE:
case Opcodes.RET:
varType = OBJECT_TYPE;
break;
default:
throw new IllegalArgumentException("Invalid opcode " + opcode);
}
super.visitVarInsn(opcode, remap(var, varType));
}
@Override
public void visitIincInsn(final int var, final int increment) {
super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
}
private int remap(final int var, final Type type) {
// 第一部分,處理方法的輸入?yún)?shù)
if (var + type.getSize() <= firstLocal) {
return var;
}
// 第二部分,處理方法體內(nèi)定義的局部變量
int key = 2 * var + type.getSize() - 1;
int size = remappedVariableIndices.length;
if (key >= size) { // 這段代碼,主要是處理分配空間不足的情況。我們可以假設(shè)分配的空間一直是足夠的,那么可以忽略此段代碼
int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size);
remappedVariableIndices = newRemappedVariableIndices;
}
int value = remappedVariableIndices[key];
if (value == 0) { // 如果是0,則表示還沒有記錄下來
value = newLocalMapping(type);
setLocalType(value, type);
remappedVariableIndices[key] = value + 1;
} else { // 如果不是0,則表示有具體的值
value--;
}
return value;
}
protected int newLocalMapping(final Type type) {
int local = nextLocal;
nextLocal += type.getSize();
return local;
}
}
工作原理
對于LocalVariablesSorter類的工作原理,主要依賴于三個字段:firstLocal、nextLocal和remappedVariableIndices字段。
public class LocalVariablesSorter extends MethodVisitor {
// The mapping from old to new local variable indices.
// A local variable at index i of size 1 is remapped to 'mapping[2*i]',
// while a local variable at index i of size 2 is remapped to 'mapping[2*i+1]'.
private int[] remappedVariableIndices = new int[40];
protected final int firstLocal;
protected int nextLocal;
}
首先,我們來看一下firstLocal和nextLocal初始化,它發(fā)生在LocalVariablesSorter類的構(gòu)造方法中。其中,firstLocal是一個final類型的字段,一次賦值之后就不能變化了;而nextLocal字段的取值可以繼續(xù)變化。
public class LocalVariablesSorter extends MethodVisitor {
protected LocalVariablesSorter(final int api, final int access, final String descriptor,
final MethodVisitor methodVisitor) {
super(api, methodVisitor);
nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0; // 首先,判斷是不是靜態(tài)方法
for (Type argumentType : Type.getArgumentTypes(descriptor)) { // 接著,循環(huán)方法接收的參數(shù)
nextLocal += argumentType.getSize();
}
firstLocal = nextLocal; // 最后,為firstLocal字段賦值。
}
}
對于上面的代碼,主要是對兩方面內(nèi)容進行判斷:
- 第一方面,是否需要處理this變量。
- 第二方面,對方法接收的參數(shù)進行處理。
在執(zhí)行完LocalVariablesSorter類的構(gòu)造方法后,firstLocal和nextLocal的值是一樣的,其值表示下一個方法體中的變量在local variables當中的位置。接下來,就是該考慮第三方面的事情了:
- 第三方面,方法體內(nèi)定義的變量。對于這些變量,又分成兩種情況:
- 第一種情況,程序代碼中原來定義的變量。
- 第二種情況,程序代碼中新定義的變量。
對于LocalVariablesSorter類來說,它要處理的一個關(guān)鍵性的工作,就是處理好“舊變量”和“新變量”之間的關(guān)系。其實,不管是“新變量”,還是“舊變量”,它都是通過newLocalMapping(type)方法來找到自己的位置。newLocalMapping(type)方法的邏輯就是“先到先得”。有一個形象的例子,可以幫助我們理解newLocalMapping(type)方法的作用。高考之后,過一段時間,大學就會開學,新生就會來報到;不管新學生來自于什么地方,第一個來到學校的學生就分配001的編號,第二個來到學校的學生就分配002的編號,依此類推。
我們先來說明第二種情況,也就是在程序代碼中添加新的變量。
添加新變量
如果要添加新的變量,那么需要調(diào)用newLocal(type)方法。
- 在newLocal(type)方法中,它會進一步調(diào)用newLocalMapping(type)方法;
- 在newLocalMapping(type)方法中,首先會記錄nextLocal的值到local局部變量中,接著會更新nextLocal的值(即加上type.getSize()的值),最后返回local的值。那么,local的值就是新變量在local variables當中存儲的位置。
public class LocalVariablesSorter extends MethodVisitor {
public int newLocal(final Type type) {
int local = newLocalMapping(type);
return local;
}
protected int newLocalMapping(final Type type) {
int local = nextLocal;
nextLocal += type.getSize();
return local;
}
}
處理舊變量
如果要處理“舊變量”,那么需要調(diào)用visitVarInsn(opcode, var)或visitIincInsn(var, increment)方法。在這兩個方法中,會進一步調(diào)用remap(var, type)方法。其中,remap(var, type)方法的主要作用,就是實現(xiàn)“舊變量”的原位置向新位置的映射。
public class LocalVariablesSorter extends MethodVisitor {
@Override
public void visitVarInsn(final int opcode, final int var) {
Type varType;
switch (opcode) {
case Opcodes.LLOAD:
case Opcodes.LSTORE:
varType = Type.LONG_TYPE;
break;
case Opcodes.DLOAD:
case Opcodes.DSTORE:
varType = Type.DOUBLE_TYPE;
break;
case Opcodes.FLOAD:
case Opcodes.FSTORE:
varType = Type.FLOAT_TYPE;
break;
case Opcodes.ILOAD:
case Opcodes.ISTORE:
varType = Type.INT_TYPE;
break;
case Opcodes.ALOAD:
case Opcodes.ASTORE:
case Opcodes.RET:
varType = OBJECT_TYPE;
break;
default:
throw new IllegalArgumentException("Invalid opcode " + opcode);
}
super.visitVarInsn(opcode, remap(var, varType));
}
@Override
public void visitIincInsn(final int var, final int increment) {
super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
}
private int remap(final int var, final Type type) {
// 第一部分,處理方法的輸入?yún)?shù)
if (var + type.getSize() <= firstLocal) {
return var;
}
// 第二部分,處理方法體內(nèi)定義的局部變量
int key = 2 * var + type.getSize() - 1;
int value = remappedVariableIndices[key];
if (value == 0) { // 如果是0,則表示還沒有記錄下來
value = newLocalMapping(type);
remappedVariableIndices[key] = value + 1;
} else { // 如果不是0,則表示有具體的值
value--;
}
return value;
}
protected int newLocalMapping(final Type type) {
int local = nextLocal;
nextLocal += type.getSize();
return local;
}
}
在remap(var, type)方法中,有兩部分主要邏輯:
第一部分,是處理方法的輸入?yún)?shù)。方法接收的參數(shù),它們在local variables當中的索引位置是不會變化的,所以處理起來也比較簡單,直接返回var的值。
第二部分,是處理方法體內(nèi)定義的局部變量。在這個部分,就是remappedVariableIndices字段發(fā)揮作用的地方,也會涉及到nextLocal字段。
在remap(var, type)方法中,我們重點關(guān)注第二部分,代碼處理的步驟是:第一步,計算出remappedVariableIndices字段的一個索引值key,即int key = 2 * var + type.getSize() - 1。假設(shè)有一個變量的索引是i,如果該變量的大小是1,那么它在remappedVariableIndices字段中的索引位置是2i;如果該變量(long或double類型)的大小是2,那么它在remappedVariableIndices字段中的索引位置是2i+1。
第二步,根據(jù)key值,取出remappedVariableIndices字段當中的value值。大家注意,int[] remappedVariableIndices = new int[40],也就是說,
remappedVariableIndices字段是一個數(shù)組,所有元素的默認值是0。
如果value的值是0,說明還沒有記錄“舊變量”的新位置;那么,就通過value =newLocalMapping(type)計算出新的位置,將value + 1賦值給remappedVariableIndices字段中key位置。
如果value的值不是0,說明已經(jīng)記錄“舊變量”的新位置;這個時候,要進行value--操作。第三步,返回value的值。那么,這個value值就是“舊變量”的新位置。
示例
預期目標
假如有一個HelloWorld類,代碼如下:
import java.util.Random;
public class HelloWorld {
public void test(int a, int b) throws Exception {
int c = a + b;
int d = c * 10;
Random rand = new Random();
int value = rand.nextInt(d);
Thread.sleep(value);
}
}
我們想實現(xiàn)的預期目標:添加一個新的局部變量t,然后使用變量t計算方法的運行時間。
import java.util.Random;
public class HelloWorld {
public void test(int a, int b) throws Exception {
long t = System.currentTimeMillis();
int c = a + b;
int d = c * 10;
Random rand = new Random();
int value = rand.nextInt(d);
Thread.sleep(value);
t = System.currentTimeMillis() - t;
System.out.println("test method execute: " + t);
}
}
編碼實現(xiàn)
下面的MethodTimerAdapter3類繼承自LocalVariablesSorter類。
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import static org.objectweb.asm.Opcodes.*;
public class MethodTimerVisitor3 extends ClassVisitor {
public MethodTimerVisitor3(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null && !"<init>".equals(name) && !"<clinit>".equals(name)) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodTimerAdapter3(api, access, name, descriptor, mv);
}
}
return mv;
}
private static class MethodTimerAdapter3 extends LocalVariablesSorter {
private final String methodName;
private final String methodDesc;
private int slotIndex;
public MethodTimerAdapter3(int api, int access, String name, String descriptor, MethodVisitor methodVisitor) {
super(api, access, descriptor, methodVisitor);
this.methodName = name;
this.methodDesc = descriptor;
}
@Override
public void visitCode() {
// 首先,實現(xiàn)自己的邏輯
slotIndex = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, slotIndex);
// 其次,調(diào)用父類的實現(xiàn)
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
// 首先,實現(xiàn)自己的邏輯
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, slotIndex);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, slotIndex);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn(methodName + methodDesc + " method execute: ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, slotIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
// 其次,調(diào)用父類的實現(xiàn)
super.visitInsn(opcode);
}
}
}
需要注意的是,我們使用的是mv.visitVarInsn(opcode, var)方法,而不是使用super.visitVarInsn(opcode, var)方法。為什么要使用mv,而不使用super呢?因為使用super.visitVarInsn(opcode, var)方法,實質(zhì)上是調(diào)用了LocalVariablesSorter.visitVarInsn(opcode, var),它會進一步調(diào)用remap(var, type)方法,這就可能導致新添加的變量在local variables中的位置發(fā)生“位置偏移”。
下面的MethodTimerAdapter4類繼承自AdviceAdapter類。
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_NATIVE;
public class MethodTimerVisitor4 extends ClassVisitor {
public MethodTimerVisitor4(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if (mv != null) {
boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
boolean isNativeMethod = (access & ACC_NATIVE) != 0;
if (!isAbstractMethod && !isNativeMethod) {
mv = new MethodTimerAdapter4(api, mv, access, name, descriptor);
}
}
return mv;
}
private static class MethodTimerAdapter4 extends AdviceAdapter {
private int slotIndex;
public MethodTimerAdapter4(int api, MethodVisitor mv, int access, String name, String descriptor) {
super(api, mv, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
slotIndex = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, slotIndex);
}
@Override
protected void onMethodExit(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, slotIndex);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, slotIndex);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn(getName() + methodDesc + " method execute: ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, slotIndex);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
}
}
進行轉(zhuǎn)換
import lsieun.utils.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public class HelloWorldTransformCore {
public static void main(String[] args) {
String relative_path = "sample/HelloWorld.class";
String filepath = FileUtils.getFilePath(relative_path);
byte[] bytes1 = FileUtils.readBytes(filepath);
//(1)構(gòu)建ClassReader
ClassReader cr = new ClassReader(bytes1);
//(2)構(gòu)建ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//(3)串連ClassVisitor
int api = Opcodes.ASM9;
ClassVisitor cv = new MethodTimerVisitor4(api, cw);
//(4)結(jié)合ClassReader和ClassVisitor
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cv, parsingOptions);
//(5)生成byte[]
byte[] bytes2 = cw.toByteArray();
FileUtils.writeBytes(filepath, bytes2);
}
}
驗證結(jié)果
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
HelloWorld instance = new HelloWorld();
instance.test(10, 20);
}
}
總結(jié)
本文對LocalVariablesSorter類進行介紹,內(nèi)容總結(jié)如下:
- 第一點,了解LocalVariablesSorter類的各個部分,都有哪些信息。
- 第二點,理解LocalVariablesSorter類的工作原理。
- 第三點,如何使用LocalVariablesSorter類添加新的變量。