diff options
| author | 2026-10-15 22:15:38 +0330 | |
|---|---|---|
| committer | 2026-10-15 22:15:38 +0330 | |
| commit | be43babc1886fc5efee898b2a3d0285012e511fb (patch) | |
| tree | b7ae73e7a6d4c896e2c49b7090168ef589b574af /src/compiler/analyzer.zc | |
| parent | merge work/known-bugs: constant-expression precedence, local const in constan... (diff) | |
definite assignment: reading a local before it is initialized is a compile error (spec 4.1.5, b.1.9)
track linited per local alongside the ownership state; snapshots, exit records and joins carry it, and a join keeps a local set only when every reaching path set it. x = e, a declaration initializer, &x (out-parameter), a full array initializer, or using an aggregate as an address set a local; field and element writes do not set the aggregate. while/for bodies that may run zero times do not count; do/while does. compiler, runtime, std, actor and lsp sources initialize the locals the rule flagged; zc0 accepts = {} brace lists (zeroed). tests: tests/errors/uninit_*.zc, tests/conformance/4-statements/uninit_*.zc
Diffstat (limited to 'src/compiler/analyzer.zc')
| -rw-r--r-- | src/compiler/analyzer.zc | 46 |
1 files changed, 35 insertions, 11 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index 55a96bb..3802437 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -440,6 +440,7 @@ int newloc(int n) { if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0; if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0; if (!local_shadow) local_shadow = chk_resize(0, 0, loccap); local_shadow[i] = 0; + if (!linited) linited = chk_resize(0, 0, loccap); linited[i] = 0; lpos[i] = 0; lreadpos[i] = 0; lowner[i] = 0; @@ -1024,6 +1025,18 @@ int luse(int i) { if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1; return 0; } +// definite assignment (spec 4.1.5): a local must be written on every path +// before it is read. linited[i] is 1 once that is true for the current path. +int mark_inited(int i) { + if (i >= 0 && linited) linited[i] = 1; + return 0; +} +int need_inited(int i) { + if (!fchk || panic || dry || i < 0 || !linited) return 0; + if (!linited[i]) serrn(lname[i], "used before it is initialized"); + return 0; +} + // touch records a use of a tracked local and rejects dead objects. a d object // read in an inner block moves there, where the block owns it as m. int touch(int i) { @@ -1190,16 +1203,16 @@ int alias_end(int dep, int save) { } return 0; } -// snapshots: [lbase] [nloc] then state, owner, home per local +// snapshots: [lbase] [nloc] then state, owner, home, inited per local // save the states of all current locals; returns the snapshot offset int snap_push() { int b = snaptop, i = lbase; word p; af_forget(); - reserve_R_SNAP(b + 2 + 3 * (nloc - lbase)); + reserve_R_SNAP(b + 2 + 4 * (nloc - lbase)); p = R_SNAP + b; p[0] = lbase; p[1] = nloc; p = p + 2; - while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; } + while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; if (linited) p[3] = linited[i]; else p[3] = 1; p = p + 4; i++; } snaptop = p - R_SNAP; return b; } @@ -1211,7 +1224,7 @@ int snap_restore(int b) { af_forget(); if (e > nloc) e = nloc; p = p + 2; - while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; } + while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; if (linited) linited[i] = p[3]; p = p + 4; i++; } return 0; } // true for a plain or dead local state, which merge to dead. @@ -1226,7 +1239,7 @@ int snap_cmp(int b) { s = lstate[i]; if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; } else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i; - p = p + 3; i++; + p = p + 4; i++; } return -1; } @@ -1258,7 +1271,7 @@ int merge_err(int i, int sa, int what) { // to analyze an if, the compiler snapshots the locals' states before the // branch, compiles the then path, restores the snapshot for the else path, and // then compares or merges the two. a snapshot is [lbase, nloc] followed by -// state, owner and home for each local; the stack of them is at R_SNAP. +// state, owner, home and inited for each local; the stack of them is at R_SNAP. // ---- joins ---- // where control flow comes back together (after an if, a loop, a switch or @@ -1274,19 +1287,24 @@ int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JO // read as plain. word jst(word p, int i) { if (i >= p[1]) return S_PLAIN; - return p[2 + 3 * (i - p[0])]; + return p[2 + 4 * (i - p[0])]; } // the owner recorded in a jump's state for local i. -word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; } +word jow(word p, int i) { return p[3 + 4 * (i - p[0])]; } // the jump's marker for local i, in the same record as jow. -word jhm(word p, int i) { return p[4 + 3 * (i - p[0])]; } +word jhm(word p, int i) { return p[4 + 4 * (i - p[0])]; } +// definite-assignment bit for local i on path p (1 if missing from the snapshot). +word jin(word p, int i) { + if (i >= p[1]) return 1; + return p[5 + 4 * (i - p[0])]; +} // the states at a break, with the jump to patch: [site] then a snapshot word exit_rec(int site) { word p; int i = lbase; word q; - reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop; + reserve_R_EXIT(extop + 3 + 4 * (nloc - lbase)); p = R_EXIT + extop; p[0] = site; p[1] = lbase; p[2] = nloc; q = p + 3; - while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; q = q + 3; i++; } + while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; if (linited) q[3] = linited[i]; else q[3] = 1; q = q + 4; i++; } extop = q - R_EXIT; return p; } @@ -1340,6 +1358,12 @@ int join(int n0, int what) { rt.put_s(")"); errend(); } + // definite assignment: only initialized if every reaching path assigned it + if (linited) { + j = 0; s = 1; + while (j < njps) { if (!jin(jps[j], i)) s = 0; j++; } + linited[i] = s; + } i++; } return 0; |
