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