[ 
https://issues.apache.org/jira/browse/GROOVY-12045?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Eric Milles reassigned GROOVY-12045:
------------------------------------

    Assignee: Eric Milles

> 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: class generator, groovy-runtime
>    Affects Versions: 6.0.0-alpha-1
>            Reporter: Leonard Brünings
>            Assignee: Eric Milles
>            Priority: Major
>
> 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)

Reply via email to