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

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

blackdrag commented on PR #3086:
URL: https://github.com/apache/groovy/pull/3086#issuecomment-6750516676

   Hibernate proxy rules as I understand what has to be there to support lazy 
init:
   1.  method must be not private, final or static.
   2.  fields are not supported using the subclass proxy.
   3. supporting package private and protected methods requires same package 
and class loader (and module), which requires ByteBuddy for example to go to 
quite some lengths to support that properly, including exposing a LookupObject 
that has full access to the entity.
   4. interface default methods may or may not be in the proxy, depending on 
settings. Technically they do not exist on the entity
   5. constructor is not on the proxy
   6. proxy will contain additional methods to support the interfaces it 
implements, which excludes them from the entity as possible method names
   7. equals (and hashcode) may require special handling.
   8. inherited parent methods for entity may call methods from entity
   
   In case of inheritance in Java we have these rules for the proxy
   1.  cannot be overwritten, private methods are not really inherited, but 
also not visible outside. Proxy does not contain them
   2. fields are inherited, if not private, but they break lazy init in the 
hibernate proxy. Proxy does not contain them.
   3. package private and protected methods are visible in the proxy, if in the 
same package and class loader.  Proxy must contain them.
   4. Proxy may contain them
   5. no entity constructor on the proxy
   6.  obviously the proxy will contain them, but that restricts available 
method names in the entity
   7.  assume I have a check `other.getClass() == MyEntity.class`, then a proxy 
in `other` would break. 
   8. need to create proxy methods for them. the field restriction also applies 
to parent classes
    
   What happens if we use such a proxy in Groovy? Because the proxy is a Groovy 
class it will proxy getMetaClass and deliver the entity meta class instead of 
the proxy metaclass.
   What would happen if metaClass(proxy) is the meta class for the proxy class? 
In that case we would see something similar to what Java sees, so all should be 
fine within the Java rules.  
   If entity has no normal meta class, or for example EMC with a method added? 
Then the meta class of proxy may not see that. 
   
   If the proxy uses the meta class of the entity it does proxy, then you would 
see methods added through runtime meta programming to entity also on proxy. It 
would not see additional methods on proxy, it would claim to have private 
methods and fields on proxy, that are not on proxy. It would report the 
constructors of entity, not the ones from proxy. A static method of the same 
name on proxy would be called instead of entity if used like an instance 
method. 
   
   Which reminds me of a chapter I should open here as well... that is 
closures. A closure in the entity would operate on the proxy instance. If that 
closure tries to call a private method/field on entity it my fail because it 
actually works on proxy but thinks it operates on entity.
   
   ```Groovy
   class X {
     private foo() {"foo X"}
     public bar() {def cl = {foo()}; cl()}
   }
   assert new X().bar() == "foo X"
   
   class Y extends X {
     private foo() {"foo Y"}
   }
   assert new Y().bar() == "foo Y"
   
   def y = new Y()
   def x = new X()
   y.metaClass = x.metaClass
   assert y.bar() == "foo X" // <

> Indy: call site keeps a method replaced on a metaclass the receiver shares 
> with another class
> ---------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12558
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12558
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 6.0.0
>            Reporter: James Daugherty
>            Priority: Major
>
> On Groovy 6 with invokedynamic, a call site whose receiver uses the metaclass 
> of another class keeps invoking a method after that metaclass replaces it. 
> Groovy 5, and Groovy 6 with classic call sites, invoke the replacement. This 
> is a regression from GROOVY-12191 (6.0.0-beta-1).
> Such receivers are common in tests: Spock's {{Mock()}} generates a subclass 
> of the mocked type whose {{getMetaClass()}} answers the mocked type's 
> metaclass, so a feature that replaces a method on the mock's metaclass 
> changes the {{ExpandoMetaClass}} of the mocked type, and many proxy libraries 
> behave the same way. A specification that replaces such a method in one 
> feature and runs the same code under test in a later feature gets the earlier 
> feature's closure: a {{0 * collaborator.update(*_)}} interaction fails with 
> "Too many invocations", or an expected exception is never thrown.
> *Reproducer:* 
> https://github.com/jdaugherty/groovy-indy-shared-metaclass-switchpoint-reproducer
>  ({{./run.sh 5.1.3 6.0.0}}). {{GreeterProxy extends Greeter}} and is given 
> the {{ExpandoMetaClass}} of {{Greeter}}, as a mock or proxy subclass is. One 
> call site invokes {{greet()}} and one {{+=}} site invokes {{plus()}}; each 
> method is replaced on the metaclass between two calls of the same site:
> {noformat}
> == Groovy 5.1.3
>    indy=true: method call after replacing the method:   second (OK)
>    indy=true: compound assignment after replacing plus: second (OK)
> == Groovy 6.0.0
>    indy=true: method call after replacing the method:   first (STALE: the 
> call site still invokes the replaced method)
>    indy=true: compound assignment after replacing plus: first (STALE: the 
> call site still invokes the replaced method)
>    indy=false: method call after replacing the method:   second (OK)
>    indy=false: compound assignment after replacing plus: second (OK)
> {noformat}
> *Cause:* GROOVY-12191 replaced the global indy SwitchPoint with per-class 
> domains. A call site is guarded by the SwitchPoint of the receiver's class 
> ({{Selector.MethodSelector.captureTokenAndGetMetaClass()}}, 
> {{IndyInvalidation.classSwitchPointFor(args[0])}}; likewise in 
> {{IndyCompoundAssign.resolve}}). A change to a metaclass retires the domain 
> of the class the metaclass belongs to, {{MetaClass.getTheClass()}} 
> ({{IndyInvalidation.switchPointForMetaClass}} / {{invalidateMetaClass}}). 
> When the receiver's class is not the metaclass's class, as for 
> {{GreeterProxy}} and the {{Greeter}} metaclass it uses, the change retires 
> {{Greeter}}'s domain while the site waits on {{GreeterProxy}}'s. The receiver 
> metaclass guard ({{SAME_MC}}) does not catch it either, because replacing an 
> {{ExpandoMetaClass}} method keeps the same metaclass instance.
> *Proposed fix:* when the selected metaclass belongs to a class other than the 
> receiver's, also capture that class's SwitchPoint before selection and guard 
> the site with both. That applies to {{Selector}} and to 
> {{IndyCompoundAssign}}, which captures its token the same way. A patch with 
> tests follows as a pull request against master, with a backport for the 6.0.x 
> line.



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

Reply via email to