[ 
https://issues.apache.org/jira/browse/GROOVY-12666?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18137427#comment-18137427
 ] 

ASF GitHub Bot commented on GROOVY-12666:
-----------------------------------------

codeconsole opened a new pull request, #3187:
URL: https://github.com/apache/groovy/pull/3187

   A `@CompileStatic` closure class runs its body against `getThisObject()`. So 
a copy made by `rehydrate` reads the new this object's members, and throws 
`ClassCastException` when called if that object is not an instance of the 
enclosing class. A packed closure (`@PackedClosures` or 
`-Dgroovy.target.closure.pack=true`) dispatched to the receiver captured at 
construction, so the copy silently kept using the original owner:
   
   ```groovy
   @CompileStatic @PackedClosures
   class O {
       String name
       String foo() { 'foo ' + name }
       List run(O b) {
           Closure c = { -> foo() }
           [c.rehydrate(null, b, b)(), c.rehydrate(new D(), new D(), new D())()]
       }
   }
   new O(name: 'a').run(new O(name: 'b'))   // was [foo a, foo a]; the closure 
class gives foo b, then ClassCastException
   ```
   
   Seen in 6.0.0 too.
   
   On the non-strict path, `PackedClosure.rehydrate` now gives the copy the new 
this object as its receiver:
   - always, when the compiler found that the hoisted body uses its receiver 
(`this`, an instance field or property, an implicit-this instance method call 
other than an extension method, or an inner class creation). For an object that 
is not an instance of the enclosing class, the dispatch table's checkcast then 
throws the same `ClassCastException` as the closure class;
   - otherwise, only when the object is an instance of the receiver's class, 
which is always sound.
   
   A body that does not use its receiver keeps the original one for an 
unrelated this object, so rehydrating it onto a builder (the DSL idiom) keeps 
working. So does a null this object (`dehydrate`).
   
   `StaticTypesClosureWriter.hoistedBodyUsesReceiver` computes the bit. It 
reaches the adapter through a new ten-argument 
`ScriptBytecodeAdapter.packedClosure` (`@GroovyABI(since = "7.0.0")`, added to 
`compatibility/groovy-abi-surface.json`). The nine-argument factory stays for 
class files compiled by 6.0.0 and passes false, so those get the always-sound 
instance swap. A 6.0.x backport would need the patch version in the ABI 
annotation instead. The dynamic `@PackedClosures` trust path is not changed.
   
   Merging with #3182 (GROOVY-12661): `PackedClosure.rehydrate` conflicts; put 
#3182's `if (strict) { ... }` guard first, followed by this change's non-strict 
receiver swap.
   
   Two shapes the hoisted body cannot reproduce for a this object that is not 
an instance of the enclosing class: an implicit-this call to an `Object` method 
now throws `ClassCastException` instead of silently using the old receiver, and 
an implicit-this extension method that reads its self argument keeps the old 
receiver, because the `println`/`sprintf` family must keep working there. For 
an instance of the enclosing class both are correct.
   
   ## Tests
   
   
`PackedClosuresTransformTest.compileStaticRehydrateDispatchesToTheNewThisObject`
 (16 shapes against an instance, a subclass instance and a foreign object, 
compared with the same literals compiled unpacked), 
`rehydratedCompileStaticPackedClosureKeepsItsReceiverForNullThisObject` and 
`packedClosureFromTheEarlierFactoryRehydratesOntoAnInstanceOfItsReceiverClass`, 
and 
`ClosurePackCapabilityTest.autoPackedClosureRehydratedOntoAnotherOwnerUsesIt` 
and `syntacticallyPackedClosureRehydratedOntoAnInstanceOfItsOwnerClassUsesIt` 
fail without the change. The packing test classes, `groovy.lang.*Closure*`, 
`groovy.ClosureTest`, `org.codehaus.groovy.classgen.asm.sc.*` and 
`groovy.transform.stc.*` pass. The Checkstyle and CodeNarc gates pass.
   




> Rehydrating a @CompileStatic packed closure ignores the new this object and 
> keeps calling the original owner
> ------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12666
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12666
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 7.0.0-alpha-1, 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> {code:groovy}
> import groovy.transform.CompileStatic
> import groovy.transform.PackedClosures
> class D {}
> @CompileStatic
> @PackedClosures
> class O {
>     String name
>     String foo() { 'foo ' + name }
>     List run(O b) {
>         Closure c1 = { -> foo() }
>         Closure c2 = { -> name }
>         [c1.rehydrate(null, b, b)(), c2.rehydrate(null, b, b)(), 
> c1.rehydrate(new D(), new D(), new D())()]
>     }
> }
> println new O(name: 'a').run(new O(name: 'b'))
> {code}
> *Actual:* {{[foo a, a, foo a]}}. The same happens without the annotation when 
> compiling with {{-Dgroovy.target.closure.pack=true}}.
> *Expected*, as with the closure class compiled without packing: {{[foo b, 
> b]}}, then a ClassCastException from the third call, because D is not an O.
> A @CompileStatic closure class runs its body against getThisObject(). A 
> packed closure dispatches its hoisted method to the receiver captured at 
> construction, and Closure.rehydrate only replaces the visible owner, delegate 
> and this object, so the new this object is silently ignored.
> Verified on 6.0.0 and master.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to