Search code examples
javajavassist

javassist NoSuchFieldError after changing the type of a field


I'm currently messing around with javassist and stumpled across this problem. I'm trying to change a field type using javassist. However, when the code is trying to access this field after i changed it, the result is a NoSuchFieldException.

So here's the class i'm trying to change:

public class AlterMe {
    private ClassA someField;
    public void doSomething() {
        someField.doSomething(Object someArg);
    }
}

And here's the part of my ClassLoader doing the actual change:

//...Left out all the boilerplate code for testing if its the right class
Class ctClass = ClassPool.getDefault().get(name);
ctClass.getField("SomeField").setType(ClassPool.getDefault().get("the.replacement.ClassB");
// I also do the instantiation inside the constructors
for (CtConstructor constructor : ctClass.getConstructors()) {
    constructor.insertBefore("someField = new the.replacement.ClassB();");
}

I also tried several other ways of changing the field type. For example I tried removing the field and adding one by the same name or redirecting the calls to someField to someFieldNew which I added before using the right type. So when I run doSomething() now, I get this exception:

Exception in thread "main" java.lang.NoSuchFieldError: someField

For completeness, I'm using source level 1.7 with javassist version 3.21.0-GA.

I hope anybody can help with this, because I sure am stuck here.


Solution

  • Ok, here's the solution I finally found. It's not nice but it works. Instead of replacing the type of

    someField
    

    I added a field with the correct type and used an ExprEditor to change all the method calls. Here's the code:

    CtField field = new CtField(ClassPool.getDefault().get("the.replacement.classB"), "classB", ctClass);
    ctClass.addField(field, CtField.Initializer.byExpr("new the.replacement.classB()"));
    for (CtMethod method : ctClass.getMethods()) {
        method.instrument(new ExprEditor() {
            public void edit(MethodCall methodCall) throws CannotCompileException {
               if (methodCall.getMethodName().equals("doSomething") && methodCall.getClassName().getClassName().equals("the.original.ClassA")) {
                   methodCall.replace("classB.doSomething($1)");
               }
          }
    }
    

    The only things I noticed was that overloading the method won't work. Let's say ClassB has methods

     doSomething(Object)
     doSomething(Sting)
     doSomething(int)
    

    it will always call the Object method. But thats something i can live with.

    If anybody knows a better solution I'd still like to see it.