[
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)