From 0432ac6aefccbc5a9af25bd4ed2d4451611c4ff4 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:57:42 +0330 Subject: analyzer: an outer owner moved into a deeper container can take it back (loop-built lists); block-end frees name the owner (why 9) in messages and LSP related info --- src/compiler/analyzer.zc | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'src/compiler/analyzer.zc') diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index ff6a6c1..a1588e8 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -122,7 +122,7 @@ int shadow_finish(int from) { while (k < 24) { if ((e[2] >> k) & 1) flags = flags | effect_get(e[1], k); k++; } if ((flags & (EF_ALIAS | EF_IDENTITY)) && !(flags & EF_UNKNOWN) && !dry) { tunpack(e[3]); curinc = e[4]; panic = 0; - errat(); ended_message(e[6], e[7], e[8], e[9]); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend(); + errat(); ended_message(e[6], e[7], e[8], e[9], 0); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend(); } e = next; } @@ -1147,6 +1147,7 @@ int block_close(int save) { unused_check(save); exit_vars(fdepth, 1); lend_drop(fdepth, save); + block_end_owners(save); alias_end(fdepth, save); while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } local_drop(save); @@ -1155,6 +1156,26 @@ int block_close(int save) { return 0; } +// owners freed at this block's end: an outer alias of one, or an outer owner +// stored into one, dies with it and remembers where (why 9). +int block_end_owners(int save) { + int i = save, j; word info = 0; + if (!fchk || !live) return 0; + while (i < nloc) { + if (lhome[i] >= fdepth && lstate[i] == S_M && lown[i]) { + info = 0; j = lbase; + while (j < save) { + if (lstate[j] == S_CHILD && lroot(j) == i) { + if (!info) { info = talloc(6); info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); info[3] = 9; info[4] = 0; info[5] = info[2]; } + local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; + } + j++; + } + } + i++; + } + return 0; +} // an alias in an outer block whose owner is about to disappear dies with it int alias_end(int dep, int save) { int i, r; @@ -1532,6 +1553,19 @@ int assigncheck(int k, int i, int rc) { else lstate[i] = S_PLAIN; return C_PLAIN; } + // an outer owner moved into a deeper container takes the container back: + // head = item after item.next = head. the container leaves its block. + if (st == S_CHILD && lown[i] && j >= 0 && lroot(i) == j && lstate[j] == S_M && lown[j] + && lhome[j] >= lhome[i] && !local_captured[i] && !local_captured[j]) { + int k = lbase; + while (k < nloc) { if (k != i && lstate[k] == S_CHILD && lowner[k] == j) lowner[k] = i; k++; } + lstate[i] = S_M; lused[i] = 0; + lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j]; + local_ended[i] = 0; local_end_info[i] = 0; + end_why = 3; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_MOVE, i); + if (ltag[i] & 15) lstate[i] = ltag[i] & 15; + return C_PLAIN; + } if (!empty(st)) { cerrn(i, "still owns an object; overwriting it would leak it"); return C_PLAIN; @@ -1587,7 +1621,7 @@ int assigncheck(int k, int i, int rc) { } else { end_why = 4; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away } else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)"); - else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into"); + else if (lhome[j] < lhome[r] && (r != i || !lown[j] || (local_captured && (local_captured[i] || local_captured[j])))) cerrn(j, "lives in an outer block than the container it's stored into"); else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); } return C_PLAIN; } -- cgit v1.2.3 From 8703e2c85a8ecdea9e869e876e347ebd4cb6ef88 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:59:31 +0330 Subject: analyzer: a back link (container's owner stored into it) stays a plain reference, fixing a compile hang on a<->b and a leak; ending an owner kills its owned children too (fixes use after reassigning a container); escape fixtures --- src/compiler/analyzer.zc | 5 ++++- tests/conformance/6-ownership/back_link_alias.zc | 6 ++++++ tests/conformance/6-ownership/back_link_block_ok.zc | 6 ++++++ tests/conformance/6-ownership/back_link_cont.zc | 6 ++++++ tests/conformance/6-ownership/back_link_cycle.zc | 17 +++++++++++++++++ tests/conformance/6-ownership/back_link_free.zc | 6 ++++++ tests/conformance/6-ownership/back_link_glob.zc | 7 +++++++ tests/conformance/6-ownership/back_link_move.zc | 7 +++++++ tests/conformance/6-ownership/back_link_reasg.zc | 6 ++++++ tests/conformance/6-ownership/back_link_ret.zc | 7 +++++++ 10 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/6-ownership/back_link_alias.zc create mode 100644 tests/conformance/6-ownership/back_link_block_ok.zc create mode 100644 tests/conformance/6-ownership/back_link_cont.zc create mode 100644 tests/conformance/6-ownership/back_link_cycle.zc create mode 100644 tests/conformance/6-ownership/back_link_free.zc create mode 100644 tests/conformance/6-ownership/back_link_glob.zc create mode 100644 tests/conformance/6-ownership/back_link_move.zc create mode 100644 tests/conformance/6-ownership/back_link_reasg.zc create mode 100644 tests/conformance/6-ownership/back_link_ret.zc (limited to 'src/compiler/analyzer.zc') diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index a1588e8..839c1ef 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -1009,7 +1009,7 @@ int end_owner(int i) { end_why = 0; end_whom = 0; local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD; while (j < nloc) { - if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } + if (j != i && lstate[j] == S_CHILD && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } // aliases and owned children go with it j++; } return 0; @@ -1612,6 +1612,9 @@ int assigncheck(int k, int i, int rc) { touch(i); if (rc == C_FRESH) { emitown(i); return C_PLAIN; } if (j < 0) return C_PLAIN; + // a back link: storing the container's own owner (or the container itself) + // into it. it stays a plain reference; ownership would form a cycle. + if (r == j) { touch(j); return C_PLAIN; } js = lstate[j]; if (js == S_CHILD) cerrn(j, "already belongs to a container"); else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored"); diff --git a/tests/conformance/6-ownership/back_link_alias.zc b/tests/conformance/6-ownership/back_link_alias.zc new file mode 100644 index 0000000..58db710 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_alias.zc @@ -0,0 +1,6 @@ +// expect error: b: is an owned object and can't be copied into another variable +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* p; { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; p = b; } rt.put_n(p.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_block_ok.zc b/tests/conformance/6-ownership/back_link_block_ok.zc new file mode 100644 index 0000000..7955081 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_block_ok.zc @@ -0,0 +1,6 @@ +// expect: 40 +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { memdebug on; { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; a.value = 4; rt.put_n(b.prev.value); } rt.put_n(rt.memreport()); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_cont.zc b/tests/conformance/6-ownership/back_link_cont.zc new file mode 100644 index 0000000..d8083fd --- /dev/null +++ b/tests/conformance/6-ownership/back_link_cont.zc @@ -0,0 +1,6 @@ +// expect error: b: already belongs to a container +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* c = rt.new(node); node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; c.next = b; rt.put_n(c.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_cycle.zc b/tests/conformance/6-ownership/back_link_cycle.zc new file mode 100644 index 0000000..fc47100 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_cycle.zc @@ -0,0 +1,17 @@ +// expect: 0/ +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { + memdebug on; + for (int round = 0; round < 50; round++) { + node* a = rt.new(node); + node* b = rt.new(node); + a.next = b; + b.prev = a; + a.value = round; + b.value = b.prev.value; + } + rt.put_n(rt.memreport()); rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_free.zc b/tests/conformance/6-ownership/back_link_free.zc new file mode 100644 index 0000000..a8fb5e8 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_free.zc @@ -0,0 +1,6 @@ +// expect error: b: used after it was given away; b borrows a, and a was freed +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; rt.free(a); rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_glob.zc b/tests/conformance/6-ownership/back_link_glob.zc new file mode 100644 index 0000000..ecba2e5 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_glob.zc @@ -0,0 +1,7 @@ +// expect error: b: is not an owner, so it can't be stored in a global +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* keep; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; keep = b; } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_move.zc b/tests/conformance/6-ownership/back_link_move.zc new file mode 100644 index 0000000..47490d6 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_move.zc @@ -0,0 +1,7 @@ +// expect error: a was stored in a global or untracked memory +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* keep2; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; keep2 = a; rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_reasg.zc b/tests/conformance/6-ownership/back_link_reasg.zc new file mode 100644 index 0000000..02709a8 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_reasg.zc @@ -0,0 +1,6 @@ +// expect error: b: used after it was given away; b borrows a, and a was freed by reassigning it +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; a = rt.new(node); a.value = 1; rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_ret.zc b/tests/conformance/6-ownership/back_link_ret.zc new file mode 100644 index 0000000..ee10fce --- /dev/null +++ b/tests/conformance/6-ownership/back_link_ret.zc @@ -0,0 +1,7 @@ +// expect error: b: has to be u to be returned +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* g() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; return b; } +fn main() { node* x = g(); rt.put_n(x.value); } +// zc0: skip -- cgit v1.2.3 From 5aaf70abdda0026a7b4d647746a109d94b25f7a0 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:01:29 +0330 Subject: analyzer: a .u (declared or inferred) function can't return a borrowed parameter or a value derived from one; fixtures --- src/compiler/analyzer.zc | 8 ++++++++ src/compiler/decl.zc | 2 +- .../6-ownership/infer_owned_return_borrow_first.zc | 10 ++++++++++ tests/conformance/6-ownership/infer_owned_return_mixed.zc | 11 +++++++++++ tests/conformance/6-ownership/u_return_param_field.zc | 7 +++++++ tests/conformance/6-ownership/u_return_plain_param.zc | 8 ++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/6-ownership/infer_owned_return_borrow_first.zc create mode 100644 tests/conformance/6-ownership/infer_owned_return_mixed.zc create mode 100644 tests/conformance/6-ownership/u_return_param_field.zc create mode 100644 tests/conformance/6-ownership/u_return_plain_param.zc (limited to 'src/compiler/analyzer.zc') diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index 839c1ef..55a96bb 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -1644,6 +1644,7 @@ int assigncheck(int k, int i, int rc) { // the function has no .u (its callers compiled after this point see it). an m // object is lent, not given, so returning it is an error, and so is a plain word // from an owned function that was called before its body. +int ret_borrowed; int retcheck(int c) { int i; if (isptr(xty) || xty == tword) { @@ -1670,9 +1671,16 @@ int retcheck(int c) { if ((isptr(xty) || isagg(xty)) && ((xaf && xaf[2] == 4) || (xbor >= 0 && lafact[xbor] && lafact[xbor][2] == 4))) { cerr("an array literal cannot escape its function through return"); return 0; } i = -1; if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR; + // a value that comes from a parameter the caller still owns (not a .d one, + // which is tracked) is a borrow: it can't become the caller's owned result. + if (c != C_FRESH && i < 0 && isptr(xty) && effect_mask(c)) { + if (fret) { cerr("a borrowed parameter cannot be returned as an owned result"); return 0; } + ret_borrowed = 1; + } if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) { + if (ret_borrowed) cerr("this function also returns a borrowed parameter, so its result can't be owned"); if (sflag[finsym] & F_EARLY) serrn(sname[finsym], "is called before its body, so its owned return can't be known there; declare the return type with .u"); ssig[finsym] = ssig[finsym] | 1; // the return is owned: callers read it from the signature sflag[finsym] = sflag[finsym] | F_RETOWN; diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 6bc8245..e2c3613 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -435,7 +435,7 @@ int fndef(int chk, int rty, int ret, int n) { fixall(sfix[s], here); sfix[s] = 0; fn_begin(chk, ret); lt = lt_begin(sval[s], sname[s], lfl); xc_fn = sname[s]; - finsym = s; finfer = !ret; + finsym = s; finfer = !ret; ret_borrowed = 0; effect_current = effect_record(s); rt.fill_w(effect_current, 0, 50); effect_current[0] = 1; if (!chk || stubgen) effect_current[0] = 2; effect_current[1] = np; i = 0; while (i < np) { if (pt[i] == 2) { effect_current[2 + i] = EF_CONSUME; effect_current[26 + i] = EF_CONSUME; } i++; } if (typed && explicit_rty) fty = rty; diff --git a/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc b/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc new file mode 100644 index 0000000..e75fb10 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc @@ -0,0 +1,10 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int v; node* next; }; +node* pick(node* other, int which) { + if (which) return other; + return rt.new(node); +} +fn main() { } +// zc0: skip diff --git a/tests/conformance/6-ownership/infer_owned_return_mixed.zc b/tests/conformance/6-ownership/infer_owned_return_mixed.zc new file mode 100644 index 0000000..5216003 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_mixed.zc @@ -0,0 +1,11 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int value; node* next; }; +node* pick(node* other, int which) { + node* fresh = rt.new(node); + if (which) return fresh; + return other; +} +fn main() { node* a = rt.new(node); node* b = pick(a, 1); rt.put_n(b.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/u_return_param_field.zc b/tests/conformance/6-ownership/u_return_param_field.zc new file mode 100644 index 0000000..af8284d --- /dev/null +++ b/tests/conformance/6-ownership/u_return_param_field.zc @@ -0,0 +1,7 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int v; node* next; }; +node*.u nxt(node* l) { node* c = l.next; return c; } +fn main() { } +// zc0: skip diff --git a/tests/conformance/6-ownership/u_return_plain_param.zc b/tests/conformance/6-ownership/u_return_plain_param.zc new file mode 100644 index 0000000..5aca40b --- /dev/null +++ b/tests/conformance/6-ownership/u_return_plain_param.zc @@ -0,0 +1,8 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +memdebug on; +struct node { int value; node* next; }; +node*.u pick(node* other) { return other; } +fn main() { node* a = rt.new(node); { node* b = pick(a); rt.put_n(b.value); } rt.put_n(a.value); } +// zc0: skip -- cgit v1.2.3