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

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

paulk-asert opened a new pull request, #2767:
URL: https://github.com/apache/groovy/pull/2767

   …es unemitted call-site helper under indy=false




> Interface default method without dynamic calls references unemitted call-site 
> helper under indy=false
> -----------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12235
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12235
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> An interface {{default}} method whose body contains *no* dynamic code (e.g. 
> {{return null}}) is broken under {{indy=false}}. GROOVY-11982 routes the 
> call-site array prologue to the synthetic helper class ({{MyInterface$1}}), 
> but the helper is only materialised when at least one named call site is 
> registered. The prologue ({{INVOKESTATIC $getCallSiteArray()}}) is emitted 
> unconditionally for every method body, so a default method with zero dynamic 
> calls ends up with a dangling {{Methodref}} to a class that is never 
> generated, and the first invocation throws {{NoClassDefFoundError: 
> MyInterface$1}}.
> Reproducer:
> {code:groovy}
> import org.codehaus.groovy.control.CompilerConfiguration
> def config = new CompilerConfiguration()
> config.optimizationOptions.put('indy', false)
> new GroovyShell(config).evaluate '''
>     interface MyInterface {
>         default Object defaultValue() {
>             return null
>         }
>     }
>     class MyImpl implements MyInterface {
>     }
>     assert new MyImpl().defaultValue() == null
> '''
> {code}
> Expected: assertion passes.
> Actual (master / 5.0.x / 5.1.x with the GROOVY-11982 fix):
> {noformat}
> java.lang.NoClassDefFoundError: MyInterface$1
>       at MyInterface.defaultValue(Script1.groovy)
> {noformat}
> On releases predating the GROOVY-11982 fix (e.g. 5.0.4) the same shape fails 
> with {{IncompatibleClassChangeError}} instead, because the prologue's owner 
> is the interface itself — so this shape has never worked under 
> {{indy=false}}; the fix only changed the failure mode.
> Root cause: {{AsmClassGenerator.visitStdMethod}} calls 
> {{CallSiteWriter.makeSiteEntry()}} before the method body is visited, so the 
> prologue cannot know the body will register no call sites. The guard in 
> {{AsmClassGenerator.createInterfaceSyntheticStaticFields}} checks 
> {{getCallSites().isEmpty()}}, which only reflects *named* call sites, and 
> skips emitting the helper.
> Suggested fix: materialise the helper whenever a prologue was actually 
> emitted for the interface (e.g. track a flag in 
> {{CallSiteWriter.makeSiteEntry()}}), not only when named call sites were 
> registered. A helper with an empty {{CallSite[]}} array is valid. This also 
> covers static interface methods with no dynamic code.
> The existing {{Groovy11982.groovy}} tests all use default methods *with* 
> dynamic bodies (GStrings, dynamic calls), which is why this gap went 
> unnoticed.
> Real-world impact: hit by the Grails 9 / Groovy 6 canary (apache/grails-core 
> PR 15558) — {{grails.core.GrailsApplicationLifeCycle#beanRegistrar()}} 
> ({{default ... return null}}) broke app boot under {{-PgrailsIndy=false}}; 
> Grails worked around it by converting the interface to Java (commit 
> 20e8ec71d7e4). The workaround row can be removed once this is fixed.
> Should be backported to 5.1.x and 5.0.x, which carry the same residual gap 
> via the GROOVY-11982 backport.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to