groovy
  1. groovy
  2. GROOVY-4097

MOP implementation does not handle method calls in relation with class objects properly!

    Details

    • Type: Bug Bug
    • Status: Open Open
    • Priority: Critical Critical
    • Resolution: Unresolved
    • Affects Version/s: 1.7.0
    • Fix Version/s: None
    • Component/s: groovy-runtime
    • Labels:
      None
    • Environment:
      Tested on an AMD Turion64 under WinXP with Eclipse Galileo, Java 6u17, will occur on any platform though since this are groovy source code bugs.
    • Testcase included:
      yes
    • Number of attachments :
      1

      Description

      In a few places, the Meta Object Protocoll implementation uses parameter objects classes to look up the matching meta methods. But if one of these arguments already is of type Class, instead of using Class<Class<?>>, the original class parameter is used. This leads to a couple of bugs.

      My attached TestCase includes two methods, each demonstrating on of these bugs.

      First example:

      The first method, testClassArguments(), presents a major issue.
      The TestClass has two methods: getSomething(String) and getSomething(Class).

      Using the MetaObjectProtocol, i first retrieve the MetaMethod of getSomething (line 23) for an argument set [String] (line 22). MetaClassImpl looks up this method by taking the classes of [String] (namely [Class<String>]), and looking for a method with a signature, that matches [Class<String>]. This works fine, the result printed (line 24) is the correct method, and the assertion (line 25) passes.

      Now i do the same thing again, but this time using an argument set [Class<String>] (line 27). getMetaMethod returns a method (line 28). But, as one can see in the output (line 29), the method is once again getSomething(String): Since MetaClassImpl internally looks up the method, by taking the parameters classes, but in the process skipping class transformation for class arguments, it once again looks for a method with signature [Class<String>] instead of [Class<Class<String>>]. Now, invoking getSomething(String) with Class<String> as parameter of course leads to an IllegalArgumentException, thereby failing the test.

      Second example

      This second example, method testInvokeStaticMethod(), demonstrates, how a call to getSuperclass, a normal public instance method for Class<T>, is wrongly executed as a static method call on T.

      This bug does not occur, when using a default MetaClass: since all default MetaClasses are subclasses of MetaClassImpl, and the class org.codehaus.groovy.runtime.callsite.CallSiteArray has an instanceof test at the third line of it's method createCallStaticSite(CallSite,Class,Object[]), this does not happen for any of MetaClassImpl subclasses.

      So the first thing this test does is, to replace its MetaClass with a DelegatingMetaClass, which does nothing except forward all calls to the original MetaClassImpl, but it is not a subclass of MetaClassImpl, so the mentioned instanceof test does not work.

      Firstly, the test now retrieves it's own Class object (line 38), which works as expected.

      Now, the tests asserts, that its own superclass is GroovyTestCase (line 39), but this assertion fails. The actually returned value of the clazz.getSuperclass() call is "This was the wrong method!", which is returned by the test cases static getSuperclass() method (lines 42 to 44).

      So what happened here? That is pretty simple:

      A normal static call to the getSuperclass() method at lines 42 to 44 would be translated to invokeStaticMethod(GroovyTestCase.class, "getSuperclass", []) on the MetaClass of GroovyTestCase.

      The superclass call at line 39 should have been translated to invokeMethod(clazz, "getSuperclass", []) on the MetaClass of Class<T>.

      But what happened instead is, since clazz == GroovyTestCase.clazz, the call site was simply "mistaken" for a static call site, and delegated to the wrong MetaClass.

      some final words

      though the displayed test cases may seem a little artificially constructed, i actually stumbled over both of these bugs in my project, after renaming a few java files to groovy. So please note that these are real issues, the examples only may seem that surreal because i reduced them to a minimum. This stuff does regularly break code when doing some heavy MOP related work.

        Activity

        Paul King made changes -
        Field Original Value New Value
        Description In a few places, the Meta Object Protocoll implementation uses parameter objects classes to look up the matching meta methods. But if one of these arguments already is of type Class, instead of using Class<Class<?>>, the original class parameter is used. This leads to a couple of bugs.

        My attached TestCase includes two methods, each demonstrating on of these bugs.

        h4. First example:

        The first method, testClassArguments(), presents a major issue.
        The TestClass has two methods: getSomething(String) and getSomething(Class).

        Using the MetaObjectProtocol, i first retrieve the MetaMethod of getSomething (line 23) for an argument set [String] (line 22). MetaClassImpl looks up this method by taking the classes of [String] (namely [Class<String>]), and looking for a method with a signature, that matches [Class<String>]. This works fine, the result printed (line 24) is the correct method, and the assertion (line 25) passes.

        Now i do the same thing again, but this time using an argument set [Class<String>] (line 27). getMetaMethod returns a method (line 28). But, as one can see in the output (line 29), the method is once again getSomething(String): Since MetaClassImpl internally looks up the method, by taking the parameters classes, but in the process _skipping_ class transformation for class arguments, it once again looks for a method with signature [Class<String>] instead of [Class<Class<String>>]. Now, invoking getSomething(String) with Class<String> as parameter of course leads to an IllegalArgumentException, thereby failing the test.

        .h4 Second example

        This second example, method testInvokeStaticMethod(), demonstrates, how a call to getSuperclass, a normal public instance method for Class<T>, is wrongly executed as a static method call on T.

        This bug does not occur, when using a default MetaClass: since all default MetaClasses are subclasses of MetaClassImpl, and the class org.codehaus.groovy.runtime.callsite.CallSiteArray has an instanceof test at the third line of it's method createCallStaticSite(CallSite,Class,Object[]), this does not happen for any of MetaClassImpl subclasses.

        So the first thing this test does is, to replace its MetaClass with a DelegatingMetaClass, which does nothing except forward all calls to the original MetaClassImpl, but it is not a subclass of MetaClassImpl, so the mentioned instanceof test does not work.

        Firstly, the test now retrieves it's own Class object (line 38), which works as expected.

        Now, the tests asserts, that its own superclass is GroovyTestCase (line 39), but this assertion fails. The actually returned value of the clazz.getSuperclass() call is "This was the wrong method!", which is returned by the test cases static getSuperclass() method (lines 42 to 44).

        So what happened here? That is pretty simple:

        A normal static call to the getSuperclass() method at lines 42 to 44 would be translated to invokeStaticMethod(GroovyTestCase.class, "getSuperclass", []) on the MetaClass of GroovyTestCase.

        The superclass call at line 39 *should* have been translated to invokeMethod(clazz, "getSuperclass", []) on the MetaClass of Class<T>.

        But what happened instead is, since clazz == GroovyTestCase.clazz, the call site was simply "mistaken" for a static call site, and delegated to the wrong MetaClass.

        .h5 some final words
        though the displayed test cases may seem a little artificially constructed, i actually stumbled over both of these bugs in my project, after renaming a few java files to groovy. So please note that these are real issues, the examples only may seem that surreal because i reduced them to a minimum. This stuff does regularly break code when doing some heavy MOP related work.
        In a few places, the Meta Object Protocoll implementation uses parameter objects classes to look up the matching meta methods. But if one of these arguments already is of type Class, instead of using Class<Class<?>>, the original class parameter is used. This leads to a couple of bugs.

        My attached TestCase includes two methods, each demonstrating on of these bugs.

        h4. First example:

        The first method, testClassArguments(), presents a major issue.
        The TestClass has two methods: getSomething(String) and getSomething(Class).

        Using the MetaObjectProtocol, i first retrieve the MetaMethod of getSomething (line 23) for an argument set [String] (line 22). MetaClassImpl looks up this method by taking the classes of [String] (namely [Class<String>]), and looking for a method with a signature, that matches [Class<String>]. This works fine, the result printed (line 24) is the correct method, and the assertion (line 25) passes.

        Now i do the same thing again, but this time using an argument set [Class<String>] (line 27). getMetaMethod returns a method (line 28). But, as one can see in the output (line 29), the method is once again getSomething(String): Since MetaClassImpl internally looks up the method, by taking the parameters classes, but in the process _skipping_ class transformation for class arguments, it once again looks for a method with signature [Class<String>] instead of [Class<Class<String>>]. Now, invoking getSomething(String) with Class<String> as parameter of course leads to an IllegalArgumentException, thereby failing the test.

        h4. Second example

        This second example, method testInvokeStaticMethod(), demonstrates, how a call to getSuperclass, a normal public instance method for Class<T>, is wrongly executed as a static method call on T.

        This bug does not occur, when using a default MetaClass: since all default MetaClasses are subclasses of MetaClassImpl, and the class org.codehaus.groovy.runtime.callsite.CallSiteArray has an instanceof test at the third line of it's method createCallStaticSite(CallSite,Class,Object[]), this does not happen for any of MetaClassImpl subclasses.

        So the first thing this test does is, to replace its MetaClass with a DelegatingMetaClass, which does nothing except forward all calls to the original MetaClassImpl, but it is not a subclass of MetaClassImpl, so the mentioned instanceof test does not work.

        Firstly, the test now retrieves it's own Class object (line 38), which works as expected.

        Now, the tests asserts, that its own superclass is GroovyTestCase (line 39), but this assertion fails. The actually returned value of the clazz.getSuperclass() call is "This was the wrong method!", which is returned by the test cases static getSuperclass() method (lines 42 to 44).

        So what happened here? That is pretty simple:

        A normal static call to the getSuperclass() method at lines 42 to 44 would be translated to invokeStaticMethod(GroovyTestCase.class, "getSuperclass", []) on the MetaClass of GroovyTestCase.

        The superclass call at line 39 *should* have been translated to invokeMethod(clazz, "getSuperclass", []) on the MetaClass of Class<T>.

        But what happened instead is, since clazz == GroovyTestCase.clazz, the call site was simply "mistaken" for a static call site, and delegated to the wrong MetaClass.

        h5. some final words

        though the displayed test cases may seem a little artificially constructed, i actually stumbled over both of these bugs in my project, after renaming a few java files to groovy. So please note that these are real issues, the examples only may seem that surreal because i reduced them to a minimum. This stuff does regularly break code when doing some heavy MOP related work.
        blackdrag blackdrag made changes -
        Component/s groovy-runtime [ 16250 ]

          People

          • Assignee:
            Unassigned
            Reporter:
            Joscha Drechsler
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated: