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

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

paulk-asert commented on PR #3169:
URL: https://github.com/apache/groovy/pull/3169#issuecomment-6595793782

   This spike explores the opposite direction to #3137. Instead of rejecting 
nest-mate private access under `@TypeChecked`, it makes dynamic dispatch reach 
a private field or method of a superclass from a subclass nested with it, as 
statically compiled code already does.
   
   The fallback in `MetaClassImpl` runs only when the caller's own view has no 
such member, and only if all of these hold:
   - the caller (for a closure, its enclosing class) is a subclass of the 
declaring class, and the receiver is an instance of the caller;
   - the caller and the declaring class are nest-mates;
   - the caller is not an inner class whose chain of enclosing instances leads 
to the declaring class. That case keeps resolving to the enclosing instance 
(GROOVY-12533).
   
   So a nest-mate outside the hierarchy still can't reach the member through a 
subclass instance, which keeps GROOVY-10198's "private members aren't 
inherited". A per-metaclass flag keeps the `methodMissing` / `propertyMissing` 
miss path at a field read; I measured no difference from master. The selected 
suites pass: `groovy.lang`, `o.c.g.runtime`, STC/SC, `gls`, `bugs`, 
`groovy.util`, `o.c.g.transform` and `vmplugin`, 14,232 tests in all.
   
   **Known gap:** the sender-less property entry points name the receiver's 
class as the caller. Those are `getProperty(Object, String)`, 
`getAttribute(Object, String)` and `InvokerHelper.getProperty`. So a caller 
outside the nest can still reach the field through them. Closing that properly 
needs the caller (ideally its `Lookup`) carried through the MOP, which fits 
with GEP-31's lexical, call-site-carried realm context.
   
   `Base` declares `private String z` and `private String pm()`. Java results 
are from `javac` (lambdas stand in for closures). "runtime failure" means 
`MissingPropertyException`, `MissingMethodException` or `MissingFieldException`.
   
   | Access | Java | master (dyn / TC) | spike (dyn / TC) | CS |
   |---|---|---|---|---|
   | **nested `static class Sub extends Base`** | | | | |
   | `this.z` | ✗ private access | runtime failure | ✓ | ✓ |
   | bare `z` | ✗ static context | runtime failure | ✓ | ✓ |
   | `super.z` | ✓ | runtime failure | ✓ | ✓ |
   | `((Base) this).z` | ✓ | runtime failure | ✓ | ✓ |
   | `that.z` (`Sub that`) | ✗ | runtime failure | ✓ | ✓ |
   | closure/lambda `this.z` / `super.z` | ✗ / ✓ | runtime failure | ✓ | ✓ |
   | write `this.z = 'y'` | ✗ | runtime failure | ✓ | ✓ |
   | `this.@z` | n/a | ✓ | ✓ | ✓ |
   | `this.pm()` | ✗ cannot find symbol | runtime failure | ✓ | ✓ |
   | `super.pm()` (also in a closure/lambda) | ✓ | runtime failure | ✓ | ✓ |
   | bare `pm()` in a closure/lambda | ✗ static context | runtime failure | ✓ | 
✓ |
   | **`SubSub extends Sub`** | | | | |
   | `this.z` / `((Base) this).z` | ✗ / ✓ | runtime failure | ✓ | ✓ |
   | **`Other`, a nest-mate but not a subclass** | | | | |
   | `sub.z` / `sub.pm()` / `sub.@z` | ✗ | runtime failure | runtime failure | 
✓ |
   | `((Base) sub).z` / `((Base) sub).pm()` | ✓ | runtime failure | runtime 
failure | ✓ |
   | **class outside the nest** | | | | |
   | `sub.z` | ✗ | runtime failure | runtime failure | compile error |
   | **`Sub` declares its own `getZ()` / `propertyMissing`** | n/a | `getZ` / 
`missing z` | `getZ` / `x` | `getZ` / `x` |
   | **inner (non-static) `Sub`** | | | | |
   | bare `z` | ✓ the enclosing instance's | enclosing instance | enclosing 
instance | enclosing instance |
   | `this.z` | ✗ | enclosing instance | enclosing instance | enclosing 
instance |
   
   Java decides by the static type of the qualifying expression: `super`, or a 
cast to `Base`, is allowed; a subclass-typed receiver is not. Dynamic dispatch 
only sees the runtime class and the caller, so it can't follow that rule 
exactly. Within the hierarchy the spike is more permissive than Java. The 
`Other` + cast rows are the one place it's stricter, and there only 
compile-time resolution (CS, or a direct-invocation tier in GEP-31) could give 
Java's answer.
   
   **Behaviour change to note:** a nested subclass with `propertyMissing` now 
reads its nest-mate superclass's private field instead of getting the 
`propertyMissing` call. That matches what CS does.
   
   **Possible paths forward**
   
   1. **6.0.x:** keep the current runtime behaviour, and have type checking 
report the access instead of letting it fail at run time. That's #3137, ideally 
narrowed later to match this spike's rule (reject only callers outside the 
receiver's hierarchy) and extended to private methods. No dynamic semantics 
change in a patch line.
   2. **Groovy 7, now:** turn this spike into a real change. Add the matrix as 
a regression test, close the property gap by giving the sender-less property 
entry points the same treatment as `invokeMethod(Object, String, Object[])`, 
and add a COMPATIBILITY.md entry for the `propertyMissing` precedence change. 
Then drop the #3137 rule on master, since TC's nest-mate leniency becomes 
correct.
   3. **Groovy 7, via GEP-31:** leave dynamic semantics alone for now. Instead, 
propose that realm-aware call sites carry the caller's `Lookup` alongside the 
realm token. That gives a reliable caller identity, and nest-mate private 
access through the JVM's own access checks, without `setAccessible`. 
Direct-invocation tiers would also cover the cast cases dynamic dispatch can't 
see. The matrix above would be the acceptance table, and `GROOVY_DYN` would 
inherit whichever rule we settle on.
   
   My preference: (1) for 6.0.x regardless. For 7, (2) is a self-contained 
improvement available now, but it changes `GROOVY_DYN` semantics, which (3) 
might then revise again. I'd only take (2) if GEP-31 is unlikely to land in the 
7 timeframe.




> STC accepts access to a private superclass field through a nested subclass 
> under @TypeChecked, which fails at run time
> ----------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12610
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12610
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 4.0.33, 5.0.8, 6.0.0
>            Reporter: Scott Murphy Heiberg
>            Priority: Major
>
> {code:groovy}
> class Base {
>     private String z = 'x'
>     @groovy.transform.TypeChecked
>     static class Sub extends Base {
>         def m() { this.z }   // also: z, super.z, closures, lambdas, writes, 
> that.z
>     }
> }
> new Base.Sub().m()
> {code}
> Actual: the code compiles, then fails with MissingPropertyException: No such 
> property: z for class: Base$Sub. Dynamic Groovy fails the same way.
> Expected: under @TypeChecked, a compile error "No such property: z for class: 
> Base$Sub", as already reported when Base and Sub are both nested in another 
> class. @CompileStatic keeps accepting the code, because it accesses the field 
> directly from a nest-mate.
> Cause: the type checker uses the nest-mate accessibility rule of static 
> compilation for @TypeChecked code too. Type-checked code is dispatched 
> dynamically, and the metaclass of a subclass does not list a private field of 
> its superclass.
> Seen in 4.0.33, 5.0.8 and 6.0.0.
> PR: https://github.com/apache/groovy/pull/3137



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to