codeconsole opened a new pull request, #3023:
URL: https://github.com/apache/groovy/pull/3023
When a dynamic property write carries a sender, Groovy needs to know whether
the receiver overrides the `setProperty` that `GroovyObject` provides.
- If the receiver has an override, the override receives the write.
- If it only has the interface default, the runtime calls the metaclass
itself, so the write can be resolved for the sender. That is how a superclass
writes its own private field on a subclass instance.
`ScriptBytecodeAdapter.setProperty` and `setGroovyObjectProperty` answered
that question by calling `Class.getMethod("setProperty", String, Object)` on
every write, and let any exception escape. The indy `SetPropertySelector` makes
the same check, documented as mirroring the adapter, but treats a failed lookup
as an override.
Reflection over a class can fail while the class itself works:
- In a GraalVM native image, `getMethod` on a class with no reflection
metadata throws `NoSuchMethodException`. Every dynamic write with a sender then
failed, indy writes included, even to an object whose own `setProperty` needs
no reflection. Applications such as Grails had to register every Groovy class
for reflection to work around it.
- On the JVM, a public method naming a type absent at run time makes
`getMethod` throw `NoClassDefFoundError`.
- Where reflection works, every write still paid for a method search and a
`Method` copy.
The three sites now share an internal helper,
`org.apache.groovy.internal.metaclass.GroovyObjectOverrides`, which caches the
answer per class in a `ClassValue`.
- Where reflection works, the answer is the same as before.
- Where it fails, the helper answers "overrides", as the selector already
did. The write goes to the object's own `setProperty`, which always reaches the
implementation the object really has. If that implementation is the interface
default, the write is resolved for the receiver's class instead of the sender.
`setGroovyObjectProperty`'s existing retry with the sender still covers a
superclass's private property.
- The opposite answer would bypass a real override.
No public API changes.
Tests:
- `GroovyObjectOverridesTest` covers the decision: the interface default, a
declared override, an inherited override, a re-declared override, and a class
whose public methods reflection cannot read.
- `SetPropertyDispatchTest` covers behavior under indy and classic
compilation: a custom `setProperty` intercepts static-name, dynamic-name and
`this` writes, and a class with the default has the write resolved for its
sender.
- `UnreadableClassSetPropertyTest` (Java) shows writes reaching a class's
own `setProperty` when reflection over the class fails. It fails on master with
`NoClassDefFoundError` from `ScriptBytecodeAdapter.setProperty`.
The runtime, indy, MOP, property and closure suites pass (5,010 tests), as
do the Checkstyle and CodeNarc gates.
A native-image probe built with GraalVM 25.2.4, using only the metadata
Groovy ships, writes to an unregistered class that overrides `setProperty`:
| Write path | master | this change |
|---|---|---|
| `ScriptBytecodeAdapter.setProperty` | `NoSuchMethodException` | reaches
the override |
| `setGroovyObjectProperty` | `NoSuchMethodException` | reaches the override
|
| dynamic-name write | `NoSuchMethodException` | reaches the override |
| indy `obj.x = v` | `NoSuchMethodException` | reaches the override |
In a rough JVM measurement, adapter writes went from about 85 to 21 ns for a
class with the default, and from 27 to 1.4 ns for a class with an override.
The read side has the same shape and is not changed here:
`ScriptBytecodeAdapter.getProperty` throws when the lookup fails, and
`PropertySelector` falls through to the metaclass.
There is no JIRA issue for this yet.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]