Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

899 lines
24 KiB
Go

package cfg
import (
"strings"
"testing"
)
// ---------------------------------------------------------------------------
// Python
// ---------------------------------------------------------------------------
func TestPythonElifChainAndLoops(t *testing.T) {
c := mustBuild(t, `def f(a):
x = 0
if a > 10:
x = 1
elif a > 5:
x = 2
else:
x = 3
while x > 0:
x -= 1
if x == 2:
break
else:
continue
return x
`, "python")
for _, want := range []EdgeLabel{LabelTrue, LabelFalse, LabelLoopBack, LabelBreak, LabelContinue, LabelReturn} {
if !hasEdgeLabel(c, want) {
t.Errorf("missing %s edge", want)
}
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
ch := chainFor(t, r, ret.Index, "x")
// All three branch defs and the loop decrement can reach the
// return; the zeroth def is killed on every if path... but the
// elif chain has an else, so x = 0 cannot survive — yet the
// while may run zero times, so branch defs survive.
d1 := stmtByText(t, c, "x = 1")
d2 := stmtByText(t, c, "x = 2")
d3 := stmtByText(t, c, "x = 3")
dec := stmtByText(t, c, "x -= 1")
for _, d := range []*Statement{d1, d2, d3, dec} {
if !containsInt(ch.Defs, d.Index) {
t.Errorf("def %q (stmt %d) should reach return: %v", d.Text, d.Index, ch.Defs)
}
}
d0 := stmtByText(t, c, "x = 0")
if containsInt(ch.Defs, d0.Index) {
t.Errorf("x = 0 is killed on every if/elif/else path: %v", ch.Defs)
}
// Augmented assign reads its target.
if len(dec.Uses) == 0 || dec.Uses[0] != "x" {
t.Errorf("x -= 1 must use x: %v", dec.Uses)
}
}
func TestPythonForAndTryExceptFinally(t *testing.T) {
c := mustBuild(t, `def f(items):
total = 0
for i in items:
total += i
try:
total = parse(total)
except ValueError as e:
total = -1
finally:
log(total)
return total
`, "python")
loop := stmtByText(t, c, "for i in items")
if len(loop.Defs) != 1 || loop.Defs[0] != "i" {
t.Errorf("for header must define i: %v", loop.Defs)
}
if len(loop.Uses) != 1 || loop.Uses[0] != "items" {
t.Errorf("for header must use items: %v", loop.Uses)
}
if !hasEdgeLabel(c, LabelException) || !hasEdgeLabel(c, LabelFinally) {
t.Fatalf("try/except/finally must wire exception+finally edges: %+v", c.Edges)
}
// The except binding defines e.
catch := stmtByText(t, c, "ValueError as e")
foundE := false
for _, d := range catch.Defs {
if d == "e" {
foundE = true
}
}
if !foundE {
t.Errorf("except clause must define e: %v", catch.Defs)
}
// finally sees both the try def and the handler def.
r := c.ReachingDefinitions()
logStmt := stmtByText(t, c, "log(total)")
ch := chainFor(t, r, logStmt.Index, "total")
dTry := stmtByText(t, c, "total = parse(total)")
dExc := stmtByText(t, c, "total = -1")
if !containsInt(ch.Defs, dTry.Index) || !containsInt(ch.Defs, dExc.Index) {
t.Errorf("finally must merge try and handler defs: %v", ch.Defs)
}
// An exception before the protected assignment leaves the
// pre-try def live — it must reach the finally too.
dInit := stmtByText(t, c, "total = 0")
if !containsInt(ch.Defs, dInit.Index) {
t.Errorf("pre-try def must reach the finally via the exception path: %v", ch.Defs)
}
}
func TestPythonIndentedMethodDedents(t *testing.T) {
// A method sliced out of a class body keeps its indentation —
// Build must dedent before parsing.
c := mustBuild(t, ` def m(self, a):
x = a
return x
`, "python")
if c.FuncName != "m" {
t.Errorf("FuncName = %q, want m", c.FuncName)
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
if !hasChain(r, ret.Index, "x") {
t.Errorf("dedented method must still produce chains")
}
}
// Python match: arms live inside the match body block, not as direct
// children of match_statement. Each arm's pattern, body and chains
// must survive into the CFG, and capture patterns bind names.
func TestPythonMatchArmsAndCaptures(t *testing.T) {
c := mustBuild(t, `def f(x):
match x:
case [a, b]:
r = a + b
case Point(px):
r = px
case _:
r = 0
return r
`, "python")
if !hasEdgeLabel(c, LabelCase) {
t.Fatalf("match arms must produce case edges; edges: %+v", c.Edges)
}
// The bodies of every arm must be present.
for _, want := range []string{"r = a + b", "r = px", "r = 0"} {
stmtByText(t, c, want)
}
// Capture patterns bind a, b and px as definitions; their uses
// inside the arm bodies chain to the pattern.
r := c.ReachingDefinitions()
for use, vr := range map[string]string{"r = a + b": "a", "r = px": "px"} {
st := stmtByText(t, c, use)
if !hasChain(r, st.Index, vr) {
t.Errorf("capture %q must chain into %q: chains %+v", vr, use, r.Chains)
}
}
// All three arm defs of r reach the return.
ret := stmtByText(t, c, "return r")
ch := chainFor(t, r, ret.Index, "r")
for _, def := range []string{"r = a + b", "r = px", "r = 0"} {
d := stmtByText(t, c, def)
if !containsInt(ch.Defs, d.Index) {
t.Errorf("arm def %q must reach the return: %v", def, ch.Defs)
}
}
}
// Python match guard: the guard reads variables, it must not turn
// them into phantom definitions.
func TestPythonMatchGuardReadsNotDefines(t *testing.T) {
c := mustBuild(t, `def f(x, lo):
match x:
case Box(v) if v > lo:
return v
case _:
return 0
`, "python")
pat := stmtByText(t, c, "Box(v)")
if containsStr(pat.Defs, "lo") {
t.Errorf("guard variable lo must not be a definition: %v", pat.Defs)
}
foundLo := false
for _, u := range pat.Uses {
if u == "lo" {
foundLo = true
}
}
if !foundLo {
t.Errorf("guard must read lo: %v", pat.Uses)
}
}
// Python for-else: a `break` skips the else clause, so the break-path
// definition is not killed by the else's reassignment.
func TestPythonForElseBreakSkipsElse(t *testing.T) {
c := mustBuild(t, `def f(items, target):
found = -1
for i in items:
if i == target:
found = i
break
else:
found = 0
return found
`, "python")
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return found")
ch := chainFor(t, r, ret.Index, "found")
dBreak := stmtByText(t, c, "found = i")
dElse := stmtByText(t, c, "found = 0")
// The break-path def must survive to the return — break jumps past
// the else, so the else cannot kill it.
if !containsInt(ch.Defs, dBreak.Index) {
t.Errorf("break-path def `found = i` must reach the return (break skips else): %v", ch.Defs)
}
// The else def reaches the return on the no-break path.
if !containsInt(ch.Defs, dElse.Index) {
t.Errorf("else def `found = 0` must reach the return on the normal exit: %v", ch.Defs)
}
}
func containsStr(xs []string, x string) bool {
for _, v := range xs {
if v == x {
return true
}
}
return false
}
// ---------------------------------------------------------------------------
// JavaScript / TypeScript
// ---------------------------------------------------------------------------
func TestJavaScriptSwitchFallthroughAndTry(t *testing.T) {
c := mustBuild(t, `function f(a) {
let x = 0;
switch (a) {
case 1:
x = 1;
break;
case 2:
x = 2;
default:
x = 3;
}
try {
x = g(x);
} catch (e) {
x = -1;
} finally {
log(x);
}
return x;
}
`, "javascript")
// case 2 falls through into default.
s2 := stmtByText(t, c, "x = 2")
s3 := stmtByText(t, c, "x = 3")
if !edgeBetween(c, s2.Block, s3.Block, LabelSeq) {
t.Errorf("case 2 must fall through to default: %+v", c.Edges)
}
// case 1 must NOT fall through (break).
s1 := stmtByText(t, c, "x = 1")
if edgeBetween(c, s1.Block, s2.Block, LabelSeq) {
t.Errorf("case 1 ends with break and must not fall through")
}
if !hasEdgeLabel(c, LabelException) || !hasEdgeLabel(c, LabelFinally) {
t.Fatalf("try/catch/finally edges missing")
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
ch := chainFor(t, r, ret.Index, "x")
dTry := stmtByText(t, c, "x = g(x)")
dCatch := stmtByText(t, c, "x = -1")
if !containsInt(ch.Defs, dTry.Index) || !containsInt(ch.Defs, dCatch.Index) {
t.Errorf("return must merge try and catch defs: %v", ch.Defs)
}
// catch parameter defines e.
for _, st := range c.Stmts {
if st.Kind == "catch" {
if len(st.Defs) != 1 || st.Defs[0] != "e" {
t.Errorf("catch must define e: %v", st.Defs)
}
}
}
}
func TestJavaScriptForOfAndLabeledBreak(t *testing.T) {
c := mustBuild(t, `function f(arr) {
let s = 0;
outer: for (const v of arr) {
for (let j = 0; j < v; j++) {
if (j > 3) break outer;
s += j;
}
}
return s;
}
`, "javascript")
hdr := stmtByText(t, c, "for (const v of arr)")
if len(hdr.Defs) != 1 || hdr.Defs[0] != "v" {
t.Errorf("for-of header must define v: %v", hdr.Defs)
}
if len(hdr.Uses) != 1 || hdr.Uses[0] != "arr" {
t.Errorf("for-of header must use arr: %v", hdr.Uses)
}
br := stmtByText(t, c, "break outer")
ret := stmtByText(t, c, "return s")
var breakTo = -1
for _, e := range c.Edges {
if e.From == br.Block && e.Label == LabelBreak {
breakTo = e.To
}
}
ok := breakTo == ret.Block || edgeBetween(c, breakTo, ret.Block, LabelSeq)
if !ok {
t.Errorf("labeled break must exit the outer loop (got block %d)", breakTo)
}
}
func TestTypeScriptMethodClassWrap(t *testing.T) {
// A class method sliced out of its class doesn't parse
// standalone — Build retries inside a synthetic class wrapper.
c := mustBuild(t, `private compute(a: number): number {
let x: number = a * 2;
if (x > 10) {
x = 10;
}
return x;
}
`, "typescript")
if c.FuncName != "compute" {
t.Errorf("FuncName = %q, want compute", c.FuncName)
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
ch := chainFor(t, r, ret.Index, "x")
if len(ch.Defs) != 2 {
t.Errorf("both defs of x must reach the return: %v", ch.Defs)
}
// Line numbers must NOT be offset by the synthetic wrapper line.
first := stmtByText(t, c, "let x")
if first.StartLine != 2 {
t.Errorf("wrapped parse must keep snippet-relative lines: got %d, want 2", first.StartLine)
}
}
func TestJavaScriptDoWhile(t *testing.T) {
c := mustBuild(t, `function f(n) {
let i = 0;
do {
i++;
} while (i < n);
return i;
}
`, "javascript")
if !hasEdgeLabel(c, LabelLoopBack) {
t.Fatalf("do-while needs a loop_back edge: %+v", c.Edges)
}
// Post-test: the body executes before the condition; i++ must
// reach the condition's use of i.
r := c.ReachingDefinitions()
cond := stmtByText(t, c, "i < n")
inc := stmtByText(t, c, "i++")
ch := chainFor(t, r, cond.Index, "i")
if !containsInt(ch.Defs, inc.Index) {
t.Errorf("do-while condition must see the body's def: %v", ch.Defs)
}
}
// ---------------------------------------------------------------------------
// Java
// ---------------------------------------------------------------------------
func TestJavaMethodConstructsAndChains(t *testing.T) {
c := mustBuild(t, `int f(int a) {
int x = a + 1;
for (int i = 0; i < a; i++) {
if (i == 2) continue;
if (i == 5) break;
x += i;
}
switch (x) {
case 1:
x = 10;
break;
default:
x = 20;
}
try {
x = parse(x);
} catch (Exception e) {
x = 0;
} finally {
log(x);
}
return x;
}
`, "java")
for _, want := range []EdgeLabel{LabelTrue, LabelFalse, LabelLoopBack, LabelBreak, LabelContinue, LabelCase, LabelException, LabelFinally, LabelReturn} {
if !hasEdgeLabel(c, want) {
t.Errorf("missing %s edge", want)
}
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
ch := chainFor(t, r, ret.Index, "x")
dTry := stmtByText(t, c, "x = parse(x)")
dCatch := stmtByText(t, c, "x = 0")
if !containsInt(ch.Defs, dTry.Index) || !containsInt(ch.Defs, dCatch.Index) {
t.Errorf("return must merge try and catch defs: %v", ch.Defs)
}
// Augmented assignment x += i reads and writes x.
aug := stmtByText(t, c, "x += i")
if len(aug.Defs) != 1 || aug.Defs[0] != "x" {
t.Errorf("x += i defs: %v", aug.Defs)
}
wantUses := map[string]bool{"x": false, "i": false}
for _, u := range aug.Uses {
if _, ok := wantUses[u]; ok {
wantUses[u] = true
}
}
for v, seen := range wantUses {
if !seen {
t.Errorf("x += i must use %s: %v", v, aug.Uses)
}
}
}
func TestJavaEnhancedForHeader(t *testing.T) {
c := mustBuild(t, `int sum(java.util.List<Integer> items) {
int s = 0;
for (Integer v : items) {
s += v;
}
return s;
}
`, "java")
hdr := stmtByText(t, c, "for (Integer v : items)")
if len(hdr.Defs) != 1 || hdr.Defs[0] != "v" {
t.Errorf("enhanced-for must define v: %v", hdr.Defs)
}
if len(hdr.Uses) != 1 || hdr.Uses[0] != "items" {
t.Errorf("enhanced-for must use items: %v", hdr.Uses)
}
}
// Java method-call names must not register as variable uses.
func TestJavaMethodNameNotAUse(t *testing.T) {
c := mustBuild(t, `int f(java.util.List<Integer> list) {
int size = 99;
int n = list.size();
return n + size;
}
`, "java")
call := stmtByText(t, c, "int n = list.size()")
for _, u := range call.Uses {
if u == "size" {
t.Errorf("the .size() method name must not count as a use of the local `size`: %v", call.Uses)
}
}
}
// Java arrow-form switch: `case 1 -> { … }` never falls through. The
// arm bodies must not be chained to one another by a phantom seq edge;
// the post-switch use must see every arm's def but no fallthrough.
func TestJavaArrowSwitchNoFallthrough(t *testing.T) {
c := mustBuild(t, `int f(int x) {
int y = 0;
switch (x) {
case 1 -> { y = 1; }
case 2 -> { y = 2; }
default -> { y = 3; }
}
return y;
}`, "java")
s1 := stmtByText(t, c, "y = 1")
s2 := stmtByText(t, c, "y = 2")
s3 := stmtByText(t, c, "y = 3")
// No fallthrough seq edges between arrow rules.
if edgeBetween(c, s1.Block, s2.Block, LabelSeq) {
t.Errorf("arrow rule `case 1` must not fall through to `case 2`; edges: %+v", c.Edges)
}
if edgeBetween(c, s2.Block, s3.Block, LabelSeq) {
t.Errorf("arrow rule `case 2` must not fall through to default; edges: %+v", c.Edges)
}
// Every arm's def reaches the return (each via its own path).
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return y")
ch := chainFor(t, r, ret.Index, "y")
for _, d := range []*Statement{s1, s2, s3} {
if !containsInt(ch.Defs, d.Index) {
t.Errorf("arm def %q must reach the return: %v", d.Text, ch.Defs)
}
}
}
// ---------------------------------------------------------------------------
// Rust
// ---------------------------------------------------------------------------
func TestRustMatchLoopsAndChains(t *testing.T) {
c := mustBuild(t, `fn f(a: i32) -> i32 {
let mut x = a + 1;
while x > 0 {
x -= 1;
if x == 2 { break; }
}
for i in 0..3 {
x += i;
}
loop {
x += 1;
if x > 5 { break; }
}
match x {
1 => { x = 10; }
n => { x = n + 1; }
}
return x;
}
`, "rust")
for _, want := range []EdgeLabel{LabelTrue, LabelFalse, LabelLoopBack, LabelBreak, LabelCase, LabelReturn} {
if !hasEdgeLabel(c, want) {
t.Errorf("missing %s edge", want)
}
}
// The match binding pattern `n` defines n; the arm body uses it.
r := c.ReachingDefinitions()
armBody := stmtByText(t, c, "x = n + 1")
ch := chainFor(t, r, armBody.Index, "n")
pat := c.Stmts[ch.Defs[0]]
if pat.Kind != "case" {
t.Errorf("n's def should be the arm pattern statement, got kind %q (%q)", pat.Kind, pat.Text)
}
// for header defines i.
hdr := stmtByText(t, c, "for i in 0..3")
if len(hdr.Defs) != 1 || hdr.Defs[0] != "i" {
t.Errorf("for header must define i: %v", hdr.Defs)
}
}
// Rust labeled break: `break 'outer` must exit the outer loop, so the
// outer loop_end (holding `return s`) stays reachable. Before the fix
// the label never resolved and the break leaked to the innermost
// loop.
func TestRustLabeledBreakExitsOuter(t *testing.T) {
c := mustBuild(t, `fn f() -> i32 {
let mut s = 0;
'outer: loop {
loop {
s += 1;
if s > 3 { break 'outer; }
}
}
return s;
}`, "rust")
br := stmtByText(t, c, "break 'outer")
var breakTo = -1
for _, e := range c.Edges {
if e.From == br.Block && e.Label == LabelBreak {
breakTo = e.To
}
}
if breakTo < 0 {
t.Fatalf("no break edge from block %d", br.Block)
}
ret := stmtByText(t, c, "return s")
ok := breakTo == ret.Block || edgeBetween(c, breakTo, ret.Block, LabelSeq)
if !ok {
t.Errorf("labeled break must exit the outer loop (block %d), reaching the return block %d; edges: %+v", breakTo, ret.Block, c.Edges)
}
// The label identifier must not leak in as a variable use.
if containsStr(br.Uses, "outer") {
t.Errorf("loop label `outer` must not register as a use: %v", br.Uses)
}
}
// Rust match guard: the guard condition lives on the match_pattern as
// its `condition` field; its reads must not become phantom binding
// definitions that kill the real parameter def.
func TestRustMatchGuardReadsNotDefines(t *testing.T) {
c := mustBuild(t, `fn f(o: Option<i32>, z: i32) -> i32 {
match o {
Some(y) if z > 0 => y,
_ => z,
}
}`, "rust")
pat := stmtByText(t, c, "Some(y)")
// z is read by the guard, not bound by the pattern.
if containsStr(pat.Defs, "z") {
t.Errorf("guard variable z must not be a pattern definition: %v", pat.Defs)
}
foundZ := false
for _, u := range pat.Uses {
if u == "z" {
foundZ = true
}
}
if !foundZ {
t.Errorf("guard must read z: %v", pat.Uses)
}
// The capture y is still a definition.
if !containsStr(pat.Defs, "y") {
t.Errorf("capture y must be a definition: %v", pat.Defs)
}
}
func TestRustIfLetBindsInHeader(t *testing.T) {
c := mustBuild(t, `fn f(opt: Option<i32>) -> i32 {
let mut x = 0;
if let Some(v) = opt {
x = v;
}
return x;
}
`, "rust")
cond := stmtByText(t, c, "let Some(v) = opt")
foundV := false
for _, d := range cond.Defs {
if d == "v" {
foundV = true
}
}
if !foundV {
t.Errorf("if-let must define v in the header: defs=%v", cond.Defs)
}
r := c.ReachingDefinitions()
use := stmtByText(t, c, "x = v")
if !hasChain(r, use.Index, "v") {
t.Errorf("v's use must chain to the if-let binding")
}
}
// ---------------------------------------------------------------------------
// Ruby
// ---------------------------------------------------------------------------
func TestRubyConstructsAndChains(t *testing.T) {
c := mustBuild(t, `def f(a)
x = a + 1
if x > 10
x = 10
elsif x > 5
x = 5
else
x = 0
end
while x > 0
x -= 1
break if x == 2
end
case x
when 1
x = 100
else
x = 200
end
return x
end
`, "ruby")
for _, want := range []EdgeLabel{LabelTrue, LabelFalse, LabelLoopBack, LabelBreak, LabelCase, LabelReturn} {
if !hasEdgeLabel(c, want) {
t.Errorf("missing %s edge", want)
}
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return x")
ch := chainFor(t, r, ret.Index, "x")
d100 := stmtByText(t, c, "x = 100")
d200 := stmtByText(t, c, "x = 200")
if !containsInt(ch.Defs, d100.Index) || !containsInt(ch.Defs, d200.Index) {
t.Errorf("case-arm defs must union at the return: %v", ch.Defs)
}
}
func TestRubyRescueEnsureAndBlocks(t *testing.T) {
c := mustBuild(t, `def f(items)
total = 0
items.each do |it|
total += it
next if it < 0
end
begin
total = parse(total)
rescue ArgumentError => e
total = -1
ensure
log(total)
end
total
end
`, "ruby")
if !hasEdgeLabel(c, LabelException) || !hasEdgeLabel(c, LabelFinally) {
t.Fatalf("begin/rescue/ensure must wire exception+finally edges")
}
// The block call models a loop: block param defined, loop_back
// present, `next` is a continue.
if !hasEdgeLabel(c, LabelLoopBack) || !hasEdgeLabel(c, LabelContinue) {
t.Errorf("each-block must model loop_back and continue: %+v", c.Edges)
}
var paramStmt *Statement
for _, st := range c.Stmts {
if st.Kind == "param" && len(st.Defs) == 1 && st.Defs[0] == "it" {
paramStmt = st
}
}
if paramStmt == nil {
t.Fatalf("block parameter |it| must be a definition")
}
r := c.ReachingDefinitions()
use := stmtByText(t, c, "total += it")
ch := chainFor(t, r, use.Index, "it")
if !containsInt(ch.Defs, paramStmt.Index) {
t.Errorf("it's use must chain to the block parameter: %v", ch.Defs)
}
// rescue binding defines e.
foundE := false
for _, st := range c.Stmts {
if st.Kind == "catch" {
for _, d := range st.Defs {
if d == "e" {
foundE = true
}
}
}
}
if !foundE {
t.Errorf("rescue => e must define e")
}
// ensure merges try + rescue defs.
logStmt := stmtByText(t, c, "log(total)")
chT := chainFor(t, r, logStmt.Index, "total")
dTry := stmtByText(t, c, "total = parse(total)")
dResc := stmtByText(t, c, "total = -1")
if !containsInt(chT.Defs, dTry.Index) || !containsInt(chT.Defs, dResc.Index) {
t.Errorf("ensure must merge try and rescue defs: %v", chT.Defs)
}
}
func TestRubyUnlessAndModifiers(t *testing.T) {
c := mustBuild(t, `def f(x)
y = 0
unless x > 0
y = 1
end
y += 2 if x == 5
return y
end
`, "ruby")
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return y")
ch := chainFor(t, r, ret.Index, "y")
d0 := stmtByText(t, c, "y = 0")
d1 := stmtByText(t, c, "y = 1")
dMod := stmtByText(t, c, "y += 2")
for _, d := range []*Statement{d0, d1, dMod} {
if !containsInt(ch.Defs, d.Index) {
t.Errorf("def %q must reach return (conditional paths): %v", d.Text, ch.Defs)
}
}
}
// ---------------------------------------------------------------------------
// nested construct stress: every language parses a nested
// if-in-loop-in-if shape and produces a connected graph.
// ---------------------------------------------------------------------------
func TestNestedShapesAllLanguages(t *testing.T) {
cases := map[string]string{
"go": `func f(a int) int {
r := 0
if a > 0 {
for i := 0; i < a; i++ {
if i%2 == 0 {
r += i
} else {
r -= i
}
}
}
return r
}`,
"python": `def f(a):
r = 0
if a > 0:
for i in range(a):
if i % 2 == 0:
r += i
else:
r -= i
return r
`,
"javascript": `function f(a) {
let r = 0;
if (a > 0) {
for (let i = 0; i < a; i++) {
if (i % 2 === 0) { r += i; } else { r -= i; }
}
}
return r;
}`,
"typescript": `function f(a: number): number {
let r = 0;
if (a > 0) {
for (let i = 0; i < a; i++) {
if (i % 2 === 0) { r += i; } else { r -= i; }
}
}
return r;
}`,
"java": `int f(int a) {
int r = 0;
if (a > 0) {
for (int i = 0; i < a; i++) {
if (i % 2 == 0) { r += i; } else { r -= i; }
}
}
return r;
}`,
"rust": `fn f(a: i32) -> i32 {
let mut r = 0;
if a > 0 {
for i in 0..a {
if i % 2 == 0 { r += i; } else { r -= i; }
}
}
return r;
}`,
"ruby": `def f(a)
r = 0
if a > 0
for i in 0..a
if i % 2 == 0
r += i
else
r -= i
end
end
end
return r
end
`,
}
for lang, src := range cases {
t.Run(lang, func(t *testing.T) {
c := mustBuild(t, src, lang)
if len(c.Blocks) < 6 {
t.Fatalf("%s: nested shape should produce several blocks, got %d", lang, len(c.Blocks))
}
if !hasEdgeLabel(c, LabelLoopBack) {
t.Errorf("%s: missing loop_back", lang)
}
r := c.ReachingDefinitions()
ret := stmtByText(t, c, "return r")
ch := chainFor(t, r, ret.Index, "r")
dPlus := stmtByText(t, c, "r += i")
dMinus := stmtByText(t, c, "r -= i")
dInit := c.Stmts[0]
for _, st := range c.Stmts {
if strings.Contains(st.Text, "r = 0") || strings.Contains(st.Text, "let r = 0") || strings.Contains(st.Text, "int r = 0") || strings.Contains(st.Text, "let mut r = 0") || strings.Contains(st.Text, "r := 0") {
dInit = st
break
}
}
for _, d := range []*Statement{dInit, dPlus, dMinus} {
if !containsInt(ch.Defs, d.Index) {
t.Errorf("%s: def %q must reach the return: %v", lang, d.Text, ch.Defs)
}
}
// Every non-entry block with statements must be reachable
// from somewhere except deliberately-unreachable ones.
seenTarget := map[int]bool{c.Entry: true}
for _, e := range c.Edges {
seenTarget[e.To] = true
}
for _, bl := range c.Blocks {
if bl.ID == c.Entry || len(bl.Stmts) == 0 {
continue
}
if !seenTarget[bl.ID] && bl.Label != "unreachable" {
t.Errorf("%s: block %d (%s) has statements but no incoming edge", lang, bl.ID, bl.Label)
}
}
})
}
}