首先理清楚ASM 的主要功能,首先需要做的是对class文件的理解。
这边常量池设计的很不错,后面会说到如何在两个类合并,或者方法引入的时候,常量池的变化
这边真心想吐槽下注解,5.0后 jvm spec增加了,很多新的属性,导致ASM代码急剧扩容,小弟刚刚开始看的时候,累的一米。另外写一个完整支持JVM规范的class操作框架,真的不易,里面设计到好多优化的问题
大家可以先思考思考,对指令的解析(待续),在解析完成,ASM把常量池,方法,Feild解析出来的字节码,封装在不同的Vistor实现类中。这边要说下,增加方法,只需要
下面修改常量池的代码来了
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
class文件的结构主要不会i变化,但是解析Class文件的算法,却会有不同的实现方式,这时候ASM框架使用了Vistor模式来做解析class,代码如下:
public static void main(final String[] args) throws Exception {
ClassReader cr = new ClassReader(TestAsm.getClassInputStream("D:\\test\\Tree.class"));
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cr.accept(cw, ClassReader.SKIP_DEBUG);
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "test", "(I)String",
null, null);
mw.visitInsn(ILOAD);
mw.visitInsn(ISTORE);
mw.visitFieldInsn(GETSTATIC, "TestAsm", "analyze", "B");
mw.visitEnd();
cw.toByteArray();
}
}
在构造ClassReader的构造方法如下
public ClassReader(final byte[] b, final int off, final int len) {
this.b = b;
// checks the class version
if (readShort(off + 6) > Opcodes.V1_8) {
throw new IllegalArgumentException();
}
// parses the constant pool
items = new int[readUnsignedShort(off + 8)];
int n = items.length;
strings = new String[n];
int max = 0;
int index = off + 10;
//这边解析常量池逻辑
for (int i = 1; i < n; ++i) {
items[i] = index + 1;
int size;
switch (b[index]) {
case ClassWriter.FIELD:
case ClassWriter.METH:
case ClassWriter.IMETH:
case ClassWriter.INT:
case ClassWriter.FLOAT:
case ClassWriter.NAME_TYPE:
case ClassWriter.INDY:
size = 5;
break;
case ClassWriter.LONG:
case ClassWriter.DOUBLE:
size = 9;
++i;
break;
case ClassWriter.UTF8:
//UTF-8字节读写的逻辑
size = 3 + readUnsignedShort(index + 1);
if (size > max) {
max = size;
}
break;
case ClassWriter.HANDLE:
size = 4;
break;
// case ClassWriter.CLASS:
// case ClassWriter.STR:
// case ClassWriter.MTYPE
default:
size = 3;
break;
}
index += size;
}
maxStringLength = max;
// the class header information starts just after the constant pool
//这边解析出常量池,之后的属性。
header = index;
}
这边常量池设计的很不错,后面会说到如何在两个类合并,或者方法引入的时候,常量池的变化
在ClassReader中核心方法代码(accept)如下
public void accept(final ClassVisitor classVisitor,
final Attribute[] attrs, final int flags) {
int u = header; // current offset in the class file
char[] c = new char[maxStringLength]; // buffer used to read strings
Context context = new Context();
context.attrs = attrs;
context.flags = flags;
context.buffer = c;
// reads the class declaration
int access = readUnsignedShort(u);
String name = readClass(u + 2, c);
String superClass = readClass(u + 4, c);
String[] interfaces = new String[readUnsignedShort(u + 6)];
u += 8;
for (int i = 0; i < interfaces.length; ++i) {
interfaces[i] = readClass(u, c);
u += 2;
}
// reads the class attributes
String signature = null;
String sourceFile = null;
String sourceDebug = null;
String enclosingOwner = null;
String enclosingName = null;
String enclosingDesc = null;
int anns = 0;
int ianns = 0;
int tanns = 0;
int itanns = 0;
int innerClasses = 0;
Attribute attributes = null;
u = getAttributes();
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
// tests are sorted in decreasing frequency order
// (based on frequencies observed on typical classes)
if ("SourceFile".equals(attrName)) {
sourceFile = readUTF8(u + 8, c);
} else if ("InnerClasses".equals(attrName)) {
innerClasses = u + 8;
} else if ("EnclosingMethod".equals(attrName)) {
enclosingOwner = readClass(u + 8, c);
int item = readUnsignedShort(u + 10);
if (item != 0) {
enclosingName = readUTF8(items[item], c);
enclosingDesc = readUTF8(items[item] + 2, c);
}
} else if (SIGNATURES && "Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
} else if (ANNOTATIONS
&& "RuntimeVisibleAnnotations".equals(attrName)) {
anns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeVisibleTypeAnnotations".equals(attrName)) {
tanns = u + 8;
} else if ("Deprecated".equals(attrName)) {
access |= Opcodes.ACC_DEPRECATED;
} else if ("Synthetic".equals(attrName)) {
access |= Opcodes.ACC_SYNTHETIC
| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
} else if ("SourceDebugExtension".equals(attrName)) {
int len = readInt(u + 4);
sourceDebug = readUTF(u + 8, len, new char[len]);
} else if (ANNOTATIONS
&& "RuntimeInvisibleAnnotations".equals(attrName)) {
ianns = u + 8;
} else if (ANNOTATIONS
&& "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
itanns = u + 8;
} else if ("BootstrapMethods".equals(attrName)) {
int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
bootstrapMethods[j] = v;
v += 2 + readUnsignedShort(v + 2) << 1;
}
context.bootstrapMethods = bootstrapMethods;
} else {
Attribute attr = readAttribute(attrs, attrName, u + 8,
readInt(u + 4), c, -1, null);
if (attr != null) {
attr.next = attributes;
attributes = attr;
}
}
u += 6 + readInt(u + 4);
}
// visits the class declaration
classVisitor.visit(readInt(items[1] - 7), access, name, signature,
superClass, interfaces);
// visits the source and debug info
if ((flags & SKIP_DEBUG) == 0
&& (sourceFile != null || sourceDebug != null)) {
classVisitor.visitSource(sourceFile, sourceDebug);
}
// visits the outer class
if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName,
enclosingDesc);
}
// visits the class annotations and type annotations
if (ANNOTATIONS && anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), true));
}
}
if (ANNOTATIONS && ianns != 0) {
for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitAnnotation(readUTF8(v, c), false));
}
}
if (ANNOTATIONS && tanns != 0) {
for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), true));
}
}
if (ANNOTATIONS && itanns != 0) {
for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
v = readAnnotationTarget(context, v);
v = readAnnotationValues(v + 2, c, true,
classVisitor.visitTypeAnnotation(context.typeRef,
context.typePath, readUTF8(v, c), false));
}
}
// visits the attributes
while (attributes != null) {
Attribute attr = attributes.next;
attributes.next = null;
classVisitor.visitAttribute(attributes);
attributes = attr;
}
// visits the inner classes
if (innerClasses != 0) {
int v = innerClasses + 2;
for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
classVisitor.visitInnerClass(readClass(v, c),
readClass(v + 2, c), readUTF8(v + 4, c),
readUnsignedShort(v + 6));
v += 8;
}
}
// visits the fields and methods
u = header + 10 + 2 * interfaces.length;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readField(classVisitor, context, u);
}
u += 2;
for (int i = readUnsignedShort(u - 2); i > 0; --i) {
u = readMethod(classVisitor, context, u);
}
// visits the end of the class
classVisitor.visitEnd();
}
这边真心想吐槽下注解,5.0后 jvm spec增加了,很多新的属性,导致ASM代码急剧扩容,小弟刚刚开始看的时候,累的一米。另外写一个完整支持JVM规范的class操作框架,真的不易,里面设计到好多优化的问题
到这个过程里面ClassReader把控制权交给ClassVistor的实现类,来vistor需要的属性了,在vistor中,最核心的方法莫过于readMethod(classVisitor, context, u);里面很多都是围绕他来展开,到这个阶段我们再来回忆下jvm spec中对于mehod属性的描述
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
大家可以先思考思考,对指令的解析(待续),在解析完成,ASM把常量池,方法,Feild解析出来的字节码,封装在不同的Vistor实现类中。这边要说下,增加方法,只需要
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "test", "(I)String",
null, null);
mw.visitInsn(ILOAD);
mw.visitInsn(ISTORE);
mw.visitFieldInsn(GETSTATIC, "TestAsm", "analyze", "B");
mw.visitEnd();
这样ASM就会有个新的MethodVistor对象,在MethodVisitor中new 一个MethodWriter对象,
@Override
//指令类型 1
public void visitInsn(final int opcode) {
lastCodeOffset = code.length;
// adds the instruction to the bytecode of the method
code.putByte(opcode);
// update currentBlock
// Label currentBlock = this.currentBlock;
if (currentBlock != null) {
if (compute == FRAMES) {
currentBlock.frame.execute(opcode, 0, null, null);
} else {
// updates current and max stack sizes
int size = stackSize + Frame.SIZE[opcode];
if (size > maxStackSize) {
maxStackSize = size;
}
stackSize = size;
}
// if opcode == ATHROW or xRETURN, ends current block (no successor)
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
noSuccessor();
}
}
}
下面修改常量池的代码来了
@Override
//指令类型5
public void visitFieldInsn(final int opcode, final String owner,
final String name, final String desc) {
lastCodeOffset = code.length;
//这边修改了常量池
Item i = cw.newFieldItem(owner, name, desc);
// Label currentBlock = this.currentBlock;
if (currentBlock != null) {
if (compute == FRAMES) {
currentBlock.frame.execute(opcode, 0, cw, i);
} else {
int size;
// computes the stack size variation
char c = desc.charAt(0);
switch (opcode) {
case Opcodes.GETSTATIC:
size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
break;
case Opcodes.PUTSTATIC:
size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
break;
case Opcodes.GETFIELD:
size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
break;
// case Constants.PUTFIELD:
default:
size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
break;
}
// updates current and max stack sizes
if (size > maxStackSize) {
maxStackSize = size;
}
stackSize = size;
}
}
// adds the instruction to the bytecode of the method
code.put12(opcode, i.index);
}
/**
* Adds a field reference to the constant pool of the class being build.
* Does nothing if the constant pool already contains a similar item.
*
* @param owner
* the internal name of the field's owner class.
* @param name
* the field's name.
* @param desc
* the field's descriptor.
* @return a new or already existing field reference item.
*/
Item newFieldItem(final String owner, final String name, final String desc) {
key3.set(FIELD, owner, name, desc);
Item result = get(key3);
if (result == null) {
//真的很聪明,这样搞得代码!!!这边用HashMap来判断,数据是否存在,这样搞真的很棒,这边常量池的问题,就解决了
put122(FIELD, newClass(owner), newNameType(name, desc));
result = new Item(index++, key3);
put(result);
}
return result;
}
最后,把ASM解析 和修改的类,组装就可以了
/**
* Returns the bytecode of the class that was build with this class writer.
*
* @return the bytecode of the class that was build with this class writer.
*/
public byte[] toByteArray() {
if (index > 0xFFFF) {
throw new RuntimeException("Class file too large!");
}
// computes the real size of the bytecode of this class
int size = 24 + 2 * interfaceCount;
int nbFields = 0;
FieldWriter fb = firstField;
while (fb != null) {
++nbFields;
size += fb.getSize();
fb = (FieldWriter) fb.fv;
}
int nbMethods = 0;
MethodWriter mb = firstMethod;
while (mb != null) {
++nbMethods;
size += mb.getSize();
mb = (MethodWriter) mb.mv;
}
int attributeCount = 0;
if (bootstrapMethods != null) {
// we put it as first attribute in order to improve a bit
// ClassReader.copyBootstrapMethods
++attributeCount;
size += 8 + bootstrapMethods.length;
newUTF8("BootstrapMethods");
}
if (ClassReader.SIGNATURES && signature != 0) {
++attributeCount;
size += 8;
newUTF8("Signature");
}
if (sourceFile != 0) {
++attributeCount;
size += 8;
newUTF8("SourceFile");
}
if (sourceDebug != null) {
++attributeCount;
size += sourceDebug.length + 6;
newUTF8("SourceDebugExtension");
}
if (enclosingMethodOwner != 0) {
++attributeCount;
size += 10;
newUTF8("EnclosingMethod");
}
if ((access & Opcodes.ACC_DEPRECATED) != 0) {
++attributeCount;
size += 6;
newUTF8("Deprecated");
}
if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
if ((version & 0xFFFF) < Opcodes.V1_5
|| (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
++attributeCount;
size += 6;
newUTF8("Synthetic");
}
}
if (innerClasses != null) {
++attributeCount;
size += 8 + innerClasses.length;
newUTF8("InnerClasses");
}
if (ClassReader.ANNOTATIONS && anns != null) {
++attributeCount;
size += 8 + anns.getSize();
newUTF8("RuntimeVisibleAnnotations");
}
if (ClassReader.ANNOTATIONS && ianns != null) {
++attributeCount;
size += 8 + ianns.getSize();
newUTF8("RuntimeInvisibleAnnotations");
}
if (ClassReader.ANNOTATIONS && tanns != null) {
++attributeCount;
size += 8 + tanns.getSize();
newUTF8("RuntimeVisibleTypeAnnotations");
}
if (ClassReader.ANNOTATIONS && itanns != null) {
++attributeCount;
size += 8 + itanns.getSize();
newUTF8("RuntimeInvisibleTypeAnnotations");
}
if (attrs != null) {
attributeCount += attrs.getCount();
size += attrs.getSize(this, null, 0, -1, -1);
}
size += pool.length;
// allocates a byte vector of this size, in order to avoid unnecessary
// arraycopy operations in the ByteVector.enlarge() method
ByteVector out = new ByteVector(size);
out.putInt(0xCAFEBABE).putInt(version);
out.putShort(index).putByteArray(pool.data, 0, pool.length);
int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
| ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
out.putShort(access & ~mask).putShort(name).putShort(superName);
out.putShort(interfaceCount);
for (int i = 0; i < interfaceCount; ++i) {
out.putShort(interfaces[i]);
}
out.putShort(nbFields);
fb = firstField;
while (fb != null) {
fb.put(out);
fb = (FieldWriter) fb.fv;
}
out.putShort(nbMethods);
mb = firstMethod;
while (mb != null) {
mb.put(out);
mb = (MethodWriter) mb.mv;
}
out.putShort(attributeCount);
if (bootstrapMethods != null) {
out.putShort(newUTF8("BootstrapMethods"));
out.putInt(bootstrapMethods.length + 2).putShort(
bootstrapMethodsCount);
out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
}
if (ClassReader.SIGNATURES && signature != 0) {
out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
}
if (sourceFile != 0) {
out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
}
if (sourceDebug != null) {
int len = sourceDebug.length;
out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
out.putByteArray(sourceDebug.data, 0, len);
}
if (enclosingMethodOwner != 0) {
out.putShort(newUTF8("EnclosingMethod")).putInt(4);
out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
}
if ((access & Opcodes.ACC_DEPRECATED) != 0) {
out.putShort(newUTF8("Deprecated")).putInt(0);
}
if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
if ((version & 0xFFFF) < Opcodes.V1_5
|| (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
out.putShort(newUTF8("Synthetic")).putInt(0);
}
}
if (innerClasses != null) {
out.putShort(newUTF8("InnerClasses"));
out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
out.putByteArray(innerClasses.data, 0, innerClasses.length);
}
if (ClassReader.ANNOTATIONS && anns != null) {
out.putShort(newUTF8("RuntimeVisibleAnnotations"));
anns.put(out);
}
if (ClassReader.ANNOTATIONS && ianns != null) {
out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
ianns.put(out);
}
if (ClassReader.ANNOTATIONS && tanns != null) {
out.putShort(newUTF8("RuntimeVisibleTypeAnnotations"));
tanns.put(out);
}
if (ClassReader.ANNOTATIONS && itanns != null) {
out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations"));
itanns.put(out);
}
if (attrs != null) {
attrs.put(this, null, 0, -1, -1, out);
}
if (invalidFrames) {
anns = null;
ianns = null;
attrs = null;
innerClassesCount = 0;
innerClasses = null;
bootstrapMethodsCount = 0;
bootstrapMethods = null;
firstField = null;
lastField = null;
firstMethod = null;
lastMethod = null;
computeMaxs = false;
computeFrames = true;
invalidFrames = false;
new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);
return toByteArray();
}
return out.data;
}