Summary
setNodeLocations claims twice that it "ensures uniqueness of
BlockNode.Locations" (gnovm/pkg/gnolang/preprocess.go:6324-6325 @
33763e8269), SetBlockNode keys cacheNodes by Location with a bare
// XXX duplicate? (store.go:934-947), and the checker meant to enforce it,
checkNodeLinesLocations (preprocess.go:6350), is an empty // TODO: XXX
stub.
The invariant does not hold. else if breaks it.
Reproducer
package main
func main() {
x := 0
if x == 0 {
println("a")
} else if x == 1 {
println("b")
} else {
println("c")
}
}
Transcribing the file node and bucketing block nodes by GetLocation():
DUPLICATE LOCATION test/main.go:7:9-11:3 -> [*gnolang.IfCaseStmt *gnolang.IfStmt]
block nodes: 7, duplicate locations: 1
Cause
go2gno.go:589 sets the synthesized else-branch IfCaseStmt's span from
gon.Else, which for an else if is the inner IfStmt:
elseStmt := IfCaseStmt{Body: ess}
if gon.Else != nil {
setSpan(fs, gon.Else, &elseStmt)
}
Both block nodes then receive identical Location{PkgPath, File, Span}.
Impact
As far as I can tell, harmless today: neither colliding block can host a
declaration, so nothing currently disambiguates on their Locations in a way that
misbehaves. I have not audited every Location consumer, and SetBlockNode
silently overwrites on collision.
Filing because the invariant is load-bearing and growing more so — #6060 keys a
local type's TypeID to its declaring block's Location. A latent duplicate is a
landmine for future work, and the stub checker means a new one would go
unnoticed.
Summary
setNodeLocationsclaims twice that it "ensures uniqueness ofBlockNode.Locations" (
gnovm/pkg/gnolang/preprocess.go:6324-6325@33763e8269),SetBlockNodekeyscacheNodesby Location with a bare// XXX duplicate?(store.go:934-947), and the checker meant to enforce it,checkNodeLinesLocations(preprocess.go:6350), is an empty// TODO: XXXstub.
The invariant does not hold.
else ifbreaks it.Reproducer
Transcribing the file node and bucketing block nodes by
GetLocation():Cause
go2gno.go:589sets the synthesized else-branchIfCaseStmt's span fromgon.Else, which for anelse ifis the innerIfStmt:Both block nodes then receive identical
Location{PkgPath, File, Span}.Impact
As far as I can tell, harmless today: neither colliding block can host a
declaration, so nothing currently disambiguates on their Locations in a way that
misbehaves. I have not audited every Location consumer, and
SetBlockNodesilently overwrites on collision.
Filing because the invariant is load-bearing and growing more so — #6060 keys a
local type's TypeID to its declaring block's Location. A latent duplicate is a
landmine for future work, and the stub checker means a new one would go
unnoticed.