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

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

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

   `rehydrate` and `dehydrate` on a closure wrapper did not reach the closure 
being wrapped:
   
   ```groovy
   class O { def foo() { 'o' } }
   class D { def foo() { 'd' } }
   def c = { x, y -> foo() + x + y }
   c.curry(1).rehydrate(new D(), new O(), new O())(2)
   // ClassCastException: class O cannot be cast to class groovy.lang.Closure
   ```
   
   `Closure.rehydrate` and `Closure.dehydrate` copy the closure and replace its 
`owner`, `delegate` and `thisObject` fields.
   
   - `CurriedClosure` and `Closure.WritableClosure` keep the wrapped closure in 
`owner`. Rehydrating replaced it with the new owner, and the next call failed 
with a ClassCastException. Dehydrating set it to null, and the next call failed 
with a NullPointerException.
   - `ComposedClosure` and `TrampolineClosure` call through their own fields. 
Rehydrating them changed nothing that is called, so methods still resolved 
against the original owner. Dehydrating them left that owner reachable, so the 
result could not be serialized, which is the reason `dehydrate` exists.
   
   Seen in 4.0.33, 5.0.8 and 6.0.0 too.
   
   Each of the four wrappers now overrides `dehydrate()` and `rehydrate(Object, 
Object, Object)`. The override applies the operation to the wrapped closure 
(both closures for a composition) and wraps the copy again, so the result 
behaves like rehydrating the original closure and then currying, composing, 
trampolining or making it writable. There is one commit per wrapper. Memoized 
closures are left out: their cache needs a separate decision.
   
   ## Tests
   
   These fail without the change:
   - `ClosureCurryTest.testRehydrateCurriedClosure` and 
`testDehydrateCurriedClosure`
   - `ClosureTest.testDehydrateAndRehydrateWritableClosure`
   - `ClosureComposeTest.testRehydrateComposedClosure` and 
`testDehydrateComposedClosure`
   - `TrampolineTest.testRehydrateTrampolinedClosure` and 
`testDehydrateTrampolinedClosure`
   
   They cover `curry`, `rcurry`, `ncurry` and nested curry, `DELEGATE_FIRST`, 
`>>` and `<<`, and serialize, deserialize and rehydrate round trips. The 
`groovy.lang.*Closure*`, `groovy.*Closure*`, 
`org.codehaus.groovy.runtime.*Closure*`, 
`org.codehaus.groovy.runtime.trampoline.*` and 
`org.codehaus.groovy.runtime.memoize.*` tests pass, as do `ClosuresSTCTest`, 
`DelegatesToSTCTest`, `ClosuresStaticCompileTest` and 
`PackedClosuresTransformTest`. The Checkstyle and CodeNarc gates pass.
   




> rehydrate() on a curried, writable, composed or trampolined closure throws 
> ClassCastException or ignores the new owner and delegate
> -----------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12665
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12665
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 4.0.33, 5.0.8, 7.0.0-alpha-1, 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> {code:groovy}
> class O { def foo() { 'o' } }
> class D { def foo() { 'd' } }
> def c = { x, y -> foo() + x + y }
> assert c.rehydrate(new D(), new O(), new O()).curry(1)(2) == 'o12'   // passes
> assert c.curry(1).rehydrate(new D(), new O(), new O())(2) == 'o12'   // fails
> {code}
> *Actual:* {{java.lang.ClassCastException: class O cannot be cast to class 
> groovy.lang.Closure}}, thrown at {{CurriedClosure.getOwner}} from 
> {{CurriedClosure.call}} (on 4.0.33 a MissingMethodException for O.doCall()).
> * {{c.curry(1).dehydrate()}} throws NullPointerException when called.
> * {{c.asWritable().rehydrate(...)}} throws the same ClassCastException.
> * For {{(c >> g).rehydrate(...)}} and {{c.trampoline().rehydrate(...)}} the 
> new owner and delegate are ignored: {{foo()}} still resolves against the 
> original owner.
> * {{dehydrate()}} on a composed or trampolined closure still holds the 
> original owner, so the result cannot be serialized.
> *Expected:* rehydrating or dehydrating a wrapper closure behaves like 
> rehydrating or dehydrating the closure it wraps and wrapping the copy.
> Cause: {{Closure.rehydrate}} and {{Closure.dehydrate}} replace only the 
> wrapper's own owner, delegate and thisObject fields. {{CurriedClosure}} and 
> {{Closure.WritableClosure}} keep the wrapped closure in owner, so it is 
> overwritten. {{ComposedClosure}} and {{TrampolineClosure}} call through 
> separate fields, so the wrapped closures are never rehydrated or dehydrated.
> Verified on 4.0.33, 5.0.8, 6.0.0 and master.



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

Reply via email to