/*
A ClassVisitor that generates a corresponding ClassFile structure, as defined in the Java Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from scratch", or with one or more ClassReader and adapter ClassVisitor to generate a modified class from one or more existing Java classes.
*/
public class ClassWriter extends ClassVisitor
// A node that represents a class.
public class ClassNode extends ClassVisitor
ClassVisitor定义了两个字段api和cv
public abstract class ClassVisitor {
/**
* The ASM API version implemented by this visitor. The value of this field must be one of Opcodes
*/
protected final int api;
/** The class visitor to which this visitor must delegate method calls. May be null. */
protected ClassVisitor cv;
public ClassVisitor(final int api, final ClassVisitor classVisitor) {
this.api = api;
this.cv = classVisitor;
}
}
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];
}
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
// method的方法体由Code属性表示
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];
}
// u? 表示占用?个字节
public void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces)
version – the class version
access – the class's access flags
name – the internal name of the class
signature – the signature of this class. May be null if the class is not a generic one, and does not extend or implement generic classes or interfaces
superName – the internal of name of the super class
interfaces – the internal names of the class's interfaces
public FieldVisitor visitField(
final int access,
final String name,
final String descriptor,
final String signature,
final Object value)
access – the field's access flags
name – the field's name
descriptor – the field's descriptor
signature – the field's signature. May be null if the field's type does not use generic types
value – the field's initial value
public MethodVisitor visitMethod(
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions)
access – the method's access flags
name – the method's name
descriptor – the method's descriptor
signature – the method's signature. May be null if the method parameters, return type and exceptions do not use generic types
exceptions – the internal names of the method's exception classes
/*
Visits the end of the class. This method, which is the last one to be called, is used to inform the visitor that all the fields and methods of the class have been visited.
*/
public void visitEnd()
这四个方法内部都是调用的cv.visitXxx()
这些visitXxx方法是有调用顺序的
/*
A visitor to visit a Java class. The methods of this class must be called in the following order:
visit
[visitSource][visitModule][visitNestHost][visitOuterClass]
(
visitAnnotation|
visitTypeAnnotation|
visitAttribute
)*
(
visitNestMember|
[*visitPermittedSubclass]|
visitInnerClass|
visitRecordComponent|
visitField|visitMethod
)*
visitEnd
*/
public abstract class ClassVisitor
[]表示最多调用一次
*表示调用0次或多次
()和|表示可以任选一个方法,不分先后顺序
由于我们只关注四个visitXxx方法,上面的调用可以简化为
visit
(
visitField |
visitMethod
)*
visitEnd
ClassWriter
/*
A ClassVisitor that generates a corresponding ClassFile structure, as defined in the Java Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from scratch", or with one or more ClassReader and adapter ClassVisitor to generate a modified class from one or more existing Java classes.
*/
public class ClassWriter extends ClassVisitor{
public static final int COMPUTE_MAXS = 1;
public static final int COMPUTE_FRAMES = 2;
// The minor_version and major_version fields of the JVMS ClassFile structure.
private int version;
// The symbol table for this class (contains the constant_pool and the BootstrapMethods)
private final SymbolTable symbolTable;
// The access_flags field of the JVMS ClassFile structure
private int accessFlags;
// The this_class field of the JVMS ClassFile structure
private int thisClass;
// The super_class field of the JVMS ClassFile structure
private int superClass;
// The interface_count field of the JVMS ClassFile structure
private int interfaceCount;
// The 'interfaces' array of the JVMS ClassFile structure
private int[] interfaces;
/*
* The fields of this class, stored in a linked list of FieldWriter linked via their
* FieldWriter#fv field. This field stores the first element of this list.
*/
private FieldWriter firstField;
/**
* The fields of this class, stored in a linked list of FieldWriter linked via their
* FieldWriter#fv field. This field stores the last element of this list.
*/
private FieldWriter lastField;
/**
* The methods of this class, stored in a linked list of MethodWriter linked via their
* MethodWriter#mv field. This field stores the first element of this list.
*/
private MethodWriter firstMethod;
/**
* The methods of this class, stored in a linked list of MethodWriter linked via their
* MethodWriter#mv field. This field stores the last element of this list.
*/
private MethodWriter lastMethod;
// The number_of_classes field of the InnerClasses attribute
private int numberOfInnerClasses;
// The 'classes' array of the InnerClasses attribute
private ByteVector innerClasses;
}
COMPUTE_MAXS: A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. If this flag is set, then the arguments of the MethodVisitor.visitMaxs method of the MethodVisitor returned by the visitMethod method will be ignored, and computed automatically from the signature and the bytecode of each method.
COMPUTE_FRAMES: A flag to automatically compute the stack map frames of methods from scratch. If this flag is set, then the calls to the MethodVisitor.visitFrame method are ignored, and the stack map frames are recomputed from the methods bytecode. The arguments of the MethodVisitor.visitMaxs method are also ignored and recomputed from the bytecode. In other words, COMPUTE_FRAMES implies COMPUTE_MAXS.
COMPUTE_FRAMES包含了COMPUTE_MAXS的功能,不仅计算max stack size和max local variables,还计算stack map frames,所以一般都使用COMPUTE_FRAMES
注意:虽然这个字段能够自动计算,但代码中仍要调用visitMaxs,否则会报错
使用ClassWriter创建一个字节码文件,分为三步:
创建ClassWriter对象
调用ClassWriter#visitXxx方法
调用ClassWriter#toByteArray方法
Best Practice
尝试生成如下类
package sample;
public class HelloWorld {
public static final int NUM = 17;
public String name;
public HelloWorld() {
}
public int hi(Object a) {
return 6;
}
}
package demo;
import com.sun.org.apache.xml.internal.security.utils.JavaUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
public class Test {
public static void main(String[] args) throws Exception {
JavaUtils.writeBytesToFilename("HelloWorld.class", dump());
}
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_8, // version Java8
ACC_PUBLIC, // access flag
"sample/HelloWorld", // name - Internal Name
null, // signature
"java/lang/Object", // superName
null); // interfaces
{
FieldVisitor fv1 = cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, // access flag
"NUM", // name
"I", // descriptor
null, // signature
17); // value , could be null
fv1.visitEnd();
}
{
FieldVisitor fv2 = cw.visitField(ACC_PUBLIC, "name", "Ljava/lang/String;", null, null);
fv2.visitEnd();
}
{
MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv1.visitEnd();
}
{
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "hi", "(Ljava/lang/Object;)I", null, null);
mv2.visitLdcInsn(6);
mv2.visitInsn(IRETURN);
mv2.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
注意这里visit的第三个参数name是Internal Name的格式,Java源码中使用类名的形式是Fully Qualified Class Name,如java.lang.String,编译成class文件后类名的格式会变成Internal Name,.变成/,如java/lang/String
// A visitor to visit a Java field.
public abstract class FieldVisitor {
protected final int api;
protected FieldVisitor fv;
public FieldVisitor(final int api) {
this(api, null);
}
}
// A visitor to visit a Java method
public abstract class MethodVisitor {
protected final int api;
protected MethodVisitor mv;
public MethodVisitor(final int api) {
this(api, null);
}
}
package demo;
public class Dog {
public String name;
public int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
生成如下类:
package sample;
import demo.Dog;
public class HellWorld {
public static Dog test() {
return new Dog("taco", 8);
}
}
/*
A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions, and for try catch blocks. A label designates the instruction that is just after.
*/
public class Label {
// The offset of this label in the bytecode of its method
int bytecodeOffset;
}
Label类的bytecodeOffset即对应上面的相对偏移量
通过调用MethodVisitor#visitLabel(Label)来标记跳转目标
Best Practice
If
package example;
public class HellWorld {
public void test(boolean flag) {
if (flag) {
System.out.println("value is true");
} else {
System.out.println("value is false");
}
}
}
package example;
public class HellWorld {
public String test(int var1) {
switch (var1) {
case 0:
return "-1";
case 1:
return "1";
default:
return "0";
}
}
}
看一眼字节码
这里用到了lookupswitch指令,即将栈顶元素和查找表比较,根据匹配值跳转到不同的语句。
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)Ljava/lang/String;", null, null);
Label caseLabel1 = new Label();
Label caseLabel2 = new Label();
Label defaultLabel = new Label();
Label returnLabel = new Label();
mv2.visitCode();
mv2.visitVarInsn(ILOAD, 1);
// mv2.visitTableSwitchInsn(0, 1, defaultLabel, new Label[]{caseLabel1, caseLabel2});
mv2.visitLookupSwitchInsn(defaultLabel, new int[]{0, 1}, new Label[]{caseLabel1, caseLabel2});
// case1
mv2.visitLabel(caseLabel1);
mv2.visitLdcInsn("-1");
mv2.visitInsn(ARETURN);
// case2
mv2.visitLabel(caseLabel2);
mv2.visitLdcInsn("1");
mv2.visitInsn(ARETURN);
// default
mv2.visitLabel(defaultLabel);
mv2.visitLdcInsn("0");
mv2.visitInsn(ARETURN);
mv2.visitMaxs(0, 0);
mv2.visitEnd();
/**
* Visits a LOOKUPSWITCH instruction.
*
* @param dflt beginning of the default handler block.
* @param keys the values of the keys.
* @param labels beginnings of the handler blocks. labels[i] is the beginning of * the handler block for the keys[i] key.
*/
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels)
/**
* Visits a TABLESWITCH instruction.
*
* @param min the minimum key value.
* @param max the maximum key value.
* @param dflt beginning of the default handler block.
* @param labels beginnings of the handler blocks. labels[i] is the beginning of * the handler block for the min + i key.
*/
public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels)
/**
* Visits a try catch block.
*
* @param start the beginning of the exception handler's scope (inclusive).
* @param end the end of the exception handler's scope (exclusive).
* @param handler the beginning of the exception handler's code.
* @param type the internal name of the type of exceptions handled by the handler, * or null to catch any exceptions (for "finally" blocks).
*/
public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type)