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); } } } }