From 5137d273e5356e4377247d79740b8736f1fcfece Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Mon, 3 Aug 2026 05:51:16 -0400 Subject: [PATCH] fix #25993; In-place object construction zeroes destination before evaluating self-referencing field values (#25994) --- compiler/aliases.nim | 109 +++++++++++++++++++----- compiler/ccgcalls.nim | 4 +- compiler/ccgexprs.nim | 2 +- tests/ccgbugs/tobjconstr_self_alias.nim | 85 ++++++++++++++++++ 4 files changed, 177 insertions(+), 23 deletions(-) create mode 100644 tests/ccgbugs/tobjconstr_self_alias.nim diff --git a/compiler/aliases.nim b/compiler/aliases.nim index fa1167753f..6877028c3a 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -21,6 +21,44 @@ type TAnalysisResult* = enum arNo, arMaybe, arYes + PartFlag* = enum + pfStructural ## use structural prefix-chain detection and tree-walk + pfBidirectional ## also check reverse direction per field in nkObjConstr + +func sameLocation(a, b: PNode): bool = + template sameConstIndex(a, b: PNode): bool = + a.kind in nkLiterals and b.kind in nkLiterals and a.intVal == b.intVal + var a = a + var b = b + while a.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: a = a[1] + while b.kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv}: b = b[1] + if a.kind != b.kind: return false + case a.kind + of nkSym: result = a.sym.id == b.sym.id + of nkDotExpr, nkCheckedFieldExpr: + result = a[1].kind == nkSym and b[1].kind == nkSym and + sameLocation(a[0], b[0]) and a[1].sym.id == b[1].sym.id + of nkBracketExpr: + result = sameLocation(a[0], b[0]) and sameConstIndex(a[1], b[1]) + of nkObjUpConv, nkObjDownConv, nkDerefExpr, nkHiddenDeref: + result = sameLocation(a[0], b[0]) + else: result = false + +proc isAccessorPrefixOf(a, b: PNode): bool = + var cur = b + while cur.kind in {nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv, + nkObjDownConv, nkHiddenDeref, nkDerefExpr, + nkHiddenStdConv, nkHiddenSubConv, nkConv}: + if sameLocation(cur, a): return true + case cur.kind + of nkDotExpr, nkBracketExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv, + nkHiddenDeref, nkDerefExpr: + cur = cur[0] + of nkHiddenStdConv, nkHiddenSubConv, nkConv: + cur = cur[1] + else: discard + result = sameLocation(cur, a) + proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult proc isPartOfAux(n: PNode, b: PType, marker: var IntSet): TAnalysisResult = @@ -70,14 +108,28 @@ proc isPartOf(a, b: PType): TAnalysisResult = # watch out: parameters reversed because I'm too lazy to change the code... result = isPartOfAux(b, a, marker) -proc isPartOf*(a, b: PNode): TAnalysisResult = - ## checks if location `a` can be part of location `b`. We treat seqs and - ## strings as pointers because the code gen often just passes them as such. +proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult = + ## Checks if location `a` can be part of location `b`: i.e. whether writing to + ## `b` could affect what `a` reads. We treat seqs and strings as pointers + ## because the code gen often just passes them as such. ## ## Note: `a` can only be part of `b`, if `a`'s type can be part of `b`'s ## type. Since however type analysis is more expensive, we perform it only ## if necessary. ## + ## When `pfStructural` is set additional aliasing is detected: + ## * a structural prefix of an accessor chain is considered part of it + ## (e.g. `x.f <| x.f.g`). Normally `x.f !<| x.f.g` because the + ## same-kind `nkDotExpr` comparison treats the differing field names as + ## siblings, but `pfStructural` walks the chain to recognise the + ## relationship. + ## * Unrecognised node kinds are traversed recursively. + ## + ## When `pfBidirectional` is set: + ## * In `nkObjConstr` the reverse direction `isPartOf(value, a)` is also + ## checked per field value so that reads hidden behind calls/closures + ## are detected. + ## ## cases: ## ## YES-cases: @@ -86,13 +138,14 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = ## x[] <| x ## x[i] <| x ## x.f <| x + ## x.f <| x.f.g # when pfStructural (prefix chain) ## ``` ## ## NO-cases: ## ``` ## x !<| y # depending on type and symbol kind ## x[constA] !<| x[constB] - ## x.f !<| x.g + ## x.f !<| x.g # sibling fields at same level ## x.f !<| y.f iff x !<= y ## ``` ## @@ -121,7 +174,7 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = else: result = arNo of nkBracketExpr: - result = isPartOf(a[0], b[0]) + result = isPartOf(a[0], b[0], flags) if a.len >= 2 and b.len >= 2: # array accesses: if result == arYes and isDeepConstExpr(a[1]) and isDeepConstExpr(b[1]): @@ -131,7 +184,11 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = var y = if b[1].kind == nkHiddenStdConv: b[1][1] else: b[1] if sameValue(x, y): result = arYes + elif pfStructural in flags and isAccessorPrefixOf(a, b): + result = arYes else: result = arNo + elif pfStructural in flags and isAccessorPrefixOf(a, b): + result = arYes # else: maybe and no are accurate else: # pointer derefs: @@ -139,22 +196,25 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = if isPartOf(a.typ, b.typ) != arNo: result = arMaybe of nkDotExpr: - result = isPartOf(a[0], b[0]) + result = isPartOf(a[0], b[0], flags) if result != arNo: # if the fields are different, it's not the same location if a[1].sym.id != b[1].sym.id: - result = arNo + if pfStructural in flags and isAccessorPrefixOf(a, b): + result = arYes + else: + result = arNo of nkHiddenDeref, nkDerefExpr: - result = isPartOf(a[0], b[0]) + result = isPartOf(a[0], b[0], flags) # weaken because of indirection: if result != arYes: if isPartOf(a.typ, b.typ) != arNo: result = arMaybe of nkHiddenStdConv, nkHiddenSubConv, nkConv: - result = isPartOf(a[1], b[1]) + result = isPartOf(a[1], b[1], flags) of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr: - result = isPartOf(a[0], b[0]) + result = isPartOf(a[0], b[0], flags) else: result = arNo # Calls return a new location, so a default of ``arNo`` is fine. else: @@ -167,31 +227,31 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = case b.kind of Ix0Kinds: # a* !<| b.f iff a* !<| b - result = isPartOf(a, b[0]) + result = isPartOf(a, b[0], flags) of DerefKinds: # a* !<| b[] iff result = arNo if isPartOf(a.typ, b.typ) != arNo: - result = isPartOf(a, b[0]) + result = isPartOf(a, b[0], flags) if result == arNo: result = arMaybe of Ix1Kinds: # a* !<| T(b) iff a* !<| b - result = isPartOf(a, b[1]) + result = isPartOf(a, b[1], flags) of nkSym: # b is an atom, so we have to check a: case a.kind of Ix0Kinds: # a.f !<| b* iff a.f !<| b* - result = isPartOf(a[0], b) + result = isPartOf(a[0], b, flags) of Ix1Kinds: - result = isPartOf(a[1], b) + result = isPartOf(a[1], b, flags) of DerefKinds: if isPartOf(a.typ, b.typ) != arNo: - result = isPartOf(a[0], b) + result = isPartOf(a[0], b, flags) if result == arNo: result = arMaybe else: result = arNo @@ -199,20 +259,29 @@ proc isPartOf*(a, b: PNode): TAnalysisResult = of nkObjConstr: result = arNo for i in 1.. 0: - result = isPartOf(a, b[0]) + result = isPartOf(a, b[0], flags) else: result = arNo - else: result = arNo + else: + if pfStructural in flags: + for i in 0..