[
https://issues.apache.org/jira/browse/GROOVY-12045?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Eric Milles resolved GROOVY-12045.
----------------------------------
Fix Version/s: 6.0.0-alpha-2
Resolution: Fixed
https://github.com/apache/groovy/commit/15c1b26983cb0acf41345a327eddb357ff87d36b
> Calling an enclosing-class instance method on a static nested class instance
> throws IllegalArgumentException instead of MissingMethodException
> ----------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12045
> URL: https://issues.apache.org/jira/browse/GROOVY-12045
> Project: Groovy
> Issue Type: Bug
> Components: groovy-runtime
> Affects Versions: 6.0.0-alpha-1
> Reporter: Leonard Brünings
> Assignee: Eric Milles
> Priority: Major
> Fix For: 6.0.0-alpha-2
>
>
> This was discovered in the Spock Groovy 6 [integration
> branch|https://github.com/spockframework/spock/pull/2356].
> The analysis was done by Claude, but it seems reasonable to me.
> I've verified that the reproducers work.
> h2. Summary
> Invoking an enclosing-class **instance** method on an instance of a **static
> nested class**
> throws `IllegalArgumentException: object is not an instance of declaring
> class` instead of the
> expected `MissingMethodException`. Regression introduced by the outer-method
> resolution added in
> GROOVY-11823 / GROOVY-11858 (the new `getNonClosureOuter` helper, `@since
> 6.0.0`).
> h2. Environment
> Groovy 6.0.0-alpha-1 and current master (`f6e2248d12`), JDK 17.
> h2. Description
> When a method is not found on a static nested class, Groovy now tries to
> resolve it on the
> *enclosing* class. For a static nested class there is no enclosing
> **instance**, so the runtime
> falls back to the enclosing **Class** object and then tries to invoke the
> (instance) method on
> that Class — which fails with a raw `IllegalArgumentException` from
> reflection rather than a
> clean `MissingMethodException`.
> This also breaks delegate-based dispatch: a closure with {{resolveStrategy =
> DELEGATE_FIRST}}
> whose delegate is a static nested class no longer falls through to the owner,
> because the
> exception thrown while probing the delegate is not a
> {{MissingMethodException}}.
> h3. Steps to reproduce
> {code:groovy}
> class Outer {
> void foo() { println "foo() on ${this.class.simpleName}" }
> static class StaticInner {}
> void run() {
> // (1) direct call of an outer *instance* method on a static-nested-class
> instance
> println "--- direct ---"
> try { new StaticInner().foo() } catch (Throwable t) { println "
> ${t.class.simpleName}: ${t.message}" }
> // (2) same thing via a DELEGATE_FIRST closure (delegate = static nested
> class, owner = Outer)
> println "--- closure DELEGATE_FIRST ---"
> Closure c = { foo() }
> c.delegate = new StaticInner()
> c.resolveStrategy = Closure.DELEGATE_FIRST
> try { c() } catch (Throwable t) { println " ${t.class.simpleName}:
> ${t.message}" }
> }
> }
> new Outer().run()
> {code}
> h3. Expected (Groovy 5.0.x)
> {noformat}
> --- direct ---
> MissingMethodException: No signature of method: foo for class:
> Outer$StaticInner ...
> --- closure DELEGATE_FIRST ---
> foo() on Outer
> {noformat}
> The static nested class has no {{foo}}, so the direct call misses cleanly,
> and the
> DELEGATE_FIRST closure falls through from the delegate to the owner
> ({{Outer}}).
> ### Actual (Groovy 6.0.0-alpha-1 and master)
> {noformat}
> --- direct ---
> IllegalArgumentException: object is not an instance of declaring class
> --- closure DELEGATE_FIRST ---
> IllegalArgumentException: object is not an instance of declaring class
> {noformat}
> h2. Root cause
> In {{groovy.lang.MetaClassImpl}} (master `f6e2248d12`):
> * {{invokeOuterMethod(...)}} (line ~1328) resolves a not-found method against
> the enclosing class
> and invokes it via {{omc.invokeMethod(outerClass, target, methodName,
> ...)}} where
> {{target = getOuterReference(sender, object)}}.
> * {{getOuterReference(Class innerClass, Object object)}} (line ~1344): for a
> **static** nested
> class the {{this$0}} branch is skipped (line ~1347 guards on {{(modifiers &
> ACC_STATIC) == 0}}),
> so {{outer}} stays {{null}} and it falls back to
> {{outer = getNonClosureOuter(innerClass)}} (line ~1361), which returns the
> enclosing
> **`Class`** object (line ~1369, {{@since 6.0.0}}).
> * {{invokeOuterMethod}} then invokes the enclosing **instance** method with
> the `Class` object as
> the receiver, so reflection throws
> {{IllegalArgumentException: object is not an instance of declaring class}}.
> A static nested class has no enclosing instance, so an enclosing **instance**
> method is simply not
> applicable and should yield a {{MissingMethodException}} (allowing
> DELEGATE_FIRST/owner fallback to
> proceed). Only enclosing **static** methods are legitimately callable via the
> `Class` target.
> h2. Impact
> Breaks frameworks that rely on closure delegate/owner fallback. Spock's
> {{with}} / {{verifyAll}}
> blocks dispatch implicit-`this` method conditions on the block closure
> (DELEGATE_FIRST). When the
> {{with}} target is a static nested class and the condition calls a method
> declared on the spec
> (the owner), Groovy 6 throws instead of resolving the owner method.
> Spock reproducer: `org.spockframework.smoke.WithBlocks."with works with void
> methods"`.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)