ASM

Frequently Asked Questions

Here are some frequently asked questions about ASM, gathered by Mark Proctor.

0. How do I start using ASM?

If you want to use ASM to generate classes from scratch, write a Java source file that is representative of the classes you want to generate, compile it(*), and then run the ASMifier on the compiled class to see the Java source code that generates this class with ASM. If you are using Eclipse it is even easier, thanks to the Bytecode Outline plugin.

If you want to use ASM to transform classes, write two Java source files - with and without the features that have to be added or removed, compile them(*), and then run the ASMifier on both of them. Then compare the results with some visual diff tool. If you are using Eclipse it is even easier, thanks to the compare tool of the Bytecode Outline plugin.

(*) Note that javac may produce different code for different -target, so you'll have to compile for your target environment, repeat that excercise for all required target's or use the earliest bytecode version if possible.

1. How do I remove a method/field?

Use a ClassVisitor and return nothing:

  public FieldVisitor visitField(String name, ...) {
    if (removeField(name)) {
      // Do nothing, in order to remove this field.
      return null;
    } else {
      // Make the next visitor visit this field, in order to keep it.
      return super.visitField(name, ...);
    }
  }

2. How do I replace a method/field? I end up with duplicated members!

You must either return the replacement method/field when you visit the original one using a ClassVisitor, or you must first remove the original method/field in the ClassVisitor (see "1. How do I remove a method/field?"), and then add the new method/field by calling a visit method on the ClassWriter.

3. How do I make ASM calculate visitMaxs for me?

When calling the constructor for ClassWriter use the COMPUTE_MAXS flag. You must also still include the visitMaxs method call, but the values you give are ignored, so visitMaxs(0,0) is fine.

4. Why do I get the [xxx] verifier error?

If the message given by the JVM class verifier does not help you, you can use the verifier provided by ASM. For example, if you use a wrong constant when making "return" on a method, or if you do not use the appropriate LOAD or STORE instruction, depending on the local variable type, the JVM class verifier gives a "Register x contains wrong type" or "Expecting to find x on stack" error which does not say anything about the instruction that caused the error. In this case you can use the class verifier provided by ASM:

java -cp "asm.jar;asm-tree.jar;asm-analysis.jar;asm-util.jar" org.objectweb.asm.util.CheckClassAdapter org/domain/package/YourClass.class

which produces outputs such as the following:

org.objectweb.asm.tree.analysis.AnalyzerException:
    Error at instruction 2: Argument 1: expected Ljava/lang/String;, but found I
        at org.objectweb.asm.tree.analysis.Analyzer.analyze(...)
        at org.objectweb.asm.util.CheckClassAdapter.verify(...)
        at org.objectweb.asm.util.CheckClassAdapter.main(...)
        ...
main([Ljava/lang/String;)V
00000 String .  :  :     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
00001 String .  : PrintStream  :     LDC 1234
00002 String .  : PrintStream I  :     INVOKEVIRTUAL java/io/PrintStream.println
    (Ljava/lang/String;)V
00003 ?     :     RETURN

This shows that the error comes from instruction 2 in method main. The instruction list shows that this instruction is INVOKEVIRTUAL. It also shows the types of the local variables and of the operand stack values just before this instruction will be executed (here local variable 0 contains a String, local variable 1 is not initialized, and the stack contains a PrintStream and an int - I stands for int, as in type descriptors). As explained in the error message, the println method called by INVOKEVIRTUAL expects a String as first argument, but the stack value corresponding to this argument is an int. Then either the INVOKEVIRTUAL instruction is wrong, or the instruction that pushed the integer is wrong.

If your class is so "corrupted" that you cannot read it with a ClassReader, try to generate it by using a CheckClassAdapter in front of a ClassWriter:

  ClassWriter classWriter = new ClassWriter(0);
  ClassVisitor classVisitor = new CheckClassAdapter(classWriter);
  // Generate your class here:
  classVisitor.visit(...);
  ...

You will probably get an exception which will indicate why your generated class is incorrect. For example, if you forget to call visit(...) (which can happen if you forget to call super.visit(...) in a class visitor), the generated class contains an invalid constant pool index, and ClassReader is unable to read it. If you generate your class by using a CheckClassAdapter, as above, you get an exception "Cannot visit member before visit has been called.", which shows that you forgot to call the visit method.

5. How do I add my bytecode class to the system class loader?

You must first have the permissions to do this, as defined in the policy file - there are no security restrictions by default for a standard java install. Then use ClassLoader.defineClass, via reflection to gain access to it (this is a protected method):

  private Class loadClass(byte[] b) {
    // Override defineClass (as it is protected) and define the class.
    Class clazz = null;
    try {
      ClassLoader loader = ClassLoader.getSystemClassLoader();
      Class cls = Class.forName("java.lang.ClassLoader");
      java.lang.reflect.Method method =
          cls.getDeclaredMethod(
              "defineClass", 
              new Class[] { String.class, byte[].class, int.class, int.class });

      // Protected method invocation.
      method.setAccessible(true);
      try {
        Object[] args = 
            new Object[] { className, b, new Integer(0), new Integer(b.length)};
        clazz = (Class) method.invoke(loader, args);
      } finally {
        method.setAccessible(false);
      }
    } catch (Exception e) {
      e.printStackTrace();
      System.exit(1);
    }
    return clazz;
  }

Alternatively you can create your own ClassLoader by extending the existing class loader (example needed here).

6. How do I rename my class?

It is not enough to rename just the class, you must also rename all the references to class members. The ClassRemapper in org.objectweb.asm.commons can do this for you.

7. How do method descriptors work?

To understand this best it's good to read the source code of Type.java. Here is a quick overview:

Examples:

8. How can ASM help me create my descriptor types?

Type.java provides the static method Type.getDescriptor, which takes a Class as a parameter.

Examples:

9. How do I generate setters and getters for my class?

Use the following code (this assumes that visitMaxs are computed by ASM - see "3. How do I make ASM calculate visitMaxs for me?"):

void createSetter(String propertyName, String type, Class c) {
  String methodName = "set" + propertyName.substring(0, 1).toUpperCase() 
      + propertyName.substring(1);
  MethodVisitor mv = 
      cw.visitMethod(ACC_PUBLIC, methodName, "(" + type + ")V", null, null);
  mv.visitVarInsn(ALOAD, 0);
  mv.visitVarInsn(Type.getType(c).getOpcode(ILOAD), 1);
  mv.visitFieldInsn(PUTFIELD, className, propertyName, type);
  mv.visitInsn(RETURN);
  mv.visitMaxs(0, 0);
}

void createGetter(String propertyName, String returnType, Class c) {
  String methodName = "get" + propertyName.substring(0, 1).toUpperCase() 
      + propertyName.substring(1);
  MethodVisitor mv = 
      cw.visitMethod(ACC_PUBLIC, methodName, "()" + returnType, null, null);
  mv.visitVarInsn(ALOAD, 0);
  mv.visitFieldInsn(GETFIELD, internalClassName, propertyName, returnType);
  mv.visitInsn(Type.getType(c).getOpcode(IRETURN));
  mv.visitMaxs(0, 0);
}

10. How do I get the bytecode of an existing class?

If you want the bytecode instructions themselves, use a Textifier. If you want the ASM code to generate these bytecode instructions, use an ASMifier. Both classes provide a "main" method to allow them to be called from the command line, passing your fully qualified class name as a parameter. Example:

java -classpath "asm.jar;asm-util.jar;yourjar.jar" org.objectweb.asm.util.Textifier org.domain.package.YourClass

or

java -classpath "asm.jar;asm-util.jar" org.objectweb.asm.util.ASMifier org/domain/package/YourClass.class

Another, much easier method, if you are using Eclipse, is to use the Bytecode Outline plugin.

11. How do I generate [some Java code] with ASM?

If you want to know how to generate a synchronized block, a try catch block, a finally statement, or any other Java construct, write the Java code you want to generate in a temporary class, compile it with javac, and then use the ASMifier to get the ASM code that will generate this class (see "10. How do I get the bytecode of an existing class?").

12. How does the [xxx] bytecode instruction work?

All the bytecode instructions are specified in chapter 6 of the Java Virtual Machine Specification.

13. Is ASM thread safe?

The Type and ClassReader classes are thread safe, i.e. several threads can use a single Type object or a single ClassReader object concurrently without problems. The ClassWriter and MethodWriter classes are not thread safe, i.e. a single class cannot be generated by several concurrent threads (but, of course, several threads can generate distinct classes concurrently, if each thread uses its own ClassWriter instance). In order to generate a single class by using several concurrent threads, one should use ClassVisitor and MethodVisitor instances that delegate to normal ClassWriter and MethodWriter instances, and whose methods are all synchronized.

More generally, ClassVisitor and MethodVisitor implementations, such as ClassWriter, do not have to be thread safe. However, non thread safe visitors can be made thread safe just by using a synchronizing class adapter in front of them.

14. What is the earliest JDK required to use ASM?

ASM requires a JDK 1.5 or above.

15. Is ASM backward compatible with older versions?

All ASM versions since ASM 4.0 are backward compatible with ASM 4.0 (but not with previous versions, which were not ensuring backward compatibility). We have tests using SigTest to check that new versions do not break backward compatibility, as well as a framework, explained in Chapters 5 and 10 of the User Guide, to introduce new features in a backward compatible way.