- Class ClassReader
- Field Summary
- Constructor Summary
- Method Summary
- Methods inherited from class java.lang.Object
- Field Details
- SKIP_CODE
- SKIP_DEBUG
- SKIP_FRAMES
- EXPAND_FRAMES
- b
- header
- Constructor Details
- ClassReader
- ClassReader
- ClassReader
- ClassReader
- Method Details
- getAccess
- getClassName
- getSuperName
- getInterfaces
- accept
- accept
- readBytecodeInstructionOffset
- readLabel
- getItemCount
- getItem
- getMaxStringLength
- readByte
- readUnsignedShort
- readShort
- readInt
- readLong
- readUTF8
- readClass
- readModule
- readPackage
- readConst
- Manipulating Java Class Files with ASM 4 – Part Two: Tree API
Class ClassReader
A parser to make a ClassVisitor visit a ClassFile structure, as defined in the Java Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the appropriate visit methods of a given ClassVisitor for each field, method and bytecode instruction encountered.
Field Summary
A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, LocalVariableTypeTable, LineNumberTable and MethodParameters attributes.
Constructor Summary
Method Summary
Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this ClassReader .
Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this ClassReader .
Returns a conservative estimate of the maximum length of the strings contained in the class’s constant pool table.
Methods inherited from class java.lang.Object
Field Details
SKIP_CODE
A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed nor visited.
SKIP_DEBUG
SKIP_FRAMES
A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes are neither parsed nor visited (i.e. MethodVisitor.visitFrame(int, int, java.lang.Object[], int, java.lang.Object[]) is not called). This flag is useful when the ClassWriter.COMPUTE_FRAMES option is used: it avoids visiting frames that will be ignored and recomputed from scratch.
EXPAND_FRAMES
A flag to expand the stack map frames. By default stack map frames are visited in their original format (i.e. «expanded» for classes whose version is less than V1_6, and «compressed» for the other classes). If this flag is set, stack map frames are always visited in expanded format (this option adds a decompression/compression step in ClassReader and ClassWriter which degrades performance quite a lot).
b
header
Constructor Details
ClassReader
ClassReader
ClassReader
ClassReader
Method Details
getAccess
Returns the class’s access flags (see Opcodes ). This value may not reflect Deprecated and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes.
getClassName
getSuperName
Returns the internal name of the super class (see Type.getInternalName() ). For interfaces, the super class is Object .
getInterfaces
accept
Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this ClassReader .
accept
Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this ClassReader .
readBytecodeInstructionOffset
Handles the bytecode offset of the next instruction to be visited in accept(ClassVisitor,int) . This method is called just before the instruction and before its associated label and stack map frame, if any. The default implementation of this method does nothing. Subclasses can override this method to store the argument in a mutable field, for instance, so that MethodVisitor instances can get the bytecode offset of each visited instruction (if so, the usual concurrency issues related to mutable data should be addressed).
readLabel
Returns the label corresponding to the given bytecode offset. The default implementation of this method creates a label for the given offset if it has not been already created.
getItemCount
getItem
Returns the start offset in this ClassReader of a JVMS ‘cp_info’ structure (i.e. a constant pool entry), plus one. This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
getMaxStringLength
Returns a conservative estimate of the maximum length of the strings contained in the class’s constant pool table.
readByte
Reads a byte value in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readUnsignedShort
Reads an unsigned short value in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readShort
Reads a signed short value in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readInt
Reads a signed int value in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readLong
Reads a signed long value in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readUTF8
Reads a CONSTANT_Utf8 constant pool entry in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readClass
Reads a CONSTANT_Class constant pool entry in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readModule
Reads a CONSTANT_Module constant pool entry in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readPackage
Reads a CONSTANT_Package constant pool entry in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
readConst
Reads a numeric or string constant pool entry in this ClassReader . This method is intended for Attribute sub classes, and is normally not needed by class generators or adapters.
Manipulating Java Class Files with ASM 4 – Part Two: Tree API
What is ASM tree API: ASM Tree API is the part of ASM that lets you create/modify the class in memory. The class is viewed as a tree of information. Like the whole class is an instance of ClassNode, which contain a list of FieldNode objects, a list of MethodNode objects etc. This article assumes that the reader has already read the first part here.
A simple class through tree API: Let’s use tree API to create our first class. Again I am going to jump right into a code example, because there is nothing better than a code example. The generated class has a main method that prints “Hello World!”.
TreeAPIDemo.java
package com.geekyarticles.asm; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; public class TreeAPIDemo < public static void main(String [] args) throws Exception< ClassNode classNode=new ClassNode(4);//4 is just the API version number //These properties of the classNode must be set classNode.version=Opcodes.V1_6;//The generated class will only run on JRE 1.6 or above classNode.access=Opcodes.ACC_PUBLIC; classNode.signature="Lcom/geekyarticles/asm/Generated;"; classNode.name="com/geekyarticles/asm/Generated"; classNode.superName="java/lang/Object"; //Create a method MethodNode mainMethod=new MethodNode(4,Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,"main", "([Ljava/lang/String;)V",null, null); mainMethod.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); mainMethod.instructions.add(new LdcInsnNode("Hello World!")); mainMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); mainMethod.instructions.add(new InsnNode(Opcodes.RETURN)); //Add the method to the classNode classNode.methods.add(mainMethod); //Write the class ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); classNode.accept(cw); //Dump the class in a file File outDir=new File("out/com/geekyarticles/asm"); outDir.mkdirs(); DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"Generated.class"))); dout.write(cw.toByteArray()); dout.flush(); dout.close(); >>
As you can see, the code is very simple. A primary advantage over BCEL is that unlike BCEL, ASM does not require you to add every constant explicitly to the constant pool. Instead, ASM takes care of the constant pool itself.
Reading a class file: A ClassNode is a ClassVisitor. So, reading a class for use in tree API is as simple as creating a ClassReader object and using it to read a class file, while passing the ClassNode object in its accept method as a parameter. Once this is done, the ClassNode passed is fully initalized with all the information present in the class. In the following example, we will print all the methods in the class.
TreeAPIClassReaderDemo.java
package com.geekyarticles.asm; import java.io.FileInputStream; import java.io.InputStream; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; public class TreeAPIClassReaderDemo < public static void main(String[] args) throws Exception< InputStream in=new FileInputStream("out/com/geekyarticles/asm/Generated.class"); ClassReader cr=new ClassReader(in); ClassNode classNode=new ClassNode(); //ClassNode is a ClassVisitor cr.accept(classNode, 0); //Let's move through all the methods for(MethodNode methodNodeclassNode.methods)< System.out.println(methodNode.name+" "+methodNode.desc); >> >
Modifying a class file: Modifying a class file is a combination of the above two procedures. We first read the class in the usual way, make necessary changes to the data, and then write it back to a file. The following program implements an automatic injection of some logging code. Currently our Logger class only prints to the standard output. Every method annotated with @Loggable will be logged when they begin and when the return. In this we do not log the throw-exception. However that can also be implemented in the same manner by checking opcode ATHROW.
LoggingInsertion.java
package com.geekyarticles.asm; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Iterator; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; public class LoggingInsertion < public static void main(String[] args) throws Exception< InputStream in=LoggingInsertion.class.getResourceAsStream("/com/geekyarticles/asm/LoggingTest.class"); ClassReader cr=new ClassReader(in); ClassNode classNode=new ClassNode(); cr.accept(classNode, 0); //Let's move through all the methods for(MethodNode methodNodeclassNode.methods)< System.out.println(methodNode.name+" "+methodNode.desc); boolean hasAnnotation=false; if(methodNode.visibleAnnotations!=null)< for(AnnotationNode annotationNodemethodNode.visibleAnnotations)< if(annotationNode.desc.equals("Lcom/geekyarticles/asm/Loggable;"))< hasAnnotation=true; break; >> > if(hasAnnotation) < //Lets insert the begin logger InsnList beginList=new InsnList(); beginList.add(new LdcInsnNode(methodNode.name)); beginList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodStart", "(Ljava/lang/String;)V")); IteratorinsnNodes=methodNode.instructions.iterator(); while(insnNodes.hasNext()) < System.out.println(insnNodes.next().getOpcode()); >methodNode.instructions.insert(beginList); System.out.println(methodNode.instructions); //A method can have multiple places for return //All of them must be handled. insnNodes=methodNode.instructions.iterator(); while(insnNodes.hasNext()) < AbstractInsnNode insn=insnNodes.next(); System.out.println(insn.getOpcode()); if(insn.getOpcode()==Opcodes.IRETURN ||insn.getOpcode()==Opcodes.RETURN ||insn.getOpcode()==Opcodes.ARETURN ||insn.getOpcode()==Opcodes.LRETURN ||insn.getOpcode()==Opcodes.DRETURN)< InsnList endList=new InsnList(); endList.add(new LdcInsnNode(methodNode.name)); endList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodReturn", "(Ljava/lang/String;)V")); methodNode.instructions.insertBefore(insn, endList); >> > > //We are done now. so dump the class ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); classNode.accept(cw); File outDir=new File("out/com/geekyarticles/asm"); outDir.mkdirs(); DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"LoggingTest.class"))); dout.write(cw.toByteArray()); dout.flush(); dout.close(); > >
LoggingTest.java
package com.geekyarticles.asm; public class LoggingTest < public static void run1()< System.out.println("run 1"); >@Loggable public static void run2() < System.out.println("run 2"); >@Loggable public static void main(String [] args) < run1(); run2(); >>
package com.geekyarticles.asm; public class Logger < public static void logMethodStart(String methodName)< System.out.println("Starting method: "+methodName); >public static void logMethodReturn(String methodName) < System.out.println("Ending method: "+methodName); >>
Loggable.java
package com.geekyarticles.asm; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Loggable
If you run this program, the generated file will have a dependency on the class Logger. Manually copy the Logger class to the correct package in the out directory. If you run the generated class (which is a modified version of LoggingTest class), the following would be the output.
bash-4.1$ java com.geekyarticles.asm.LoggingTest Starting method: main run 1 Starting method: run2 run 2 Ending method: run2 Ending method: main
Note that unlike normal Lists, an InsnList object can be modified while we iterate over it. Any changes are immidiately reflected. So, if some instructions are inserted after the current position, that will also be iterated over.
Reference: Manipulating Java Class Files with ASM 4 – Part Two: Tree API from our JCG partner Debasish Ray Chawdhuri at the Geeky Articles blog.