0 Replies Latest reply on Mar 11, 2014 7:22 PM by aarontull

    Changing return type of a method impacts calling methods

    aarontull

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