10

I am new to ASM framework. I have been working around this ASM framework for a week. I saw tutorials in net regarding parsing a class and Generating a .class file from scratch. But am unable to follow how to modify a existing class in ASM.

I am unable to follow the flow of execution between the ClassVisitor, ClassWriter and ClassReader.

Kindly solve my issue by giving me a ASM example for the following code.

public class ClassName {
  public void showOne() {
    System.out.println("Show One Method");
  }

  public static void main(String[] args) {
    ClassName c = new ClassName();
    c.showOne();
  }
}

The above class should be modified as:

public class ClassName {
  public void showOne() {
    System.out.println("Show One Method");
  }

  public void showTwo() { // <- Newly added method
    System.out.println("Show Two Method");
  }

  public static void main(String[] args) {
    ClassName c = new ClassName();
    c.showOne();
    c.showTwo(); // <- Newly inserted method call
  }
}

What should be the ASM code to modify it?

I used the ASMifier tool to generate the code. But I don't know where to apply it.

Ragunath Jawahar
  • 19,513
  • 22
  • 110
  • 155
Narayana
  • 363
  • 3
  • 14

1 Answers1

14

Your requirements are a bit underspecified. Below is an example program which uses ASM’s visitor API for transforming a class assumed to have the structure of your question to the resulting class. I added a convenience method taking a byte array and returning a byte array. Such a method can be used in both cases, a static transformation applied to class files on disk as well as in an Instrumentation agent.

When combining a ClassWriter with a ClassVisitor passed to a ClassReader as below, it will automatically replicate every feature of the source class so you have to override only these methods where you want to apply changes.

Here, visitMethod is overridden to intercept when encountering the main method to modify it and visitEnd is overridden to append the entirely new showTwo method. The MainTransformer will intercept RETURN instructions (there should be only one in your example) to insert the call to showTwo before it.

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

public class MyTransformer extends ClassVisitor {

  public static byte[] transform(byte[] b) {
    final ClassReader classReader = new ClassReader(b);
    final ClassWriter cw = new ClassWriter(classReader,
      ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
    classReader.accept(new MyTransformer(cw), ClassReader.EXPAND_FRAMES);
    return cw.toByteArray();
  }

  public MyTransformer(ClassVisitor cv) {
    super(Opcodes.ASM5, cv);
  }
  @Override
  public MethodVisitor visitMethod(int access, String name, String desc,
      String signature, String[] exceptions) {

    MethodVisitor v=super.visitMethod(access, name, desc, signature, exceptions);
    if(name.equals("main") && desc.equals("([Ljava/lang/String;)V"))
      v=new MainTransformer(v, access, name, desc, signature, exceptions);
    return v;
  }
  @Override
  public void visitEnd() {
    appendShowTwo();
    super.visitEnd();
  }
  private void appendShowTwo() {
    final MethodVisitor defVisitor = super.visitMethod(
      Opcodes.ACC_PUBLIC, "showTwo", "()V", null, null);
    defVisitor.visitCode();
    defVisitor.visitFieldInsn(Opcodes.GETSTATIC,
      "java/lang/System", "out", "Ljava/io/PrintStream;");
    defVisitor.visitLdcInsn("Show Two Method");
    defVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
      "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    defVisitor.visitInsn(Opcodes.RETURN);
    defVisitor.visitMaxs(0, 0);
    defVisitor.visitEnd();
  }
  class MainTransformer extends GeneratorAdapter
  {
    MainTransformer(MethodVisitor delegate, int access, String name, String desc,
        String signature, String[] exceptions) {
      super(Opcodes.ASM5, delegate, access, name, desc);
    }
    @Override
    public void visitInsn(int opcode) {
      if(opcode==Opcodes.RETURN) {
        // before return insert c.showTwo();
        super.visitVarInsn(Opcodes.ALOAD, 1); // variable c
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
            "ClassName", "showTwo", "()V", false);
      }
      super.visitInsn(opcode);
    }
  }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks. But can please explain the flow of this program. Where is the main method to execute this ASM program. how it ll generate the new class. can u please give the full code sir!! – Narayana Aug 29 '14 at 12:58
  • 3
    There is no `main` method as you didn’t specify in your question what that `main` method should do. I already addressed this in the beginning of my answer. I provided a `static` method accepting a byte array and providing a byte array. Where you get that array from and what you will do with the result, that depends on what you really wanna do. I can’t read your mind. – Holger Aug 29 '14 at 13:10
  • Thanks for your kind reply. what i want to do is: read this class file and perform these changes and generate another class file with these changes made. can u plz help me sir! – Narayana Aug 29 '14 at 13:13
  • 1
    So [reading a file into a byte array](http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllBytes(java.nio.file.Path)) and [writing a byte array into a file](http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#write(java.nio.file.Path,%20byte[],%20java.nio.file.OpenOption...)) is no magic. That works for class files like for every other file. At which point do you have difficulties? – Holger Aug 29 '14 at 13:18
  • the problem is I dont understand how to combine ClassVisitor ClassReader and ClassWriter. plz explain the flow of it. can u plz give the code in ASM 4.0?? – Narayana Aug 29 '14 at 13:24
  • 2
    The example code shows “how to combine ClassVisitor ClassReader and ClassWriter”. If you need ASM4, just replace `Opcodes.ASM5` with `Opcodes.ASM4` in the example, there are no differences, as far as I know. I don’t know how I should explain “the flow of it”. Do you know [how the visitor pattern works](http://en.wikipedia.org/wiki/Visitor_pattern)? – Holger Aug 29 '14 at 13:34
  • I am just a beginer sir. and i dont know much about visitor pattern. I ll work with this and get back to u sir. thanks for ur efforts. – Narayana Aug 29 '14 at 13:40
  • Hi.. thanks!! I have one more doubt! suppose if i have a statement in a method as "int average=sum()/5;" I need to change that to "int average=20/5;" is that possible? – Narayana Sep 12 '14 at 08:53
  • 1
    Sure. You have to override `visitMethodInsn` and check whether the invoked method is your `"sum"` method. If so, call `super.visitLdcInsn(20);` in order to replace it, call super.visitMethodInsn` otherwise. – Holger Sep 12 '14 at 09:10
  • Sir!! can u please specify the code to replace a function call. Am not getting which method to call and override. It ll b of grt help sir! – Narayana Sep 17 '14 at 10:29
  • 1
    @Narayana: the comments are not meant to ask dozens of new questions. If you have a new question, [open a new question](http://stackoverflow.com/questions/ask). – Holger Sep 17 '14 at 11:39