5 Replies Latest reply on Jun 8, 2004 3:09 AM by Shigeru Chiba

    nested method calls

    lucentum Newbie

      Hello

      I want to instrument method calls. My instrumentation replaces a method call by an equivalent method call of a similar class. The class Similar, has a static void method named "method", which takes as parameter the name of the original called method and its arguments, executes a "similar" call. Then the return value is retrieved with a subsequent call to Similar.getReturnValue

      public void edit(MethodCall m) throws CannotCompileException {
      String replacingBlock = new String();
      replacingBlock +=
      "{" +
      "$_=Similar.method("\"+m.getMethodName()\"" +",$args);" +
      "$_=Similar.getReturnValue();" +
      "}";
      m.replace(replacingBlock);
      }

      This works fine for simple calls. However, when there is a nested call (i.e., a parameter to a method is another method call), a problem arises. Consider the simple code fragment:
      bytestream.write(myfile.toBytes());

      then I get the following error:
      javassist.CannotCompileException: [source error] incompatible type for =

      What can I do to let javassist know that I don't want to assign the return value to a variable all the time. In this case, the return value of the nested call must be passed directly as a parameter to the outer method call.

      Any hints, workarounds, or whatever will be greatly appreciated.

      Thanks in advance.

      Martin

        • 1. ...and more
          lucentum Newbie

          Hi again, in the previous msg there is an error I had not noticed. The line that reads

          "$_=Similar.method("\"+m.getMethodName()\"" +",$args);" +

          was supposed to be:

          "$_=Similar.method("+m.getMethodName()+",$args);" +

          (m is a MethodCall)
          However, neither the first or the second work (though the first is clearly wrong), and this shows yet another problem I'm getting, appart from the "nested method calls" problem I originally wanted to draw attention to.

          This other problem I have is that apparently I can't pass the name of the originally called method, as an argument to the method call replacement. The Javassist compiler complains about this saying:
          javassist.CannotCompileException: [source error] no such field: toBytes

          (here toBytes is the name of the method called)

          If Javassist doesn't support this, then my problem is even bigger than the original problem I posted.

          I'd appreciate if Chiba or another developer (although it seems Chiba is the only developer working in Javassist) could help me by commenting on this.

          I've previously worked with BCEL, and I've done similar things to what I want to do here, although with a lot more work. Coming across Javassist made me think I could manipulate method calls without effort (with BCEL one needs to parse the method calls before being able to manipulate them as such!).

          It seems Javassist is intended to give developers a source-level abstraction. However, passing a String with tokens of special meaning ($_, $0, $$, etc.), to a replace method is way too obscure and doesn't provide the flexibility I would expect from a source-level abstraction.

          If Javassist parses a class file into some kind of AST structure, then it would be much more intuitive for a developer to work with expression and statement objects that are aware of their context, therefore reflecting the nested and recursive nature of the grammar.

          For instance, it seems more intuitive (and helpful) if a MethodCall object had the following methods:
          - isParameter() : returns true if this method call is nested inside another one
          - getEnclosingMethodCall(): if isParameter() is true, returns the enclosing MethodCall object.
          - getParameters(): returns an array of the parameters, some of which may be MethodCall's.
          -setTargetClass: changes the target class of the method
          -setMethodName: changes the name of the method being called
          -setParameters, and so on...

          Then, having those methods available, replace() could be a void method appended after doing the transformations needed to the parameters, target class, method name, etc.

          Finally, a Method object, could have a getMethodCalls(), and then one would be able to probe each one, to see their nested structure, and lay out (and therefore manipulate) the list of MethodCall's in the exact same way they are presented in a source text editor. This can be extended to all other expressions and constructs such as loops (also reflecting their nested structure).
          A getAllExprs() call to a Method object could return a sequential list of all expressions, and statements in the method body, each one being able to reflect their precise context, that is, their sequential and nested relationship to the others.

          I am aware that implementing such behavior would probably require a complete change of direction. But I'm not just being demanding or critical. I'd like to help implementing such changes/additions to the library. Maybe I should be posting this in the developer forum as well :) . I think that unless expressions(field access, method calls) and statements (loops, conditionals, etc.) can be manipulated in such ways, a real source-level abstraction will be a far goal.


          Thanks again,

          Martin

          • 2. Re: nested method calls
            lucentum Newbie

            My bad, the line should be
            "$_=Similar.method("+'\"'+m.getMethodName()+'\"'+",$args);" +
            That adds the double quote to the String (I hadn't realized that it is a String within a String).

            A $name special variable representing the name of the method called, would make easier for slow people like me ;)

            I still would appreciate an answer to problem #1 (i.e., nested method calls), and the suggestions in my previous message.

            Best regards,

            Martin

            • 3. Re: nested method calls
              Shigeru Chiba Expert

              Hi,

              Let me make your problem clear.
              Javassist deals with nested method calls as independent two
              method calls. You cannot deal with them as a set of related
              methods. For example,

              bytestream.write(myfile.toBytes());

              is compiled into the following bytecode:

              load bytestream
              load myfile
              call toBytes
              call write

              ExprEditor first finds "call toBytes" and translates into:

              load bytestream
              load myfile
              <the code passed to relace().
              some value must be assigned to $_>
              load $_
              call write

              Then ExprEditor finds "call write" and translates into:

              load bytestream
              load myfile
              <the code passed to replace() for toBytes>
              load $_
              <the code passed to replace() for write>
              load $_ if necessary

              So to be honest, I could not understand why you want to give
              this code:

              { $_=Similar.method($name, $args);
              $_=Similar.getReturnValue(); }

              to replace(). Why do you assign the result of Similar.method()
              to $_? I think I don't really understand your argument.

              Best regards,

              Chiba

              • 4. Re: nested method calls
                lucentum Newbie

                Hi again, and thanks for replying. What I'm really doing is:

                {
                Similar.method($name, $args);
                $_=Similar.getReturnValue();
                }

                because Similar.method() is a void method. The error I get is with this code above. My original post had a copy-pasting bug (probably due to late night fatigue). Sorry about that.

                In the example
                bytestream.write(myfile.toBytes());
                I don't want to assign a value to $_ for the call to toBytes(). The return value must be pushed onto the stack and remain there to be passed as a parameter to the enclosing method write().

                The problem, I believe, is that I can only pass one string to replace() for all method calls, whether they're nested or not. So in the case of non-nested method calls, the code works, since the return value from the call to getReturnValue() is assigned to whatever original left-hand variable is there. But in the case of a nested method call, this assignment is not possible, because there is no result variable for the inner method call. Hence the error.

                I hope you can give me a solution to this.

                cheers,

                Martin

                • 5. Re: nested method calls
                  Shigeru Chiba Expert

                  > The problem, I believe, is that I can only pass one string to replace()
                  > for all method calls, whether they're nested or not.

                  You can give different code to replace() for a different MethodCall
                  object. Since edit() in ExprEditor is a sort of callback method,
                  in the case of nested method calls, edit() is invoked twice;
                  it first receives a MethodCall object representing the inner call
                  toBytes(), then it receives a different MethodCall object for
                  write().

                  > In the example
                  > bytestream.write(myfile.toBytes());
                  > I don't want to assign a value to $_ for the call to toBytes().
                  > The return value must be pushed onto the stack and remain
                  > there to be passed as a parameter to the enclosing method write().

                  The value assined to $_ during the first call to edit() for toBytes()
                  is pushed onto the stack. It is referred to by $1 during the second
                  call to edit() for write().

                  Maybe I should revise the tutorial to clarify this issue...