[
https://issues.apache.org/jira/browse/GROOVY-12664?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18137409#comment-18137409
]
ASF GitHub Bot commented on GROOVY-12664:
-----------------------------------------
codeconsole opened a new pull request, #3185:
URL: https://github.com/apache/groovy/pull/3185
GROOVY-11858 changed `ClosureMetaClass.invokeMethod` to defer a method found
on the closure's metaclass (an Object extension method such as `with`, `tap` or
`sprintf`, called with implicit this from the closure body), so that delegate
and owner are tried first. When neither can take the call, the fallback ran the
deferred method but did not return its result, and then threw
`MissingMethodException`:
```groovy
int count = 0
def c = { -> with { ++count } }
c.resolveStrategy = Closure.DELEGATE_ONLY
c.delegate = null
assert c() == 1 // 2 on 6.0.0
assert count == 1 // 2 on 6.0.0
```
From compiled code, the indy `MissingMethodException` handler retries
through `GroovyObject.invokeMethod`, so the method runs a second time. A direct
`MetaClass.invokeMethod(sender, ...)` call throws instead. The fallback now
returns the result, which restores the 4.0.x and 5.0.x behaviour. 6.0.0 is
affected.
## Tests
`ClosureDelegationTest.testClosureExtensionCalledOnceWhenDelegateAndOwnerUnavailable`
(null delegate, self delegate, `dehydrate`, `rehydrate` with `OWNER_ONLY`),
`testClosureExtensionCalledOnceWhenDelegateAndOwnerUnavailableTypeChecked` and
`testClosureExtensionResultReturnedByMetaClass` fail without the change.
`groovy.lang.*Closure*`, `groovy.*Closure*`, `org.codehaus.groovy.runtime.*`,
`gls.invocation.*`, `groovy.ThisAndSuperTest`, `groovy.transform.stc.*` and
`org.codehaus.groovy.classgen.asm.sc.*` pass. The Checkstyle and CodeNarc gates
pass.
> Implicit-this call to an Object extension method inside a closure runs twice
> when delegate and owner cannot take it
> -------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12664
> URL: https://issues.apache.org/jira/browse/GROOVY-12664
> Project: Groovy
> Issue Type: Bug
> Affects Versions: 7.0.0-alpha-1, 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> {code:groovy}
> int count = 0
> def c = { -> with { ++count } }
> c.resolveStrategy = Closure.DELEGATE_ONLY
> c.delegate = null
> def result = c()
> println "result=$result count=$count"
> {code}
> *Actual* (6.0.0 and master): {{result=2 count=2}}. The {{with}} block runs
> twice. Calling the metaclass directly, as
> {{c.metaClass.invokeMethod(this.getClass(), c, 'sprintf', ['%d', 42] as
> Object[], false, true)}}, throws MissingMethodException.
> *Expected* (as in 4.0.33 and 5.0.8): {{result=1 count=1}}, and the direct
> metaclass call returns "42".
> The same happens with a delegate that is the closure itself, after
> dehydrate() with DELEGATE_ONLY, after rehydrate(null, null, null) with
> OWNER_ONLY, and with @TypeChecked.
> Cause: GROOVY-11858 made {{ClosureMetaClass.invokeMethod}} defer a
> non-closure method so that delegate and owner are tried first. When neither
> can take the call, the deferred method is invoked but its result is not
> returned, and MissingMethodException is thrown. The indy fallback then
> retries through GroovyObject.invokeMethod and calls the method again.
> Verified: 6.0.0 and master affected; 4.0.33 and 5.0.8 not affected.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)