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

Access to outer class private field from closure leads to ClassCastException (@CompileStatic)

Attach filesAttach ScreenshotVotersWatch issueWatchersCreate sub-taskLinkCloneUpdate Comment AuthorReplace String in CommentUpdate Comment VisibilityDelete Comments
    XMLWordPrintableJSON

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: 3.0.0-rc-3
    • Fix Version/s: 4.0.0-alpha-1, 3.0.1
    • Component/s: Static compilation
    • Labels:
      None
    • Environment:

      Description

      Given the following code:

      package org.example
      
      import groovy.transform.CompileStatic
      import org.objectweb.asm.ClassReader
      import org.objectweb.asm.util.TraceClassVisitor
      
      @CompileStatic
      class OuterClass {
          private int counter
      
          void call() {
              { ->
                  def closureClassName = new Throwable().stackTrace[0].className
                  counter++
      
                  printClass(closureClassName)
              }.call()
          }
      
          static void printClass(String className) {
              new ClassReader(className).accept(new TraceClassVisitor(new PrintWriter(System.err)), ClassReader.EXPAND_FRAMES)
          }
      
          static void main(String[] args) {
              try {
                  new OuterClass().call()
              } catch (ClassCastException e) {
                  printClass(e.stackTrace[0].className)
                  throw e
              }
          }
      }
      
      Groovy 3.0.0-rc-3

      Compilation OK
      Runtime ClassCastException

      Exception in thread "main" java.lang.ClassCastException: class org.example.OuterClass$_call_closure1 cannot be cast to class org.example.OuterClass (org.example.OuterClass$_call_closure1 and org.example.OuterClass are in unnamed module of loader 'app')
      	at org.example.OuterClass$_call_closure1.doCall(OuterClass.groovy:14)
      	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
      	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:107)
      	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:1027)
      	at groovy.lang.Closure.call(Closure.java:412)
      	at groovy.lang.Closure.call(Closure.java:406)
      	at org.example.OuterClass.call(OuterClass.groovy:12)
      	at org.example.OuterClass.main(OuterClass.groovy:26)
      
      Execution failed for task ':OuterClass.main()'.
      > Process 'command 'C:/Program Files/Java/zulu11.35.15-ca-jdk11.0.5-win_x64/bin/java.exe'' finished with non-zero exit value 1
      

      Disassembled view from ASM (preceding the exception):

      // class version 55.0 (55)
      // access flags 0x31
      public final class org/example/OuterClass$_call_closure1 extends groovy/lang/Closure implements org/codehaus/groovy/runtime/GeneratedClosure {
      
        // compiled from: OuterClass.groovy
        OUTERCLASS org/example/OuterClass call ()V
        // access flags 0x11
        public final INNERCLASS org/example/OuterClass$_call_closure1 null _call_closure1
      
        // 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 Lorg/example/OuterClass$_call_closure1; 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 13 L0
          NEW java/lang/Throwable
          DUP
          INVOKESPECIAL java/lang/Throwable.<init> ()V
          INVOKEVIRTUAL java/lang/Throwable.getStackTrace ()[Ljava/lang/StackTraceElement;
          ICONST_0
          INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.objectArrayGet ([Ljava/lang/Object;I)Ljava/lang/Object;
          CHECKCAST java/lang/StackTraceElement
          INVOKEVIRTUAL java/lang/StackTraceElement.getClassName ()Ljava/lang/String;
          ASTORE 1
         L1
          ALOAD 1
          POP
         L2
          LINENUMBER 14 L2
          ALOAD 0
          CHECKCAST org/example/OuterClass
          LDC "counter"
          INVOKEINTERFACE groovy/lang/GroovyObject.getProperty (Ljava/lang/String;)Ljava/lang/Object; (itf)
          DUP
          ASTORE 2
          INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.intUnbox (Ljava/lang/Object;)I
          ICONST_1
          IADD
          DUP
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          LDC Lorg/example/OuterClass$_call_closure1;.class
          ALOAD 0
          LDC "counter"
          CHECKCAST java/lang/String
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setGroovyObjectProperty (Ljava/lang/Object;Ljava/lang/Class;Lgroovy/lang/GroovyObject;Ljava/lang/String;)V
          POP
          ALOAD 2
          POP
         L3
          LINENUMBER 16 L3
          ALOAD 1
          INVOKESTATIC org/example/OuterClass.printClass (Ljava/lang/String;)V
          ACONST_NULL
          ARETURN
         L4
         FRAME FULL [] [java/lang/Throwable]
          NOP
          ATHROW
          LOCALVARIABLE this Lorg/example/OuterClass$_call_closure1; L0 L4 0
          LOCALVARIABLE closureClassName Ljava/lang/String; L1 L4 1
          MAXSTACK = 5
          MAXLOCALS = 3
      
        // access flags 0x1004
        protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          LDC Lorg/example/OuterClass$_call_closure1;.class
          IF_ACMPEQ L0
          ALOAD 0
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
          ARETURN
         L0
         FRAME FULL [org/example/OuterClass$_call_closure1] []
          GETSTATIC org/example/OuterClass$_call_closure1.$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 org/example/OuterClass$_call_closure1.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
         L1
         FRAME FULL [org/example/OuterClass$_call_closure1 org/codehaus/groovy/reflection/ClassInfo] []
          ALOAD 1
          INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
          ARETURN
          MAXSTACK = 2
          MAXLOCALS = 2
      }
      
      Groovy 2.5.9

      Compilation OK
      No runtime exception

      Disassembled view of the closure

      // class version 55.0 (55)
      // access flags 0x31
      public final class org/example/OuterClass$_call_closure1 extends groovy/lang/Closure implements org/codehaus/groovy/runtime/GeneratedClosure {
      
        // compiled from: OuterClass.groovy
        OUTERCLASS org/example/OuterClass call ()V
        // access flags 0x11
        public final INNERCLASS org/example/OuterClass$_call_closure1 null _call_closure1
      
        // 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 Lorg/example/OuterClass$_call_closure1; 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 13 L0
          NEW java/lang/Throwable
          DUP
          INVOKESPECIAL java/lang/Throwable.<init> ()V
          INVOKEVIRTUAL java/lang/Throwable.getStackTrace ()[Ljava/lang/StackTraceElement;
          ICONST_0
          INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.objectArrayGet ([Ljava/lang/Object;I)Ljava/lang/Object;
          CHECKCAST java/lang/StackTraceElement
          INVOKEVIRTUAL java/lang/StackTraceElement.getClassName ()Ljava/lang/String;
          ASTORE 1
         L1
          ALOAD 1
          POP
         L2
          LINENUMBER 14 L2
          ALOAD 0
          INVOKEVIRTUAL groovy/lang/Closure.getThisObject ()Ljava/lang/Object;
          CHECKCAST org/example/OuterClass
          INVOKESTATIC org/example/OuterClass.pfaccess$0 (Lorg/example/OuterClass;)I
          DUP
          ISTORE 2
          ICONST_1
          IADD
          DUP
          INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
          LDC Lorg/example/OuterClass$_call_closure1;.class
          ALOAD 0
          LDC "counter"
          CHECKCAST java/lang/String
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setGroovyObjectProperty (Ljava/lang/Object;Ljava/lang/Class;Lgroovy/lang/GroovyObject;Ljava/lang/String;)V
          POP
          ILOAD 2
          POP
         L3
          LINENUMBER 16 L3
          ALOAD 1
          INVOKESTATIC org/example/OuterClass.printClass (Ljava/lang/String;)V
          ACONST_NULL
          ARETURN
         L4
         FRAME FULL [] [java/lang/Throwable]
          NOP
          ATHROW
          LOCALVARIABLE this Lorg/example/OuterClass$_call_closure1; L0 L4 0
          LOCALVARIABLE closureClassName Ljava/lang/String; L1 L4 1
          MAXSTACK = 5
          MAXLOCALS = 3
      
        // access flags 0x1004
        protected synthetic $getStaticMetaClass()Lgroovy/lang/MetaClass;
          ALOAD 0
          INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
          LDC Lorg/example/OuterClass$_call_closure1;.class
          IF_ACMPEQ L0
          ALOAD 0
          INVOKESTATIC org/codehaus/groovy/runtime/ScriptBytecodeAdapter.initMetaClass (Ljava/lang/Object;)Lgroovy/lang/MetaClass;
          ARETURN
         L0
         FRAME FULL [org/example/OuterClass$_call_closure1] []
          GETSTATIC org/example/OuterClass$_call_closure1.$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 org/example/OuterClass$_call_closure1.$staticClassInfo : Lorg/codehaus/groovy/reflection/ClassInfo;
         L1
         FRAME FULL [org/example/OuterClass$_call_closure1 org/codehaus/groovy/reflection/ClassInfo] []
          ALOAD 1
          INVOKEVIRTUAL org/codehaus/groovy/reflection/ClassInfo.getMetaClass ()Lgroovy/lang/MetaClass;
          ARETURN
          MAXSTACK = 2
          MAXLOCALS = 2
      }
      

      It seems the call to getThisObject is missing (not sure whether Groovy 3 expects that part to change):

      Groovy 3.0.0-rc-3
          LINENUMBER 14 L2
          ALOAD 0
          CHECKCAST org/example/OuterClass
      
      Groovy 2.5.9
          LINENUMBER 14 L2
          ALOAD 0
          INVOKEVIRTUAL groovy/lang/Closure.getThisObject ()Ljava/lang/Object;
          CHECKCAST org/example/OuterClass
      

        Attachments

          Activity

            People

            • Assignee:
              emilles Eric Milles
              Reporter:
              chuong_f Frédéric Chuong

              Dates

              • Created:
                Updated:
                Resolved:

                Time Tracking

                Estimated:
                Original Estimate - Not Specified
                Not Specified
                Remaining:
                Remaining Estimate - 0h
                0h
                Logged:
                Time Spent - 1h
                1h

                  Issue deployment