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