Groovy
  1. Groovy
  2. GROOVY-7541

GroovyBugError in TypeTransformer.addTransformer

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Works for Me
    • Affects Version/s: 2.4.4
    • Fix Version/s: 2.4.7
    • Component/s: groovy-runtime
    • Labels:
      None
    • Environment:
      Java web app on either Apache on Linux or Windows, or WebSphere on AIX. Java 7.1. Groovy via ScriptEngineManager.getEngineByName("groovy")

      Description

      Just updated from Groovy 2.3.6 to 2.4.4 and this code no longer works when dynamic invocation is used (works fine when indy not used):

      d1.setIndexes(new HashMap<List<String>,DatasetIndex>());

      where d1 is one of the Java classes imported to the Groovy script. setIndexes is just a plain setter of a field on the class. The error is

      BUG! Unknown transformation for argument <... stuff from toString of d1 here...> at position 0 with class com.ibm.cayce.sdv.dataset.Dataset for parameter of type class org.codehaus.groovy.vmplugin.v7.IndyInterface

      It contains no stack trace, but by searching for sources of "Unknown transformation for argument", I found it to be in TypeTransformer.addTransformer. If I put a breakpoint there, this is the call stack:

      TypeTransformers.addTransformer(MethodHandle, int, Object, Class) line: 124
      Selector$MethodSelector.correctCoerce() line: 793
      Selector$MethodSelector.setCallSiteTarget() line: 958
      IndyInterface.selectMethod(MutableCallSite, Class, String, int, Boolean, Boolean, Boolean, Object, Object[]) line: 214
      Script2.run() line: 2
      GroovyScriptEngineImpl.eval(Class, ScriptContext) line: 323
      GroovyScriptEngineImpl.eval(String, ScriptContext) line: 124
      GroovyScriptEngineImpl(AbstractScriptEngine).eval(String) line: 276

      Looks like TypeTransformer was introduced via org.codehaus.groovy.vmplugin.v7 in version 2.3.8, but I don't see anything in the 2.3.8 change log about it (at least not under an obvious description).

      FWIW, we turn on dynamic invocation via:

      CompilerConfiguration configuration = new CompilerConfiguration(); configuration.setTargetBytecode(System.getProperty("java.specification.version"));
      configuration.getOptimizationOptions().put("indy", true);
      configuration.getOptimizationOptions().put("int", false);
      configuration.addCompilationCustomizers(importCustomizer,secureCustomizer);
      GroovyClassLoader gcl = new GroovyClassLoader(this.getClass().getClassLoader(), configuration);
      ((GroovyScriptEngineImpl) engine).setClassLoader(gcl);

      Please let me know if any more info is needed, or if there is a work around (other than not using indy).

        Activity

        Hide
        Dan Nimick added a comment -

        Another developer had time to delve into this some more and discovered that the error occurs when the setIndexes method is not public (it's protected, currently).

        So we have an obvious fix, and it makes sense that there might be an error invoking something that isn't public, but perhaps a better error message is in order?

        Show
        Dan Nimick added a comment - Another developer had time to delve into this some more and discovered that the error occurs when the setIndexes method is not public (it's protected, currently). So we have an obvious fix, and it makes sense that there might be an error invoking something that isn't public, but perhaps a better error message is in order?
        Hide
        Jochen Theodorou added a comment -

        GroovyBugError message are not supposed to appear at all and are not to be seen as messages informing the user. Instead they show bugs and that something happened that should not have happened and that a bug fix is required. What addTransformers does is adding transformations for some transformation cases like GString->String, Object->array, Number->Number. Any other kind of transformation is not supposed to get to this point. The message

        BUG! Unknown transformation for argument <... stuff from toString of d1 here...> at position 0 with class com.ibm.cayce.sdv.dataset.Dataset for parameter of type class org.codehaus.groovy.vmplugin.v7.IndyInterface

        tells me that the first parameter of the method we want to call got an object of type DataSet, but the method signature of the method to be called expects at this position an object of type IndyInterface. This would mean to me, we are talking about the this part: d1.setIndexes(HashMap), where d1 is a DataSet. This means position 0 is supposed to have DataSet, not IndyInterface. This would indicate we are talking about a method invokedynamic invocation with IndyInterface itself, which is not supposed to happen.

        What you could do is setting the system property groovy.indy.logging (value does not really matter, as long as it is not null), and provide me the log from the last method invocation on. Then I can maybe see the problem, but at least what method was supposed to be called. Other than that, I see nothing I can do right now. There is of course always the chance of actually seeing a JVM bug here, but before I start blaming poor JVM engineers, it would be better to ensure Groovy is doing things as it is supposed to do. Best would be a test case of course.

        Show
        Jochen Theodorou added a comment - GroovyBugError message are not supposed to appear at all and are not to be seen as messages informing the user. Instead they show bugs and that something happened that should not have happened and that a bug fix is required. What addTransformers does is adding transformations for some transformation cases like GString->String, Object->array, Number->Number. Any other kind of transformation is not supposed to get to this point. The message BUG! Unknown transformation for argument <... stuff from toString of d1 here...> at position 0 with class com.ibm.cayce.sdv.dataset.Dataset for parameter of type class org.codehaus.groovy.vmplugin.v7.IndyInterface tells me that the first parameter of the method we want to call got an object of type DataSet, but the method signature of the method to be called expects at this position an object of type IndyInterface. This would mean to me, we are talking about the this part: d1.setIndexes(HashMap), where d1 is a DataSet. This means position 0 is supposed to have DataSet, not IndyInterface. This would indicate we are talking about a method invokedynamic invocation with IndyInterface itself, which is not supposed to happen. What you could do is setting the system property groovy.indy.logging (value does not really matter, as long as it is not null), and provide me the log from the last method invocation on. Then I can maybe see the problem, but at least what method was supposed to be called. Other than that, I see nothing I can do right now. There is of course always the chance of actually seeing a JVM bug here, but before I start blaming poor JVM engineers, it would be better to ensure Groovy is doing things as it is supposed to do. Best would be a test case of course.
        Hide
        Dan Nimick added a comment -

        Hello Jochen, Thank you for your reply!

        FWIW, the setIndexes method is declared like this:

        protected void setIndexes(Map<List<String>, DatasetIndex> indexes)

        { this.indexes = indexes; }

        So it's not expecting a Dataset at all, just a Map, as is being passed to it from the Groovy script.

        Also, when it's declared public or private it works. It's only when declared protected that it fails. And it doesn't matter what argument is expected (can even be none - see test case), and it fails when protected.

        Here's a simple test case:

        ====== Java class ======
        public class IndyTest {
        private String anything;
        protected void setAnything(String value)

        { this.anything = value; }

        protected String getAnything()

        { return anything; }

        }

        ====== Groovy script ======
        it = new IndyTest();
        it.setAnything("something")

        ====== Indy log ======
        INFO: ----------------------------------------------------
        invocation of method 'setAnything'
        invocation type: METHOD
        sender: class Script1
        targetType: (Object,String)Object
        safe navigation: false
        thisCall: false
        spreadCall: false
        with 2 arguments
        argument[0] = com.sdv.dataset.IndyTest@b000f27
        argument[1] = something
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setCallSiteTarget
        INFO: meta class is groovy.lang.MetaClassImpl@264c63c[class com.sdv.dataset.IndyTest]
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setSelectionBase
        INFO: selection base set to class com.sdv.dataset.IndyTest
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector getMetaClassImpl
        INFO: meta class is a recognized MetaClassImpl
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector chooseMeta
        INFO: retrieved method from meta class: protected void com.sdv.dataset.IndyTest.setAnything(java.lang.String)
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is category type method: false
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is static category type method: false
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is CachedMethod instance
        Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: successfully unreflected method

        ====== test of getAnything ======
        it = new IndyTest();
        it.getAnything()

        INFO: ----------------------------------------------------
        invocation of method 'getAnything'
        invocation type: METHOD
        sender: class Script1
        targetType: (Object)Object
        safe navigation: false
        thisCall: false
        spreadCall: false
        with 1 arguments
        argument[0] = com.sdv.dataset.IndyTest@58dd2a59
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setCallSiteTarget
        INFO: meta class is groovy.lang.MetaClassImpl@7e20efa8[class com.sdv.dataset.IndyTest]
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setSelectionBase
        INFO: selection base set to class com.sdv.dataset.IndyTest
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector getMetaClassImpl
        INFO: meta class is a recognized MetaClassImpl
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector chooseMeta
        INFO: retrieved method from meta class: protected java.lang.String com.sdv.dataset.IndyTest.getAnything()
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is category type method: false
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is static category type method: false
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: meta method is CachedMethod instance
        Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod
        INFO: successfully unreflected method

        Show
        Dan Nimick added a comment - Hello Jochen, Thank you for your reply! FWIW, the setIndexes method is declared like this: protected void setIndexes(Map<List<String>, DatasetIndex> indexes) { this.indexes = indexes; } So it's not expecting a Dataset at all, just a Map, as is being passed to it from the Groovy script. Also, when it's declared public or private it works. It's only when declared protected that it fails. And it doesn't matter what argument is expected (can even be none - see test case), and it fails when protected. Here's a simple test case: ====== Java class ====== public class IndyTest { private String anything; protected void setAnything(String value) { this.anything = value; } protected String getAnything() { return anything; } } ====== Groovy script ====== it = new IndyTest(); it.setAnything("something") ====== Indy log ====== INFO: ---------------------------------------------------- invocation of method 'setAnything' invocation type: METHOD sender: class Script1 targetType: (Object,String)Object safe navigation: false thisCall: false spreadCall: false with 2 arguments argument [0] = com.sdv.dataset.IndyTest@b000f27 argument [1] = something Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setCallSiteTarget INFO: meta class is groovy.lang.MetaClassImpl@264c63c [class com.sdv.dataset.IndyTest] Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setSelectionBase INFO: selection base set to class com.sdv.dataset.IndyTest Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector getMetaClassImpl INFO: meta class is a recognized MetaClassImpl Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector chooseMeta INFO: retrieved method from meta class: protected void com.sdv.dataset.IndyTest.setAnything(java.lang.String) Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is category type method: false Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is static category type method: false Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is CachedMethod instance Aug 06, 2015 5:43:05 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: successfully unreflected method ====== test of getAnything ====== it = new IndyTest(); it.getAnything() INFO: ---------------------------------------------------- invocation of method 'getAnything' invocation type: METHOD sender: class Script1 targetType: (Object)Object safe navigation: false thisCall: false spreadCall: false with 1 arguments argument [0] = com.sdv.dataset.IndyTest@58dd2a59 Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setCallSiteTarget INFO: meta class is groovy.lang.MetaClassImpl@7e20efa8 [class com.sdv.dataset.IndyTest] Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setSelectionBase INFO: selection base set to class com.sdv.dataset.IndyTest Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector getMetaClassImpl INFO: meta class is a recognized MetaClassImpl Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector chooseMeta INFO: retrieved method from meta class: protected java.lang.String com.sdv.dataset.IndyTest.getAnything() Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is category type method: false Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is static category type method: false Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: meta method is CachedMethod instance Aug 06, 2015 5:56:36 AM org.codehaus.groovy.vmplugin.v7.Selector$MethodSelector setHandleForMetaMethod INFO: successfully unreflected method
        Hide
        Jochen Theodorou added a comment -

        and those scripts fail for you? As for expecting dataset ... in the invokedynamic logic in groovy the real receiver cannot be used because we allow calls on null. The jvm invocation constructs do not allow for that and shortcut to a NPE in this case. So we use dummy receiver instead, which is simply thrown away during invocation. That's why the receiver IndyTest appears as argument[0]. A MethodHandle on the other hand does not really know a receiver. A MethodHandle has only a MethodType, which has parameters. In case of the invocation of a non-static method the first parameter is therefore the receiver. A lot of transformations can be applied to such a handle, to for example reposition arguments; drop, add or replace them; even transform them or what the handle invocation will return. An argument can for example become the receiver. And so on.

        Show
        Jochen Theodorou added a comment - and those scripts fail for you? As for expecting dataset ... in the invokedynamic logic in groovy the real receiver cannot be used because we allow calls on null. The jvm invocation constructs do not allow for that and shortcut to a NPE in this case. So we use dummy receiver instead, which is simply thrown away during invocation. That's why the receiver IndyTest appears as argument [0] . A MethodHandle on the other hand does not really know a receiver. A MethodHandle has only a MethodType, which has parameters. In case of the invocation of a non-static method the first parameter is therefore the receiver. A lot of transformations can be applied to such a handle, to for example reposition arguments; drop, add or replace them; even transform them or what the handle invocation will return. An argument can for example become the receiver. And so on.
        Hide
        Dan Nimick added a comment -

        Umm, yeah, it fails for me, or I wouldn't have opened the bug.

        Does it not fail for you? Remember that this IndyTest is a Java class that needs to be added to the classpath prior to running a Groovy script that uses it. It's not a Groovy class...

        Show
        Dan Nimick added a comment - Umm, yeah, it fails for me, or I wouldn't have opened the bug. Does it not fail for you? Remember that this IndyTest is a Java class that needs to be added to the classpath prior to running a Groovy script that uses it. It's not a Groovy class...
        Hide
        Dan Nimick added a comment -

        Looks like this has resolved itself either by other Groovy updates or newer JDK versions.

        Show
        Dan Nimick added a comment - Looks like this has resolved itself either by other Groovy updates or newer JDK versions.

          People

          • Assignee:
            Unassigned
            Reporter:
            Dan Nimick
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development