Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
2.5.6
-
None
-
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() } }
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 }
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
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
- relates to
-
GROOVY-7687 Bug with @CompileStatic and nested closures
- Closed
-
GROOVY-7996 Using with method with a closure that references a protected property produces ClassCastException
- Closed