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

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

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 3.0.0-rc-3
    • 4.0.0-alpha-1, 3.0.1, 2.5.16
    • Static compilation
    • None

    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

        Issue Links

          Activity

            People

              emilles Eric Milles
              chuong_f Frédéric Chuong
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Time Tracking

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