Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions compiler/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,15 @@ func (cfg *CFG) typedStatementEvents(stmt ast.Statement, reads []VarEvent, effec
}
events = append(events, VarEvent{Name: target.Value, Kind: Read, Token: target.Tok()})
}
// A callee that reads its seed observes an existing destination. A fresh
// destination supplies a zero seed, so there is nothing to read.
for _, targetIndex := range effect.CalleeReadsSeed {
target := let.Name[targetIndex]
if !cfg.isDefined(target.Value) {
continue
}
events = append(events, VarEvent{Name: target.Value, Kind: Read, Token: target.Tok()})
}
for _, write := range effect.Writes {
target := let.Name[write.TargetIndex]
var kind EventType
Expand Down
87 changes: 87 additions & 0 deletions compiler/cfg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,48 @@ func getValidTestCases() []cfgTestCase {
name: "Failable Value Protects Only Its Own Destination",
input: "x = 7\na = 10\na, b = x < 5, 30\na, b",
},
{
// The callee always writes, but its value depends on the incoming
// seed, so the prior value is read rather than overwritten.
name: "Seed Dependent Call Reads Prior Value",
code: `res = maybeIncrement(x)
res = x > 0 x
res = res + 1`,
input: "x = 20\nx = maybeIncrement(-1)\nx",
},
{
// The dependency composes through a wrapper whose only statement
// forwards the output to a seed-reading callee.
name: "Nested Seed Dependent Call Reads Prior Value",
code: `res = maybeIncrement(x)
res = x > 0 x
res = res + 1

res = outer(x)
res = maybeIncrement(x)`,
input: "x = 20\nx = outer(-1)\nx",
},
{
// Inside a body, the inner call reads the reset value, which keeps
// that unconditional write live.
name: "Reset Before Seed Dependent Call Inside Body",
code: `res = maybeIncrement(x)
res = x > 0 x
res = res + 1

res = resetThenIncrement(x)
res = 0
res = maybeIncrement(x)`,
input: "x = resetThenIncrement(-1)\nx",
},
{
// An indirect output reads its destination-seeded staging slot.
name: "Indirect Seed Dependent Call Reads Prior Value",
code: `s = maybeTag(n, t)
s = n > 0 t
s = s ⊕ "!"`,
input: "w = \"hi\"\nw = maybeTag(-1, \"x\")\nw",
},
}
}

Expand Down Expand Up @@ -210,6 +252,51 @@ func getErrorTestCases() []cfgTestCase {
input: "x = 7\nx = alwaysWrite(0:2)\nx",
errorContains: `unconditional assignment to "x" overwrites a previous value that was never used`,
},
{
// A definite overwrite before the read removes the seed dependency,
// so the prior value really is unused.
name: "Overwrite Before Seed Read Still Overwrites Prior Value",
code: `res = overwrite(x)
res = x
res = res + 1`,
input: "x = 7\nx = overwrite(3)\nx",
errorContains: `unconditional assignment to "x" overwrites a previous value that was never used`,
},
{
// The nested read observes the reset, never the caller's value.
name: "Reset Before Nested Seed Read Overwrites Prior Value",
code: `res = maybeIncrement(x)
res = x > 0 x
res = res + 1

res = resetThenIncrement(x)
res = 0
res = maybeIncrement(x)`,
input: "x = 7\nx = resetThenIncrement(-1)\nx",
errorContains: `unconditional assignment to "x" overwrites a previous value that was never used`,
},
{
// A heap destination cannot seed a static-string output: the
// callee's staging slot gets an ABI-typed zero seed, so the
// destination's value is never observed and the overwrite is real.
name: "Incompatible Storage Seed Does Not Reach Callee",
code: `s = readStatic(n)
s = n > 0 "x"
"seen <-s>"
s = "done"`,
input: "value = \"never\" ⊕ \"read\"\nvalue = readStatic(0)\nvalue",
errorContains: `unconditional assignment to "value" overwrites a previous value that was never used`,
},
{
// A fresh destination supplies a zero seed; the callee's read does
// not make the result used.
name: "Fresh Seed Dependent Result Is Unused",
code: `res = maybeIncrement(x)
res = x > 0 x
res = res + 1`,
input: "x = maybeIncrement(-1)",
errorContains: `value assigned to "x" is never used`,
},
{
// A || yields whenever its final fallback does, so the resolver
// boundary holds and this write is unconditional.
Expand Down
Loading
Loading