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

Paul King edited comment on GROOVY-11907 at 4/6/26 10:29 AM:
-------------------------------------------------------------

Claude's analysis:

h2. Summary

*Root Cause:* GROOVY-11817 moved `DYNAMIC_RESOLUTION` checking from 
method-level (in `StaticTypesWriterController.getCallSiteWriter()`) to 
per-expression (in `getCallSiteWriterFor(Expression)`). This works correctly 
for property expressions, which were wired up to use the new per-expression 
check. However, trait static field helpers generate a compound expression like:

{code}
(($static$self instanceof Class) ? $static$self : $static$self.getClass())
    .traitName__fieldName$get()
{code}

The outer `MethodCallExpression` (the field getter) is correctly marked with 
`DYNAMIC_RESOLUTION` by `TraitTypeCheckingExtension.makeDynamic()`, which also 
sets `DYNAMIC_RESOLUTION` on the enclosing method. The inner 
`$static$self.getClass()` sub-expression is NOT marked. Before GROOVY-11817, 
the method-level flag caused the dynamic call site writer to be used for all 
expressions in the method, including this sub-expression. After GROOVY-11817, 
the method-level check was removed, and the unmarked sub-expression falls 
through to the static call site writer. The mix of dynamic (outer) and static 
(inner) writers within the ternary produces bytecode with inconsistent stack 
frame types, causing `AssertionError` in ASM's `Frame.putAbstractType` during 
frame computation.

*Trigger conditions:* The bug manifests when compilation order causes 
`TraitTypeCheckingExtension.handleMissingMethod` to be invoked for the trait 
static field helper (triggering `makeDynamic`). This occurs when a compiler 
configuration script with an inline `CONVERSION` phase customizer is active 
together with another global AST transform (e.g. Spock's `SpockTransform`) that 
alters class processing order. Without these, the trait helper method resolves 
normally during type checking, `makeDynamic` is never called, all expressions 
use the static writer consistently, and no frame issue occurs.

*Fix:* In `TraitReceiverTransformer.asClass()`, mark the `getClass()` 
sub-expression with `DYNAMIC_RESOLUTION` directly when generating receiver 
expressions for static field access. This ensures 
`StaticInvocationWriter.makeCall()` delegates it to the dynamic writer, 
consistent with the outer field getter expression, regardless of whether the 
method-level flag is checked.

*Bisected to:* commit `920970323e` (GROOVY-11817) on the `GROOVY_5_0_X` branch.



was (Author: paulk):
h2. Summary

*Root Cause:* GROOVY-11817 moved `DYNAMIC_RESOLUTION` checking from 
method-level (in `StaticTypesWriterController.getCallSiteWriter()`) to 
per-expression (in `getCallSiteWriterFor(Expression)`). This works correctly 
for property expressions, which were wired up to use the new per-expression 
check. However, trait static field helpers generate a compound expression like:

{code}
(($static$self instanceof Class) ? $static$self : $static$self.getClass())
    .traitName__fieldName$get()
{code}

The outer `MethodCallExpression` (the field getter) is correctly marked with 
`DYNAMIC_RESOLUTION` by `TraitTypeCheckingExtension.makeDynamic()`, which also 
sets `DYNAMIC_RESOLUTION` on the enclosing method. The inner 
`$static$self.getClass()` sub-expression is NOT marked. Before GROOVY-11817, 
the method-level flag caused the dynamic call site writer to be used for all 
expressions in the method, including this sub-expression. After GROOVY-11817, 
the method-level check was removed, and the unmarked sub-expression falls 
through to the static call site writer. The mix of dynamic (outer) and static 
(inner) writers within the ternary produces bytecode with inconsistent stack 
frame types, causing `AssertionError` in ASM's `Frame.putAbstractType` during 
frame computation.

*Trigger conditions:* The bug manifests when compilation order causes 
`TraitTypeCheckingExtension.handleMissingMethod` to be invoked for the trait 
static field helper (triggering `makeDynamic`). This occurs when a compiler 
configuration script with an inline `CONVERSION` phase customizer is active 
together with another global AST transform (e.g. Spock's `SpockTransform`) that 
alters class processing order. Without these, the trait helper method resolves 
normally during type checking, `makeDynamic` is never called, all expressions 
use the static writer consistently, and no frame issue occurs.

*Fix:* In `TraitReceiverTransformer.asClass()`, mark the `getClass()` 
sub-expression with `DYNAMIC_RESOLUTION` directly when generating receiver 
expressions for static field access. This ensures 
`StaticInvocationWriter.makeCall()` delegates it to the dynamic writer, 
consistent with the outer field getter expression, regardless of whether the 
method-level flag is checked.

*Bisected to:* commit `920970323e` (GROOVY-11817) on the `GROOVY_5_0_X` branch.


> SC: trait static field helper generates invalid bytecode when method-level 
> DYNAMIC_RESOLUTION is present (GROOVY-11817 regression) 
> -----------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-11907
>                 URL: https://issues.apache.org/jira/browse/GROOVY-11907
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Priority: Major
>
> This is to track the *_grails-geb testFixtures_* error discussed here:
> [Canary] Grails 8 on Groovy 5 + Spring Boot 4 integration branch
> https://github.com/apache/grails-core/pull/15557



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

Reply via email to