50 - ASM之AnalyzerAdapter

对于AnalyzerAdapter类来说,它的特点是“可以模拟frame的变化”,或者说“可以模拟local variables和operand stack的变化”。

The AnalyzerAdapter is a MethodVisitor that keeps track of stack map frame changes between visitFrame(int, int, Object[], int, Object[]) calls.
This AnalyzerAdapter adapter must be used with the ClassReader.EXPAND_FRAMES option.

This method adapter computes the stack map frames before each instruction, based on the frames visited in visitFrame.
Indeed, visitFrame is only called before some specific instructions in a method, in order to save space,
and because “the other frames can be easily and quickly inferred from these ones”.
This is what this adapter does.

frame 变化

AnalyzerAdapter类

class info

AnalyzerAdapter类的父类是MethodVisitor类。

public class AnalyzerAdapter extends MethodVisitor {
}

fields

AnalyzerAdapter类定义的字段有哪些。

我们将以下列出的字段分成3个组:

  • 第1组,包括locals、stack、maxLocals和maxStack字段,它们是与local variables和operand stack直接相关的字段。
  • 第2组,包括labels和uninitializedTypes字段,它们记录的是未初始化的对象类型,是属于一些特殊情况。
  • 第3组,是owner字段,表示当前类的名字。
public class AnalyzerAdapter extends MethodVisitor {
    // 第1组字段:local variables和operand stack
    public List<Object> locals;
    public List<Object> stack;
    private int maxLocals;
    private int maxStack;

    // 第2组字段:uninitialized类型
    private List<Label> labels;
    public Map<Object, Object> uninitializedTypes;

    // 第3组字段:类的名字
    private String owner;
}

constructors

AnalyzerAdapter类定义的构造方法有哪些。

有一个问题:AnalyzerAdapter类的构造方法,到底是想实现一个什么样的代码逻辑呢?回答:它想构建方法刚进入时的Frame状态。在方法刚进入时,Frame的初始状态是什么样的呢?其中,operand stack上没有任何元素,而local variables则需要考虑存储this和方法的参数信息。在AnalyzerAdapter类的构造方法中,主要就是围绕着locals字段来展开,它需要将this和方法参数添加进入。

public class AnalyzerAdapter extends MethodVisitor {
    public AnalyzerAdapter(String owner, int access, String name, String descriptor, MethodVisitor methodVisitor) {
        this(Opcodes.ASM9, owner, access, name, descriptor, methodVisitor);
    }

    protected AnalyzerAdapter(int api, String owner, int access, String name, String descriptor, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
        this.owner = owner;
        locals = new ArrayList<>();
        stack = new ArrayList<>();
        uninitializedTypes = new HashMap<>();
        
        // 首先,判断是不是static方法、是不是构造方法,来更新local variables的初始状态
        if ((access & Opcodes.ACC_STATIC) == 0) {
            if ("<init>".equals(name)) {
                locals.add(Opcodes.UNINITIALIZED_THIS);
            } else {
                locals.add(owner);
            }
        }

        // 其次,根据方法接收的参数,来更新local variables的初始状态
        for (Type argumentType : Type.getArgumentTypes(descriptor)) {
            switch (argumentType.getSort()) {
                case Type.BOOLEAN:
                case Type.CHAR:
                case Type.BYTE:
                case Type.SHORT:
                case Type.INT:
                    locals.add(Opcodes.INTEGER);
                    break;
                case Type.FLOAT:
                    locals.add(Opcodes.FLOAT);
                    break;
                case Type.LONG:
                    locals.add(Opcodes.LONG);
                    locals.add(Opcodes.TOP);
                    break;
                case Type.DOUBLE:
                    locals.add(Opcodes.DOUBLE);
                    locals.add(Opcodes.TOP);
                    break;
                case Type.ARRAY:
                    locals.add(argumentType.getDescriptor());
                    break;
                case Type.OBJECT:
                    locals.add(argumentType.getInternalName());
                    break;
                default:
                    throw new AssertionError();
            }
        }
        maxLocals = locals.size();
    }
}

methods

AnalyzerAdapter类定义的方法有哪些。

  • execute方法

在AnalyzerAdapter类当中,多数的visitXxxInsn()方法都会去调用execute()方法;而execute()方法是模拟每一条instruction对于local variables和operand stack的影响。

public class AnalyzerAdapter extends MethodVisitor {
    private void execute(final int opcode, final int intArg, final String stringArg) {
        // ......
    }
}
  • return和throw

当遇到return或throw时,会将locals字段和stack字段设置为null。如果遇到return之后,就代表了“正常结束”,方法的代码执行结束了;如果遇到throw之后,就代表了“出现异常”,方法处理不了某种情况而退出。

public class AnalyzerAdapter extends MethodVisitor {
    // 这里对应return语句
    public void visitInsn(final int opcode) {
        super.visitInsn(opcode);
        execute(opcode, 0, null);
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
            this.locals = null;
            this.stack = null;
        }
    }
}
  • jump

当遇到goto、switch(tableswitch和lookupswitch)时,也会将locals字段和stack字段设置为null。遇到jump相关的指令,意味着代码的逻辑要进行“跳转”,从一个地方跳转到另一个地方执行。

public class AnalyzerAdapter extends MethodVisitor {
    // 这里对应goto语句
    public void visitJumpInsn(final int opcode, final Label label) {
        super.visitJumpInsn(opcode, label);
        execute(opcode, 0, null);
        if (opcode == Opcodes.GOTO) {
            this.locals = null;
            this.stack = null;
        }
    }

    // 这里对应switch语句
    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
        super.visitTableSwitchInsn(min, max, dflt, labels);
        execute(Opcodes.TABLESWITCH, 0, null);
        this.locals = null;
        this.stack = null;
    }
    
    // 这里对应switch语句
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        super.visitLookupSwitchInsn(dflt, keys, labels);
        execute(Opcodes.LOOKUPSWITCH, 0, null);
        this.locals = null;
        this.stack = null;
    }
}
  • visitFrame方法

当遇到jump相关的指令后,程序的代码会发生跳转。那么,跳转到新位置之后,就需要给local variables和operand stack重新设置一个新的状态;而visitFrame()方法,是将local variables和operand stack设置成某一个状态。跳转之后的代码,就是在这个新状态的基础上发生变化。

public class AnalyzerAdapter extends MethodVisitor {
    public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
        if (type != Opcodes.F_NEW) { // Uncompressed frame.
            throw new IllegalArgumentException("AnalyzerAdapter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
        }
        
        super.visitFrame(type, numLocal, local, numStack, stack);
        
        if (this.locals != null) {
            this.locals.clear();
            this.stack.clear();
        } else {
            this.locals = new ArrayList<>();
            this.stack = new ArrayList<>();
        }

        visitFrameTypes(numLocal, local, this.locals);
        visitFrameTypes(numStack, stack, this.stack);
        maxLocals = Math.max(maxLocals, this.locals.size());
        maxStack = Math.max(maxStack, this.stack.size());
    }

    private static void visitFrameTypes(int numTypes, Object[] frameTypes, List<Object> result) {
        for (int i = 0; i < numTypes; ++i) {
            Object frameType = frameTypes[i];
            result.add(frameType);
            if (frameType == Opcodes.LONG || frameType == Opcodes.DOUBLE) {
                result.add(Opcodes.TOP);
            }
        }
    }
}
  • new和invokespecial

在执行程序代码的时候,有些特殊的情况需要处理:

  • 当遇到new时,会创建Label对象来表示“未初始化的对象”,并将label存储到uninitializedTypes字段内;
  • 当遇到invokespecial时,会把“未初始化的对象”从uninitializedTypes字段内取出来,转换成“经过初始化之后的对象”,然后同步到locals字段和stack字段内。
public class AnalyzerAdapter extends MethodVisitor {
    // 对应于new
    public void visitTypeInsn(final int opcode, final String type) {
        if (opcode == Opcodes.NEW) {
            if (labels == null) {
                Label label = new Label();
                labels = new ArrayList<>(3);
                labels.add(label);
                if (mv != null) {
                    mv.visitLabel(label);
                }
            }
            for (Label label : labels) {
                uninitializedTypes.put(label, type);
            }
        }
        super.visitTypeInsn(opcode, type);
        execute(opcode, 0, type);
    }

    // 对应于invokespecial
    public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
        super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
        int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;
        
        if (this.locals == null) {
            labels = null;
            return;
        }
        pop(descriptor);
        if (opcode != Opcodes.INVOKESTATIC) {
            Object value = pop();
            if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
                Object initializedValue;
                if (value == Opcodes.UNINITIALIZED_THIS) {
                    initializedValue = this.owner;
                } else {
                    initializedValue = uninitializedTypes.get(value);
                }
                for (int i = 0; i < locals.size(); ++i) {
                    if (locals.get(i) == value) {
                        locals.set(i, initializedValue);
                    }
                }
                for (int i = 0; i < stack.size(); ++i) {
                    if (stack.get(i) == value) {
                        stack.set(i, initializedValue);
                    }
                }
            }
        }
        pushDescriptor(descriptor);
        labels = null;
    }
}

工作原理

在上面的内容,我们分别介绍了AnalyzerAdapter类的各个部分的信息,那么在这里,我们的目标是按照一个抽象的逻辑顺序来将各个部分组织到一起。那么,这个抽象的逻辑是什么呢?就是local variables和operand stack的状态变化,从初始状态,到中间状态,再到结束状态。

一个类能够为外界提供什么样的“信息”,只要看它的public成员就可以了。如果我们仔细观察一下AnalyzerAdapter类,就会发现:除了从MethodVisitor类继承的visitXxxInsn()方法,AnalyzerAdapter类自己只定义了三个public类型的字段,即locals、stack和uninitializedTypes。如果我们想了解和使用AnalyzerAdapter类,只要把握住这三个字段就可以了。

AnalyzerAdapter类的主要作用就是记录stack map frame的变化情况;在frame当中,有两个重要的结构,即local variables和operand stack。结合刚才的三个字段,其中locals和stack分别表示local variables和operand stack;而uninitializedTypes则是记录一种特殊的状态,这个状态就是“对象已经通过new创建了,但是还没有调用它的构造方法”,这个状态只是一个“临时”的状态,等后续调用它的构造方法之后,它就是一个真正意义上的对象了。举一个例子,一个人拿到了大学录取通知书,可以笼统的叫作”大学生“,但是还不是真正意义上的”大学生“,是一种”临时“的过渡状态,等到去大学报到之后,才成为真正意义上的大学生。

public class AnalyzerAdapter extends MethodVisitor {
    // 第1组字段:local variables和operand stack
    public List<Object> locals;
    public List<Object> stack;

    // 第2组字段:uninitialized类型
    private List<Label> labels;
    public Map<Object, Object> uninitializedTypes;
}

我们在研究local variables和operand stack的变化时,遵循下面的思路就可以了:

  • 首先,初始状态。也就是说,最开始的时候,local variables和operand stack是如何布局的。
  • 其次,中间状态。local variables和operand stack会随着Instruction的执行而发生变化。按照Instruction执行的顺序,我们这里又分成两种情况:
    • 第一种情况,Instruction按照顺序一条一条的向下执行。在这第一种情况里,还有一种特殊情况,就是new对象时,出现的特殊状态下的对象,也就是“已经分配内存空间,但还没有调用构造方法的对象”。
    • 第二种情况,遇到jump相关的Instruction,程序代码逻辑要发生跳转。
  • 最后,结束状态。方法退出,可以是正常退出(return),也可以异常退出(throw)。

这三种状态,可以与“生命体”作一个类比。在这个世界上,大多数的生命体,都会经历出生、成长、衰老和死亡的变化。

在Java语言当中,流程控制语句有三种,分别是顺序(sequential structure)、选择(selective structure)和循环(cycle structure)。但是,如果进入到ByteCode层面或Instruction层面,那么选择(selective structure)和循环(cycle structure)本质上是一样的,都是跳转(Jump)。

初始状态

首先,就是local variables和operand stack的初始状态,它是通过AnalyzerAdapter类的构造方法来为locals和stack字段赋值。

public class AnalyzerAdapter extends MethodVisitor {
    protected AnalyzerAdapter(int api, String owner, int access, String name, String descriptor, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
        this.owner = owner;
        locals = new ArrayList<>();
        stack = new ArrayList<>();
        uninitializedTypes = new HashMap<>();
        
        // 首先,判断是不是static方法、是不是构造方法,来更新local variables的初始状态
        if ((access & Opcodes.ACC_STATIC) == 0) {
            if ("<init>".equals(name)) {
                locals.add(Opcodes.UNINITIALIZED_THIS);
            } else {
                locals.add(owner);
            }
        }

        // 其次,根据方法接收的参数,来更新local variables的初始状态
        for (Type argumentType : Type.getArgumentTypes(descriptor)) {
            switch (argumentType.getSort()) {
                case Type.BOOLEAN:
                case Type.CHAR:
                case Type.BYTE:
                case Type.SHORT:
                case Type.INT:
                    locals.add(Opcodes.INTEGER);
                    break;
                case Type.FLOAT:
                    locals.add(Opcodes.FLOAT);
                    break;
                case Type.LONG:
                    locals.add(Opcodes.LONG);
                    locals.add(Opcodes.TOP);
                    break;
                case Type.DOUBLE:
                    locals.add(Opcodes.DOUBLE);
                    locals.add(Opcodes.TOP);
                    break;
                case Type.ARRAY:
                    locals.add(argumentType.getDescriptor());
                    break;
                case Type.OBJECT:
                    locals.add(argumentType.getInternalName());
                    break;
                default:
                    throw new AssertionError();
            }
        }
        maxLocals = locals.size();
    }
}

在上面的构造方法中,operand stack的初始状态是空的;而local variables的初始状态需要考虑两方面的内容:

  • 第一方面,当前方法是不是static方法、当前方法是不是<init>()方法。
  • 第二方面,方法接收的参数。

中间状态

  • 顺序执行

接着,就是instruction的执行会使得local variables和operand stack状态发生变化。在这个过程中,visitXxxInsn()方法大多是通过调用execute(opcode, intArg, stringArg)方法来完成。

public class AnalyzerAdapter extends MethodVisitor {
    private void execute(final int opcode, final int intArg, final String stringArg) {
        // ......
    }
}
  • 发生跳转

当遇到jump相关的指令时,程序代码会从一个地方跳转到另一个地方。

当程序跳转完成之后,需要通过visitFrame()方法为locals和stack字段赋一个新的初始值。再往下执行,可能就进入到“顺序执行”的过程了。

  • 特殊情况:new对象

对于“未初始化的对象类型”,我们来举个例子,比如说new String()会创建一个String类型的对象,但是对应到ByteCode层面是3条instruction:

NEW java/lang/String
DUP
INVOKESPECIAL java/lang/String.<init> ()V
  • 第1条instruction,是NEW java/lang/String,会为即将创建的对象分配内存空间,确切的说是在堆(heap)上分配内存空间,同时将一个reference放到operand stack上,这个reference就指向这块内存空间。由于这块内存空间还没有进行初始化,所以这个reference对应的内容并不能确切的叫作“对象”,只能叫作“未初始化的对象”,也就是“uninitialized object”。
  • 第2条instruction,是DUP,会将operand stack上的原有的reference复制一份,这时候operand stack上就有两个reference,这两个reference都指向那块未初始化的内存空间,这两个reference的内容都对应于同一个“uninitialized object”。
  • 第3条instruction,是INVOKESPECIAL java/lang/String.<init> ()V,会将那块内存空间进行初始化,同时会“消耗”掉operand stack最上面的reference,那么就只剩下一个reference了。由于那块内存空间进行了初始化操作,那么剩下的reference对应的内容就是一个“经过初始化的对象”,就是一个平常所说的“对象”了。

结束状态

从JVM内存空间的角度来说,每一个方法都有对应的frame内存空间:当方法开始的时候,就会创建相应的frame内存空间;当方法结束的时候,就会清空相应的frame内存空间。换句话说,当方法结束的时候,frame内存空间的local variables和operand stack也就被清空了。所以,从JVM内存空间的角度来说,结束状态,就是local variables和operand stack所占用的内存空间都“消失了”。

从Java代码的角度来说,方法的退出,就对应于visitInsn(opcode)方法中return和throw的情况。

对于local variables和operand stack的结束状态,它又重要,又不重要:

  • 它不重要,是因为它的内存空间被回收了或“消失了”,不需要我们花费太多的时间去思考它,这是从“自身所包含内容的多与少”的角度来考虑。
  • 它重要,是因为它在“初始状态-中间状态-结束状态”这个环节当中是必不可少的一部分,这是从“整体性”的角度上来考虑。

示例:打印方法的Frame

预期目标

假如有一个HelloWorld类,代码如下:

import java.util.Random;

public class HelloWorld {
    public HelloWorld() {
        super();
    }

    public boolean getFlag() {
        Random rand = new Random();
        return rand.nextBoolean();
    }

    public void test(boolean flag) {
        if (flag) {
            System.out.println("value is true");
        }
        else {
            System.out.println("value is false");
        }
    }

    public static void main(String[] args) {
        HelloWorld instance = new HelloWorld();
        boolean flag = instance.getFlag();
        instance.test(flag);
    }
}

我们想实现的预期目标:打印出HelloWorld类当中各个方法的frame变化情况。

编码实现

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AnalyzerAdapter;

import java.util.Arrays;
import java.util.List;

public class MethodStackMapFrameVisitor extends ClassVisitor {
    private String owner;

    public MethodStackMapFrameVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.owner = name;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        return new MethodStackMapFrameAdapter(api, owner, access, name, descriptor, mv);
    }

    private static class MethodStackMapFrameAdapter extends AnalyzerAdapter {
        private final String methodName;
        private final String methodDesc;

        public MethodStackMapFrameAdapter(int api, String owner, int access, String name, String descriptor, MethodVisitor methodVisitor) {
            super(api, owner, access, name, descriptor, methodVisitor);
            this.methodName = name;
            this.methodDesc = descriptor;
        }

        @Override
        public void visitCode() {
            super.visitCode();
            System.out.println();
            System.out.println(methodName + methodDesc);
            printStackMapFrame();
        }

        @Override
        public void visitInsn(int opcode) {
            super.visitInsn(opcode);
            printStackMapFrame();
        }

        @Override
        public void visitIntInsn(int opcode, int operand) {
            super.visitIntInsn(opcode, operand);
            printStackMapFrame();
        }

        @Override
        public void visitVarInsn(int opcode, int var) {
            super.visitVarInsn(opcode, var);
            printStackMapFrame();
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
            super.visitTypeInsn(opcode, type);
            printStackMapFrame();
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
            super.visitFieldInsn(opcode, owner, name, descriptor);
            printStackMapFrame();
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            printStackMapFrame();
        }

        @Override
        public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
            super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
            printStackMapFrame();
        }

        @Override
        public void visitJumpInsn(int opcode, Label label) {
            super.visitJumpInsn(opcode, label);
            printStackMapFrame();
        }

        @Override
        public void visitLdcInsn(Object value) {
            super.visitLdcInsn(value);
            printStackMapFrame();
        }

        @Override
        public void visitIincInsn(int var, int increment) {
            super.visitIincInsn(var, increment);
            printStackMapFrame();
        }

        @Override
        public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
            super.visitTableSwitchInsn(min, max, dflt, labels);
            printStackMapFrame();
        }

        @Override
        public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
            super.visitLookupSwitchInsn(dflt, keys, labels);
            printStackMapFrame();
        }

        @Override
        public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
            super.visitMultiANewArrayInsn(descriptor, numDimensions);
            printStackMapFrame();
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
            super.visitTryCatchBlock(start, end, handler, type);
            printStackMapFrame();
        }

        private void printStackMapFrame() {
            String locals_str = locals == null ? "[]" : list2Str(locals);
            String stack_str = stack == null ? "[]" : list2Str(stack);
            String line = String.format("%s %s", locals_str, stack_str);
            System.out.println(line);
        }

        private String list2Str(List<Object> list) {
            int size = list.size();
            String[] array = new String[size];
            for (int i = 0; i < size - 1; i++) {
                Object item = list.get(i);
                array[i] = item2Str(item);
            }
            if (size > 0) {
                int lastIndex = size - 1;
                Object item = list.get(lastIndex);
                array[lastIndex] = item2Str(item);
            }
            return Arrays.toString(array);
        }

        private String item2Str(Object obj) {
            if (obj == Opcodes.TOP) {
                return "top";
            }
            else if (obj == Opcodes.INTEGER) {
                return "int";
            }
            else if (obj == Opcodes.FLOAT) {
                return "float";
            }
            else if (obj == Opcodes.DOUBLE) {
                return "double";
            }
            else if (obj == Opcodes.LONG) {
                return "long";
            }
            else if (obj == Opcodes.NULL) {
                return "null";
            }
            else if (obj == Opcodes.UNINITIALIZED_THIS) {
                return "uninitialized_this";
            }
            else if (obj instanceof Label) {
                Object value = uninitializedTypes.get(obj);
                if (value == null) {
                    return obj.toString();
                }
                else {
                    return "uninitialized_" + value;
                }
            }
            else {
                return obj.toString();
            }
        }
    }
}

验证结果

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 HelloWorldFrameCore {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new MethodStackMapFrameVisitor(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.EXPAND_FRAMES; // 注意,这里使用了EXPAND_FRAMES
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}

小结

本文对AnalyzerAdapter类进行介绍,内容总结如下:

  • 第一点,了解AnalyzerAdapter类的各个不同部分。
  • 第二点,理解AnalyzerAdapter类的代码原理,它是围绕着local variables和operand stack如何变化来展开的。
  • 第三点,需要注意的一点是,在使用AnalyzerAdapter类时,要记得用ClassReader.EXPAND_FRAMES选项。

AnalyzerAdapter类,更多的是具有“学习特性”,而不是“实用特性”。所谓的“学习特性”,具体来说,就是AnalyzerAdapter类让我们能够去学习local variables和operand stack随着instruction的向下执行而发生变化。所谓的“实用特性”,就是像AdviceAdapter类那样,它有明确的使用场景,能够在“方法进入”的时候和“方法退出”的时候来添加一些代码逻辑。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • Frame内存结构 JVM Architecture由Class Loader SubSystem、Runtime...
    舍是境界阅读 778评论 0 1
  • JVM内存模型Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: ...
    光剑书架上的书阅读 2,501评论 2 26
  • 任何一台计算机的指令系统一般都包含有几十条到上百条指令,下面按一般计算机的功能把指令划分以下几种类型.(1)算术运...
    onedam阅读 1,412评论 0 0
  • 使用ASM创建新的类是围绕着Class Generation来展开,在前面的文章中,我们介绍了ASM Core A...
    舍是境界阅读 1,055评论 0 1
  • 在程序中,有三种基本控制结构:顺序、选择和循环。我们现在已经知道,MethodVisitor类是用于生成方法体的代...
    舍是境界阅读 904评论 0 2