GROOVY-12109: Harden Closure deserialization against owner/delegate r…#2634
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens Groovy closure deserialization by adding a Closure.readResolve() validation step that detects owner/delegate/thisObject cycles (which can be forged via a crafted serialized stream) and rejects them, while adding tests to ensure legitimate closure serialization continues to work.
Changes:
- Add a
readResolve-based cycle check ingroovy.lang.Closureto reject cyclic closure graphs during deserialization. - Update
MethodClosure.readResolvevisibility to align with the newClosure#readResolve. - Add JUnit tests covering self-referential and mutual-cycle closures, plus “legitimate round-trip” cases.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/main/java/groovy/lang/Closure.java | Adds readResolve() cycle detection over owner/delegate/thisObject closure links. |
| src/main/java/org/codehaus/groovy/runtime/MethodClosure.java | Adjusts readResolve accessibility in response to the new superclass hook. |
| src/test/groovy/groovy/lang/ClosureSerializationCycleTest.groovy | Adds regression tests for cyclic closure graphs and validates normal serialization round-trips. |
Comments suppressed due to low confidence (1)
src/main/java/org/codehaus/groovy/runtime/MethodClosure.java:183
- MethodClosure overrides Closure#readResolve but never invokes the superclass implementation, so the new cycle-validation in Closure.readResolve is bypassed for MethodClosure instances whenever deserialization is allowed. Call super.readResolve() (and propagate ObjectStreamException) before applying the ALLOW_RESOLVE gate so MethodClosure gets the same validation as other closures.
protected Object readResolve() { // overrides Closure#readResolve (must be at least as accessible)
if (ALLOW_RESOLVE) {
return this;
}
throw new UnsupportedOperationException();
| @Serial | ||
| protected Object readResolve() throws ObjectStreamException { | ||
| // Iterative depth-first search over the raw owner/delegate/thisObject fields, following only |
| final Map<Closure<?>, Boolean> grey = new IdentityHashMap<>(); | ||
| final Set<Closure<?>> black = Collections.newSetFromMap(new IdentityHashMap<>()); | ||
| final Deque<Object> stack = new ArrayDeque<>(); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2634 +/- ##
==================================================
+ Coverage 68.5180% 68.5202% +0.0021%
- Complexity 33597 33605 +8
==================================================
Files 1519 1519
Lines 127384 127418 +34
Branches 23123 23130 +7
==================================================
+ Hits 87281 87307 +26
- Misses 32355 32358 +3
- Partials 7748 7753 +5
🚀 New features to boost your workflow:
|
✅ All tests passed ✅🏷️ Commit: d7d7902 Learn more about TestLens at testlens.app. |
|
Merged. Thanks. |
…eference cycle