百度网站怎样做,wordpress工业模板,北京自适应网站建设,南京网站设计公司兴田德润电话多少Label 介绍
在 ASM 中#xff0c;每一个 Label 必须对应一个 Frame#xff0c;两个 Label 可以共享一个 Frame#xff0c;可以理解为将两个 Label 合并了#xff0c;而一个 Frame 只对应一个 Label#xff0c;就是创建它的 Label。每一次定义一个方法#xff0c;即执行 …Label 介绍
在 ASM 中每一个 Label 必须对应一个 Frame两个 Label 可以共享一个 Frame可以理解为将两个 Label 合并了而一个 Frame 只对应一个 Label就是创建它的 Label。每一次定义一个方法即执行 ClassWriter#visitMethod 方法时调用 MethodWriter 构造方法都会在构造方法中创建一个 Label作为 firstBasicBlock 使用接着访问切换到这个 Label。
在 Frame 中会存在
inputLocals : 方法参数存放于此对应 LOAD 指令
inputStack 类似于一个中转
outputLocals : 各种 STORE 指令会操作这个栈
outputStack 方法的运行操作主要在这个栈中执行各种指令其实最后都是转化成了针对这个outputStack 的 pop 和 push 操作
栈的初始化
此处 MethodWriter.compute 为 COMPUTE_ALL_FRAMES
inputLocals:
inputStack: final void setInputFrameFromDescriptor(final SymbolTable symbolTable,final int access,final String descriptor,final int maxLocals) {inputLocals new int[maxLocals];inputStack new int[0];int inputLocalIndex 0;if ((access Opcodes.ACC_STATIC) 0) {if ((access Constants.ACC_CONSTRUCTOR) 0) {inputLocals[inputLocalIndex] REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName());} else {inputLocals[inputLocalIndex] UNINITIALIZED_THIS;}}for (Type argumentType : Type.getArgumentTypes(descriptor)) {int abstractType getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0);inputLocals[inputLocalIndex] abstractType;if (abstractType LONG || abstractType DOUBLE) {inputLocals[inputLocalIndex] TOP;}}while (inputLocalIndex maxLocals) {inputLocals[inputLocalIndex] TOP;}}
可以看到非 static 方法非构造方法第一个参数对应抽REFERENCE_KIND表示这个类即我们常用的 this接着遍历参数放入每个参数对应的抽象类型这点与Java虚拟机栈保持一致。上面描述的方法会在自定义字节码操作完成执行 MethodWriter#visitMax 时进行调用。
outputLocals: private void setLocal(final int localIndex, final int abstractType) {// Create and/or resize the output local variables array if necessary.if (outputLocals null) {outputLocals new int[10];}int outputLocalsLength outputLocals.length;if (localIndex outputLocalsLength) {int[] newOutputLocals new int[Math.max(localIndex 1, 2 * outputLocalsLength)];System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength);outputLocals newOutputLocals;}// Set the local variable.outputLocals[localIndex] abstractType;}
当执行各种 STORE 指令时会进行 outputLocals 的初始化。
outputStack: private void push(final int abstractType) {// Create and/or resize the output stack array if necessary.if (outputStack null) {outputStack new int[10];}int outputStackLength outputStack.length;if (outputStackTop outputStackLength) {int[] newOutputStack new int[Math.max(outputStackTop 1, 2 * outputStackLength)];System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength);outputStack newOutputStack;}// Pushes the abstract type on the output stack.outputStack[outputStackTop] abstractType;// Updates the maximum size reached by the output stack, if needed (note that this size is// relative to the input stack size, which is not known yet).short outputStackSize (short) (outputStackStart outputStackTop);if (outputStackSize owner.outputStackMax) {owner.outputStackMax outputStackSize;}}
当执行涉及到入栈的指令时例如获取属性 GETFIELD、加载 ALOAD、方法调用 INVOKEVIRTUAL 等指令时会进行 outputStack 的初始化。
并且从上面可以看到各种栈都是 int 型所以当遇到转化为抽象类型为 LONG 和 DOUBLE 的变量类型时在各种栈中占两位即 2 个 int8 个 字节。
模型简介
在 ASM 的栈分为 inputStack 和 outputStackoutputStack 紧接着 inputStack。还有一个参数outputStackStart通过这个参数控制 inputStack 的大小这个参数只能为 0 或 负数为负数表明用到了上一任 Label 中的变量。
参照源码中对 outputStackStart 的注释
outputStackStart输出栈相对于输入栈的起始位置。这个偏移量总是负的或为空。空偏移量意味着输出栈必须追加到输入栈上。-n偏移量意味着前n个输出栈元素必须替换前n个输入栈栈顶元素而其他元素必须追加到输入栈上。
所以当前 Label 输入栈大小
numInputStack inputStack.length outputStackStart
不同的 Label 之间通过设置 successor 这种关系可以使用前任 Label 的输入栈和输出栈。其中前任的输入栈和输出栈会作为 successor 的输入栈所以
前任的输入栈大小numInputStack
前任的输出栈大小outputStackTop
successor 的输入栈大小numInputStack outputStackTop
应用
利用设置的 successor 关系操作变量
public class Generate49 implements Opcodes {public static void main(String[] args) {String generateClassName ASM$Generate49;ClassLoaderUtils.outputClass(generate(generateClassName), generateClassName);}private static byte[] generate(String generateClassName) {ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_FRAMES);// declare_classcw.visit(V1_8, ACC_PUBLIC, generateClassName, null, java/lang/Object, null);// declare_fieldcw.visitField(ACC_PRIVATE, name, Ljava/lang/String;, null, null);// declare_methodMethodVisitor mv cw.visitMethod(ACC_PUBLIC, toUppercaseName, ()Ljava/lang/String;, null, null);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, generateClassName, name, Ljava/lang/String;);mv.visitInsn(DUP);Label l1 new Label();mv.visitJumpInsn(IFNULL, l1);mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), toUpperCase, ()Ljava/lang/String;, false);mv.visitInsn(ARETURN);mv.visitLabel(l1);mv.visitInsn(ACONST_NULL);mv.visitInsn(ARETURN);mv.visitMaxs(0, 0);return cw.toByteArray();}
}
上面代码中的 DUP 指令就是为了保证在执行 INVOKEVIRTUAL 指令时可以利用到前任 Label的输出在执行 visitJumpInsn 时已经发生了 Label 的切换即通过 GETFIELD 指令获取到的name 属性接着执行方法调用。
利用 STORE 操作变量
public class Generate491 implements Opcodes {public static void main(String[] args) {String generateClassName ASM$Generate491;ClassLoaderUtils.outputClass(generate(generateClassName), generateClassName);}private static byte[] generate(String generateClassName) {ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_FRAMES);// declare_classcw.visit(V1_8, ACC_PUBLIC, generateClassName, null, java/lang/Object, null);// declare_fieldcw.visitField(ACC_PRIVATE, name, Ljava/lang/String;, null, null);// declare_methodMethodVisitor mv cw.visitMethod(ACC_PUBLIC, toUppercaseName, ()Ljava/lang/String;, null, null);mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, generateClassName, name, Ljava/lang/String;);mv.visitVarInsn(ASTORE, 1);Label l1 new Label();mv.visitVarInsn(ALOAD, 1);mv.visitJumpInsn(IFNULL, l1);mv.visitVarInsn(ALOAD, 1);mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), toUpperCase, ()Ljava/lang/String;, false);mv.visitInsn(ARETURN);mv.visitLabel(l1);mv.visitInsn(ACONST_NULL);mv.visitInsn(ARETURN);mv.visitMaxs(0, 0);return cw.toByteArray();}
}
可以看到使用 STORE 命令后每一次操作使用到变量都需先进行 LOAD增加了代码量但又直观的反映了栈操作的过程即先入栈再出栈。
生成的代码如下
public class ASM$Generate49 {private String name;public String toUppercaseName() {String var10000 this.name;return var10000 ! null ? var10000.toUpperCase() : null;}
}
可以看到即使是很简单的一个类和方法通过ASM操作起来代码量都是生成类中代码的好几倍。而为了便于使用衍生出了 CGLIB 这样的开源项目其实就是ASM的一个具体应用。
附上上述代码中的工具类ClassLoaderUtils:
public class ClassLoaderUtils extends ClassLoader {public static final String CLASS_SUFFIX .class;public Class? defineClass(String name, byte[] bytes) {return super.defineClass(name, bytes,0, bytes.length);}public static void outputClass(byte[] bytes, String name) {FileOutputStream fos null;try {String pathName ClassLoaderUtils.class.getResource(/).getPath() name CLASS_SUFFIX;fos new FileOutputStream(new File(pathName));fos.write(bytes);} catch (IOException e) {e.printStackTrace();} finally {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}
}