1 Reply Latest reply on May 17, 2006 7:11 PM by yanic

    Instrumentation that uses a generated class

      I'm trying to instrument an existing class with code that uses a generated class. I've prepared a small example to demonstrate what I mean and show you where I got stuck.

      Assume the following class exists :

      public class InstrumentMe {
      
       public InstrumentMe() {
       super();
       }
      
       public void printHelloWorld() {
       System.out.println("HelloWorld");
       }
      }
      


      The main-method in the class below generates a new class 'DynamicallyGenerated' on-the-fly and tries to instrument InstrumentMe#printHelloWorld() with a simple statement 'System.out.println(DynamicallyGenerated.class);'

      import javassist.CannotCompileException;
      import javassist.ClassPool;
      import javassist.CtClass;
      import javassist.CtMethod;
      import javassist.CtNewClass;
      import javassist.NotFoundException;
      
      public final class AttemptGeneration {
      
       private AttemptGeneration() {
       super();
       }
      
       public static void main (String[] ignore) throws NotFoundException, CannotCompileException {
       ClassPool pool=ClassPool.getDefault();
      
       // step 1 : generate a new class
       String generatedName="DynamicallyGenerated";
       CtClass superclass=pool.get("java.lang.Object");
       CtNewClass generated = new CtNewClass(generatedName, pool, false, superclass);
       generated.toClass();
       // (*) see below : CtClass generatedClassAgain=pool.get(generatedName);
      
       // step 2 : try to load the generated class
       try {
       Class c = Class.forName(generatedName);
       System.out.println("class "+c.getName()+" loaded.");
       } catch (ClassNotFoundException e) {
       System.out.println("class "+generatedName+" not found");
       System.exit(1);
       }
      
       // step 3 : instrument an existing class to use the generated class
       CtClass instrumented = pool.get("InstrumentMe");
       CtMethod printHelloWorld = instrumented.getDeclaredMethod("printHelloWorld");
       String instrumentation = "System.out.println("+generatedName+".class);";
      
       // .. the line below causes a CannotCompileException
       printHelloWorld.insertAfter(instrumentation);
       }
      }
      


      The call to CtBehavior#insertAfter(String) in the last statement causes a CannotCompileException with the following stack trace :

      Exception in thread "main" javassist.CannotCompileException: [source error] no such class: DynamicallyGenerated
       at javassist.CtBehavior.insertAfter(CtBehavior.java:613)
       at javassist.CtBehavior.insertAfter(CtBehavior.java:538)
       at AttemptGeneration.main(AttemptGeneration.java:40)
      Caused by: compile error: no such class: DynamicallyGenerated
       at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:406)
       at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:382)
       at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:310)
       at javassist.compiler.MemberResolver.resolveJvmClassName(MemberResolver.java:450)
       at javassist.compiler.MemberCodeGen.resolveClassName(MemberCodeGen.java:1064)
       at javassist.compiler.CodeGen.atClassObject(CodeGen.java:1546)
       at javassist.compiler.CodeGen.atExpr(CodeGen.java:1441)
       at javassist.compiler.ast.Expr.accept(Expr.java:67)
       at javassist.compiler.JvstCodeGen.atMethodArgs(JvstCodeGen.java:357)
       at javassist.compiler.MemberCodeGen.atMethodCallCore(MemberCodeGen.java:486)
       at javassist.compiler.MemberCodeGen.atCallExpr(MemberCodeGen.java:454)
       at javassist.compiler.JvstCodeGen.atCallExpr(JvstCodeGen.java:243)
       at javassist.compiler.ast.CallExpr.accept(CallExpr.java:45)
       at javassist.compiler.CodeGen.atStmnt(CodeGen.java:331)
       at javassist.compiler.ast.Stmnt.accept(Stmnt.java:49)
       at javassist.compiler.Javac.compileStmnt(Javac.java:558)
       at javassist.CtBehavior.insertAfter(CtBehavior.java:581)
       ... 2 more
      


      Note that before the exception occurs, the output 'class DynamicallyGenerated loaded.' appears, which means that the generated class can be loaded by Class#forName(String).

      I've stepped through some of the Javassist classes involved and noticed that the ClassPool doesn't seem to know the generated class. For example, when the statement marked with (*) is uncommented, it causes a NotFoundException with the following stack trace :
      Exception in thread "main" javassist.NotFoundException: DynamicallyGenerated
       at javassist.ClassPool.get(ClassPool.java:389)
       at AttemptGeneration.main(AttemptGeneration.java:31)
      


      The fact that the ClassPool doesn't know the generated class is a bit surprising, as its documentation mentions "...ClassPool objects hold all the CtClasses that have been created so that the consistency among modified classes can be guaranteed...".

      The javassist tutorial states "...A ClassPool object is a container of CtClass objects. Once a CtClass object is created, it is recorded in a ClassPool for ever. This is because a compiler may need to access the CtClass object later when it compiles source code that refers to the class represented by that CtClass....


      So my question is why the above example doesn't work and how it can be made to work.

      Thanks in advance for any help!



        • 1. Re: Instrumentation that uses a generated class

          I've finally managed to instrument a class with code that uses a generated class (that is, without storing the generated class on disk).

          As I understand it at the moment : when an instrumented class is compiled, the compiler uses the classpool to find the information it needs on any class it encounters. The default classpool only looks for classfiles in certain (stored) URL-based locations as defined in class ClassPoolTail.

          The bytecode for a generated class isn't stored anywhere, which is why the classpool cannot find it.

          A possible solution simply stores the bytecode in memory and adds a 'memory-based' classpath to the classpool.

          For the interested reader, here's how :

          Create a class that represents a new kind of classpath for the class pool (it is based on ByteArrayClassPath from the rel 3.1 distribution but avoids a linear search)

          import java.io.ByteArrayInputStream;
          import java.io.IOException;
          import java.io.InputStream;
          import java.net.MalformedURLException;
          import java.net.URL;
          import java.util.HashMap;
          
          import javassist.CannotCompileException;
          import javassist.ClassPath;
          import javassist.CtNewClass;
          import javassist.NotFoundException;
          
          public final class ClassPathForGeneratedClasses implements ClassPath {
          
           public ClassPathForGeneratedClasses() {
           super();
           classes = new HashMap<String, InputStream>();
           }
          
           public void addGeneratedClass(CtNewClass generated) throws CannotCompileException {
           try {
           generated.stopPruning(true);
           ByteArrayInputStream source = new ByteArrayInputStream(generated.toBytecode());
           classes.put(generated.getName(), source);
           generated.stopPruning(false);
           } catch (IOException e) {
           // should not happen
           System.out.println("unexpected : " + e);
           System.exit(1);
           }
           }
          
           public void close() {
           this.classes.clear();
           }
          
           public URL find(String classname) {
           try {
           String urlString = "file:/ClassPathForGeneratedClasses/" + classname;
           URL result = (classes.containsKey(classname)) ? new URL(urlString) : null;
           return result;
           } catch (MalformedURLException e) {
           return null;
           }
           }
          
           public InputStream openClassfile(String classname) throws NotFoundException {
           if (!classes.containsKey(classname)) {
           return null;
           }
           return classes.get(classname);
           }
          
           private HashMap<String, InputStream> classes;
          }
          


          Some example code that generates a new class and uses it in the instrumentation code of another class :

          import javassist.CannotCompileException;
          import javassist.ClassPool;
          import javassist.CtClass;
          import javassist.CtMethod;
          import javassist.CtNewClass;
          import javassist.NotFoundException;
          
          public final class AttemptGeneration {
          
           private AttemptGeneration() {
           super();
           }
          
           public static void main(String[] ignore) throws NotFoundException, CannotCompileException {
          
           // step 1 : create a classpath for generated classes and add it to the pool
           ClassPool pool = ClassPool.getDefault();
           ClassPathForGeneratedClasses gcp = new ClassPathForGeneratedClasses();
           pool.insertClassPath(gcp);
          
           // step 2 : generate a new class
           String generatedName = "DynamicallyGenerated";
           CtClass superclass = pool.get("java.lang.Object");
           CtNewClass generated = new CtNewClass(generatedName, pool, false, superclass);
          
           // step 3 : add the generated class to the special classpath
           gcp.addGeneratedClass(generated);
          
           // step 4 : instrument an existing class to use the generated class
           CtClass instrumented = pool.get("InstrumentMe");
           CtMethod printHelloWorld = instrumented.getDeclaredMethod("printHelloWorld");
           String instrumentation = "System.out.println(" + generatedName + ".class);";
           printHelloWorld.insertAfter(instrumentation);
          
           // step 5 : load the generated and instrumented classes
           instrumented.toClass();
           generated.toClass();
          
           // step 6 : execute the instrumented method so we can check its output
           InstrumentMe im = new InstrumentMe();
           im.printHelloWorld();
           }
          }
          


          The resulting output shows that the instrumentation was a success :
          HelloWorld
          class DynamicallyGenerated