aboutsummaryrefslogtreecommitdiff
path: root/src/compiler/analyzer.zc
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 22:15:38 +0330
commitbe43babc1886fc5efee898b2a3d0285012e511fb (patch)
treeb7ae73e7a6d4c896e2c49b7090168ef589b574af /src/compiler/analyzer.zc
parentmerge 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.zc46
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;