You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Modification through a call result that is a field of the receiver (≺) does not reach the receiver #80
viaLoop: [-] --> - MOD[], parentunmodified. This is the unsafe direction.
On ws/maddi-null, getWrapper's summary is empty ([] --> -): the loop's reassignment of node drops the link. With #72 4a (ws/maddi-null-72) the summary is right: getWrapper≺this.parent, getWrapper≺this. The caller still gets nothing from it, though. w has no link to 0:parent after the call, so w.x = 1 does not reach parent.
Expected: w ≺ 0:parent (or 0:parent.parent) in the caller, and parent modified.
Seen in fernflower.ClassesProcessor.ClassNode.getWrapper() has exactly this shape. Callers like NestedClassProcessor.setLambdaVars(parent, child) modify parent.getWrapper().getMethods().getWithKey(..) and keep parent unmodified, on both sides.
On ws/maddi-null, the chain also leaks the callee's this: method ∈ this.methods.§es, where this is ClassWrapper's, untranslated. MODREACH then marked these static methods modifying. #72 4a removes the leak, and that accounts for six of its fernflower nonModifying false→true flips.
A likely place: WriteLinksAndModification.isInvalidFieldContainment. w ≺ 0:parent has a real local on the from side that is not structurally part of parent, so the filter drops it. Its NOTE records a group-aware relaxation that was tried and reverted (TestCast / TestInstanceOf, the "≺-family cluster"). A fix has to tell "the call result is a field of the receiver" apart from an accessor copy, probably at the call site, as the pattern-binding mark does for deconstruction. A related precedent is the comment on expression-scoped fields just below, parent.wrapperNode().map ≺ $rv ← parent.
Not started; I haven't verified this location yet.
Fixed on ws/maddi-null-80 (d526ad63, 14478b2b; gates NOCALLRESULTFIELD, NOGETTERRECEIVER): a call result ≺ its receiver keeps the containment through reduceLinks, and an inlined getter on a call result keeps the receiver's evaluation.
Open: a modified element of a collection reached from a call result, parent.getWrapper().getMethods().getWithKey(key).varproc.getThisVars().put(..) (fernflower NestedClassProcessor.setLambdaVars). method ∈ $rv.methods.list.§$s composed with $rv ≺ parent gives only method ≤ 0:parent.parent, and notLinkedToModified never carries a modification back over ≤. Adding ≥ as deep-only (tried, NOOBJECTGRAPHMOD) fixes it but makes read-only iteration deep-modifying (TestModificationThroughView) and loses a structural modification (TestKotlinAnalyzerControl.clearAll).
Idea: name the unknown field. getWrapper ≺ this says "a field of this, unknown which"; as a virtual field getWrapper ← this.§f the call site gets $rv ← 0:parent.§f, and everything downstream is an ordinary path rooted at parent (method ∈ 0:parent.§f.methods.list.§$s), which the existing ∋ completion carries back. The read-only iterator stays apart (list.iterator() gets ≤ from the library summary, never ≺). Prerequisite: #86 (the call-site path translation drops a level). Check first how the virtual-field matching treats §f (vf/virtual-fields.md): it must not make w ← parent.§f match every write parent.x = ...
TryHelper.addTryWithResourceJ11(..): nonModifying true → false (it calls DecompilerContext.getCounterContainer().getCounterAndIncrement(..), a modification through a call result)
field DecompilerContext.classProcessor: unmodified true → false
Still open, as before: NestedClassProcessor.setLambdaVars (the ≤ composition through a collection element; idea: name the unknown field as §f, after #86).
The fix costs 25 more trips and 20 more retries on fernflower; those add to #92.
The main case is merged into devel (6400774, 3601648; gates NOCALLRESULTFIELD, NOGETTERRECEIVER). The remaining case, setLambdaVars, moves to 96. The trips and retries the fix adds belong to #92.
Activity
bnaudts commentedon Oct 28, 2026
A likely place:
WriteLinksAndModification.isInvalidFieldContainment.w ≺ 0:parenthas a real local on thefromside that is not structurally part ofparent, so the filter drops it. Its NOTE records a group-aware relaxation that was tried and reverted (TestCast / TestInstanceOf, the "≺-family cluster"). A fix has to tell "the call result is a field of the receiver" apart from an accessor copy, probably at the call site, as the pattern-binding mark does for deconstruction. A related precedent is the comment on expression-scoped fields just below,parent.wrapperNode().map ≺ $rv ← parent.Not started; I haven't verified this location yet.
Link: a self-referential reassignment keeps the old value's links; lo…
bnaudts commentedon Oct 29, 2026
Fixed on ws/maddi-null-80 (d526ad63, 14478b2b; gates
NOCALLRESULTFIELD,NOGETTERRECEIVER): a call result≺its receiver keeps the containment throughreduceLinks, and an inlined getter on a call result keeps the receiver's evaluation.Open: a modified element of a collection reached from a call result,
parent.getWrapper().getMethods().getWithKey(key).varproc.getThisVars().put(..)(fernflower NestedClassProcessor.setLambdaVars).method ∈ $rv.methods.list.§$scomposed with$rv ≺ parentgives onlymethod ≤ 0:parent.parent, andnotLinkedToModifiednever carries a modification back over≤. Adding≥as deep-only (tried,NOOBJECTGRAPHMOD) fixes it but makes read-only iteration deep-modifying (TestModificationThroughView) and loses a structural modification (TestKotlinAnalyzerControl.clearAll).Idea: name the unknown field.
getWrapper ≺ thissays "a field of this, unknown which"; as a virtual fieldgetWrapper ← this.§fthe call site gets$rv ← 0:parent.§f, and everything downstream is an ordinary path rooted at parent (method ∈ 0:parent.§f.methods.list.§$s), which the existing∋completion carries back. The read-only iterator stays apart (list.iterator()gets≤from the library summary, never≺). Prerequisite: #86 (the call-site path translation drops a level). Check first how the virtual-field matching treats§f(vf/virtual-fields.md): it must not makew ← parent.§fmatch every writeparent.x = ...bnaudts commentedon Nov 3, 2026
Rebased onto the #72 stack + #89 fix; now on ws/maddi-null as 6400774 and 3601648 (gates
NOCALLRESULTFIELD,NOGETTERRECEIVER).Link suite 530/18 skipped/0 failed, analyzer 679/2/0.
Fernflower (PARALLEL=1), A = both gates set, B = the fix:
Modification verdicts that change, all toward modifying (the safe direction):
DomHelper.detectStatement(Statement): nonModifying true → falseTryHelper.addTryWithResourceJ11(..): nonModifying true → false (it callsDecompilerContext.getCounterContainer().getCounterAndIncrement(..), a modification through a call result)DecompilerContext.classProcessor: unmodified true → falseStill open, as before:
NestedClassProcessor.setLambdaVars(the≤composition through a collection element; idea: name the unknown field as§f, after #86).The fix costs 25 more trips and 20 more retries on fernflower; those add to #92.
Link: a call result its callee calls a field of the receiver keeps th…
Link: an inlined getter on a call result keeps its receiver's links a…
bnaudts commentedon Nov 5, 2026
The main case is merged into devel (6400774, 3601648; gates NOCALLRESULTFIELD, NOGETTERRECEIVER). The remaining case, setLambdaVars, moves to 96. The trips and retries the fix adds belong to #92.
Link engine: lazy materialization for '∩' orphans only; LAZYMATANY fo…
MODREACH: a call result that is part of a parameter carries its modif…