Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-9063

Groovy 2.5.6 @CompileStatic generates invalid bytecode (leading to runtime ClassCastException) when accessing protected instance member from 2 level of nested closures

    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: 2.5.6
    • Fix Version/s: 3.0.0-beta-1, 2.5.7
    • Labels:
      None
    • Environment:
      Groovy 2.5.6
      @groovy.transform.CompileStatic

      Description

      Provided the following class:

      import groovy.transform.CompileStatic
      import org.objectweb.asm.ClassReader
      import org.objectweb.asm.util.TraceClassVisitor
      
      @CompileStatic
      class NoBug {
          protected String message = "hello"
      
          void noBug() {
              { ->
                  { ->
                      printClass(getClass().name)
                      message.length()
                  }.call()
              }.call()
          }
      
          static void printClass(String className) {
              new ClassReader(className).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0)
          }
      
          static void main(String[] args) {
              new NoBug().noBug()
          }
      }
      
      No problem with 2.5.5

      Compilation ans execution with Groovy 2.5.5 works fine and produce the following output:

      // class version 52.0 (52)
      // access flags 0x31
      public final class groovy255/NoBug$_noBug_closure1$_closure2 extends groovy/lang/Closure implements org/codehaus/groovy/runtime/GeneratedClosure {
      
        // compiled from: NoBug.groovy
        OUTERCLASS groovy255/NoBug$_noBug_closure1 doCall ()Ljava/lang/Object;
        // access flags 0x11
        public final INNERCLASS groovy255/NoBug$_noBug_closure1$_closure2 null _closure2
      
        // access flags 0x100A
        private static synthetic Lorg/codehaus/groovy/reflection/ClassInfo; $staticClassInfo
      
        // access flags 0x1089
        public static transient synthetic Z __$stMC
      
        // access flags 0x1
        public <init>(Ljava/lang/Object;Ljava/lang/Object;)V
         L0
          ALOAD 0
          ALOAD 1
          ALOAD 2
          INVOKESPECIAL groovy/lang/Closure.<init> (Ljava/lang/Object;Ljava/lang/Object;)V
         L1
          RETURN
          LOCALVARIABLE this Lgroovy255/NoBug$_noBug_closure1$_closure2; L0 L1 0
          LOCALVARIABLE _outerInstance Ljava/lang/Object; L0 L1 1
          LOCALVARIABLE _thisObject Ljava/lang/Object; L0 L1 2
          MAXSTACK = 3
          MAXLOCALS = 3
      
        // access flags 0x1
        public doCall()Ljava/lang/Object;
         L0
          LINENUMBER 14 L0
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          INVOKEVIRTUAL java/lang/Class.getName ()Ljava/lang/String;
          INVOKESTATIC groovy255/NoBug.printClass (Ljava/lang/String;)V
          ACONST_NULL
          POP
         L1
          LINENUMBER 15 L1
          ALOAD 0
          LDC "message"
          INVOKEINTERFACE groovy/lang/GroovyObject.getProperty (Ljava/lang/String;)Ljava/lang/Object; (itf)
          CHECKCAST java/lang/String
          INVOKEVIRTUAL java/lang/String.length ()I
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          ARETURN
         L2
          LINENUMBER 14 L2
         FRAME FULL [] [java/lang/Throwable]
          NOP
          ATHROW
          LOCALVARIABLE this Lgroovy255/NoBug$_noBug_closure1$_closure2; L0 L2 0
          MAXSTACK = 2
          MAXLOCALS = 1
      
        // access flags 0x1004
        protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          LDC Lgroovy255/NoBug$_noBug_closure1$_closure2;.class
          IF_ACMPEQ L0
          ALOAD 0
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
          ARETURN
         L0
         FRAME SAME
          GETSTATIC groovy255/NoBug$_noBug_closure1$_closure2.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
          ASTORE 1
          ALOAD 1
          IFNONNULL L1
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          INVOKESTATIC org/codehaus/groovy/reflection/ClassInfo.getClassInfo (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
          DUP
          ASTORE 1
          PUTSTATIC groovy255/NoBug$_noBug_closure1$_closure2.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
         L1
         FRAME APPEND [org/codehaus/groovy/reflection/ClassInfo]
          ALOAD 1
          INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
          ARETURN
          MAXSTACK = 2
          MAXLOCALS = 2
      }
      
      Problem with 2.5.6

      Now with Groovy 2.5.6, compilation seems fine but execution leads to ClassCastException, and inspection of the inner closure reveals that the bytecode differs:

      // class version 52.0 (52)
      // access flags 0x31
      public final class groovy256/Bug$_bug_closure1$_closure2 extends groovy/lang/Closure implements org/codehaus/groovy/runtime/GeneratedClosure {
      
        // compiled from: Bug.groovy
        OUTERCLASS groovy256/Bug$_bug_closure1 doCall ()Ljava/lang/Object;
        // access flags 0x11
        public final INNERCLASS groovy256/Bug$_bug_closure1$_closure2 null _closure2
      
        // access flags 0x100A
        private static synthetic Lorg/codehaus/groovy/reflection/ClassInfo; $staticClassInfo
      
        // access flags 0x1089
        public static transient synthetic Z __$stMC
      
        // access flags 0x1
        public <init>(Ljava/lang/Object;Ljava/lang/Object;)V
         L0
          ALOAD 0
          ALOAD 1
          ALOAD 2
          INVOKESPECIAL groovy/lang/Closure.<init> (Ljava/lang/Object;Ljava/lang/Object;)V
         L1
          RETURN
          LOCALVARIABLE this Lgroovy256/Bug$_bug_closure1$_closure2; L0 L1 0
          LOCALVARIABLE _outerInstance Ljava/lang/Object; L0 L1 1
          LOCALVARIABLE _thisObject Ljava/lang/Object; L0 L1 2
          MAXSTACK = 3
          MAXLOCALS = 3
      
        // access flags 0x1
        public doCall()Ljava/lang/Object;
         L0
          LINENUMBER 14 L0
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          INVOKEVIRTUAL java/lang/Class.getName ()Ljava/lang/String;
          INVOKESTATIC groovy256/Bug.printClass (Ljava/lang/String;)V
          ACONST_NULL
          POP
         L1
          LINENUMBER 15 L1
          ALOAD 0
          CHECKCAST groovy256/Bug$_bug_closure1$_closure2
          INVOKEVIRTUAL groovy256/Bug$_bug_closure1$_closure2.getOwner ()Ljava/lang/Object;
          CHECKCAST groovy256/Bug
          GETFIELD groovy256/Bug.message : Ljava/lang/String;
          INVOKEVIRTUAL java/lang/String.length ()I
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          ARETURN
         L2
          LINENUMBER 14 L2
         FRAME FULL [] [java/lang/Throwable]
          NOP
          ATHROW
          LOCALVARIABLE this Lgroovy256/Bug$_bug_closure1$_closure2; L0 L2 0
          MAXSTACK = 1
          MAXLOCALS = 1
      
        // access flags 0x1004
        protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          LDC Lgroovy256/Bug$_bug_closure1$_closure2;.class
          IF_ACMPEQ L0
          ALOAD 0
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
          ARETURN
         L0
         FRAME SAME
          GETSTATIC groovy256/Bug$_bug_closure1$_closure2.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
          ASTORE 1
          ALOAD 1
          IFNONNULL L1
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          INVOKESTATIC org/codehaus/groovy/reflection/ClassInfo.getClassInfo (Ljava/lang/Class;)Lorg/codehaus/groovy/reflection/ClassInfo;
          DUP
          ASTORE 1
          PUTSTATIC groovy256/Bug$_bug_closure1$_closure2.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
         L1
         FRAME APPEND [org/codehaus/groovy/reflection/ClassInfo]
          ALOAD 1
          INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
          ARETURN
          MAXSTACK = 2
          MAXLOCALS = 2
      }
      
      groovy256.Bug$_bug_closure1 cannot be cast to groovy256.Bug
      java.lang.ClassCastException: groovy256.Bug$_bug_closure1 cannot be cast to groovy256.Bug
      	at groovy256.Bug$_bug_closure1$_closure2.doCall(Bug.groovy:15)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
      	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
      	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
      	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
      	at groovy.lang.Closure.call(Closure.java:405)
      	at groovy.lang.Closure.call(Closure.java:399)
      	at groovy256.Bug$_bug_closure1.doCall(Bug.groovy:13)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
      	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
      	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
      	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
      	at groovy.lang.Closure.call(Closure.java:405)
      	at groovy.lang.Closure.call(Closure.java:399)
      	at groovy256.Bug.bug(Bug.groovy:12)
      	at groovy256.BugTest.bug(BugTest.java:11)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
      	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
      	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
      	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
      	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
      	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
      	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
      	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
      	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
      	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
      	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
      	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
      	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
      	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
      	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
      	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
      	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
      	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
      	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
      	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
      	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
      	at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
      	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118)
      	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.lang.reflect.Method.invoke(Method.java:498)
      	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
      	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
      	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:175)
      	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:157)
      	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
      	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
      	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
      	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
      	at java.lang.Thread.run(Thread.java:748)
      

      The observed change in class structure is as follows:

         L1
          LINENUMBER 15 L1
          ALOAD 0
          LDC "message"
          INVOKEINTERFACE groovy/lang/GroovyObject.getProperty (Ljava/lang/String;)Ljava/lang/Object; (itf)
          CHECKCAST java/lang/String
          INVOKEVIRTUAL java/lang/String.length ()I
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          ARETURN
      

      becomes the following with 2.5.6:

         L1
          LINENUMBER 15 L1
          ALOAD 0
          CHECKCAST groovy256/Bug$_bug_closure1$_closure2
          INVOKEVIRTUAL groovy256/Bug$_bug_closure1$_closure2.getOwner ()Ljava/lang/Object;         << retrieve owner of the 2nd closure, ie. the 1st closure
          CHECKCAST groovy256/Bug              <<<<<<< trying to cast owner (1st closure) to outer class type
          GETFIELD groovy256/Bug.message : Ljava/lang/String;
          INVOKEVIRTUAL java/lang/String.length ()I
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          ARETURN
      
      Note

      Replacing

                      message.length()
      

      with

                      this.@message.length()
      

      does not exhibit that problem.

      This might be related to GROOVY-7687 or GROOVY-7996 (both affected 2.5.6) which relates to similar conditions (nested closure and @CompileStatic).

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                paulk Paul King
                Reporter:
                chuong_f Frédéric Chuong
              • Votes:
                1 Vote for this issue
                Watchers:
                3 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: