Changing return type of a method impacts calling methods
aarontull Mar 11, 2014 7:22 PMI am trying to copy and replace a method with a different return type but it seems like the only way I can successfully do it is to have the source code. If I replace the method then instrument the calling method to call the new method I get a VerifyError (Bad type on operand stack). Is there a way to only use the bytecode and not depend on the source code when rebuilding dependent methods?
I am including source code for my example below.
Functioning Example (with source code dependency)
public class OverrideTest {
    final ClassPool POOL = ClassPool.getDefault();
    class Foo {
        public Integer getFoo() { return new Integer(1); }
        public String doA() { return getFoo().toString(); }
    }
    class FooExt {
        public String getFoo() { return "A"; }
    }
    @Test
    public void d() throws Throwable {
        CtClass srcClass = POOL.getCtClass(Foo.class.getName());
        CtClass extClass = POOL.getCtClass(FooExt.class.getName());
        CtClass d = POOL.makeClass("Derp");
        CtClass STRING = POOL.get("java.lang.String");
        CtClass INT = POOL.get("java.lang.Integer");
        {
            CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));
            CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));
            CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);
            CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);
            d.addMethod(doA);
            d.addMethod(getFoo);
            doA.setBody(doA1, null);
            getFoo.setBody(getFoo1, null);
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
            d.removeMethod(getFoo);
            CtMethod getFooExt = new CtMethod(STRING, "getFoo", new CtClass[0], d);
            d.addMethod(getFooExt);
            CtMethod getFooExt1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));
            getFooExt.setBody(getFooExt1, null);
            doA.setBody("{ return getFoo().toString(); }");
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
        }
        {
            Class<?> c = d.toClass();
            Constructor<?> ctor = c.getConstructor();
            Object derp = ctor.newInstance();
            Method getFoo = derp.getClass().getMethod("getFoo");
            Method doA = derp.getClass().getMethod("doA");
            Object doResult = doA.invoke(derp);
            Object getResult = getFoo.invoke(derp);
            assertEquals("A", getResult);
            assertEquals("A", doResult);
        }
    }
}
Non-Functioning Example (VerifyError)
public class OverrideTest {
    final ClassPool POOL = ClassPool.getDefault();
    class Foo {
        public Integer getFoo() { return new Integer(1); }
        public String doA() { return getFoo().toString(); }
    }
    class FooExt {
        public String getFoo() { return "A"; }
    }
    @Test
    public void d() throws Throwable {
        CtClass srcClass = POOL.getCtClass(Foo.class.getName());
        CtClass extClass = POOL.getCtClass(FooExt.class.getName());
        CtClass d = POOL.makeClass("Derp");
        CtClass STRING = POOL.get("java.lang.String");
        CtClass INT = POOL.get("java.lang.Integer");
        {
            CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));
            CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));
            CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);
            CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);
            d.addMethod(doA);
            d.addMethod(getFoo);
            doA.setBody(doA1, null);
            getFoo.setBody(getFoo1, null);
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
            CtMethod tempMethod = new CtMethod(getFoo.getReturnType(), "tempFoo", new CtClass[0], d);
            d.addMethod(tempMethod);
            doA.instrument(new MethodReplacer(getFoo, tempMethod));
            d.removeMethod(getFoo);
            CtMethod getFooExt = new CtMethod(STRING, "getFoo", new CtClass[0], d);
            d.addMethod(getFooExt);
            CtMethod getFooExt1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));
            getFooExt.setBody(getFooExt1, null);
            doA.instrument(new MethodReplacer(tempMethod, getFooExt));
            d.removeMethod(tempMethod);
            d.removeMethod(doA);
            CtMethod doA2 = new CtMethod(STRING, "doA", new CtClass[0], d);
            d.addMethod(doA2);
            doA2.setBody(doA, null);
            d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
        }
        {
            Class<?> c = d.toClass();
            Constructor<?> ctor = c.getConstructor();
            Object derp = ctor.newInstance();
            Method getFoo = derp.getClass().getMethod("getFoo");
            Method doA = derp.getClass().getMethod("doA");
            Object doResult = doA.invoke(derp);
            Object getResult = getFoo.invoke(derp);
            assertEquals("A", getResult);
            assertEquals("A", doResult);
        }
    }
    class MethodReplacer extends ExprEditor {
        private CtMethod replacedMethod;
        private CtMethod replacement;
        MethodReplacer(CtMethod replacedMethod, CtMethod replacement) {
            this.replacedMethod = replacedMethod;
            this.replacement = replacement;
        }
        @Override
        public void edit(MethodCall mcall) throws CannotCompileException {
            CtClass declaringClass = replacedMethod.getDeclaringClass();
            try {
                CtMethod m = mcall.getMethod();
                if (declaringClass.equals(m.getDeclaringClass()) && m.equals(replacedMethod))
                    mcall.replace("$_ = " + replacement.getName()+"($$);");
            } catch (NotFoundException e) {
                throw new RuntimeException("Unable to instrument a dependent method call to " + replacedMethod.getName(), e);
            }
        }
    }
}