[ https://issues.apache.org/jira/browse/GROOVY-9063?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16821218#comment-16821218 ]
Eric Milles commented on GROOVY-9063: ------------------------------------- I believe the changes indicated in GROOVY-9086 fix this bug as well. > Groovy 2.5.6 @CompileStatic generates invalid bytecode (leading to runtime > ClassCastException) when accessing protected instance member from 2 level of > nested closures > ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- > > Key: GROOVY-9063 > URL: https://issues.apache.org/jira/browse/GROOVY-9063 > Project: Groovy > Issue Type: Bug > Components: Compiler, Static compilation > Affects Versions: 2.5.6 > Environment: Groovy 2.5.6 > @groovy.transform.CompileStatic > Reporter: Frédéric Chuong > Priority: Major > > Provided the following class: > {code:groovy} > 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() > } > } > {code} > {panel:title=No problem with 2.5.5|borderColor=green} > Compilation ans execution with Groovy 2.5.5 works fine and produce the > following output: > {noformat} > // 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 > } > {noformat} > {panel} > {panel:title=Problem with 2.5.6|borderColor=red} > 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: > {noformat} > // 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 > } > {noformat} > {noformat} > 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) > {noformat} > {panel} > The observed change in class structure is as follows: > {code} > 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 > {code} > becomes the following with 2.5.6: > {code} > 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 > {code} > {panel:title=Note} > Replacing > {noformat} > message.length() > {noformat} > with > {noformat} > this.@message.length() > {noformat} > does not exhibit that problem. > {panel} > This might be related to [GROOVY-7687] or [GROOVY-7996] (both affected > [2.5.6|https://issues.apache.org/jira/projects/GROOVY/versions/12344751]) > which relates to similar conditions (nested closure and {{\@CompileStatic}}). -- This message was sent by Atlassian JIRA (v7.6.3#76005)