diff options
| author | 2026-10-15 14:57:42 +0330 | |
|---|---|---|
| committer | 2026-10-15 14:57:42 +0330 | |
| commit | 0432ac6aefccbc5a9af25bd4ed2d4451611c4ff4 (patch) | |
| tree | ba312d92bc223e1a4c500134cf1343f7e1f63180 | |
| parent | merge work/borrow: borrow returns before definition, borrows clause, borrow m... (diff) | |
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
| -rw-r--r-- | src/compiler/analyzer.zc | 38 | ||||
| -rw-r--r-- | src/compiler/base.zc | 11 | ||||
| -rw-r--r-- | src/lsp/server.zc | 1 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/borrow_outlives_block.zc | 21 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/loop_build_list_item_after_move.zc | 13 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/loop_build_list_not_taken_back.zc | 13 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/loop_build_list_ok.zc | 21 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/loop_build_list_other_owner.zc | 13 | ||||
| -rw-r--r-- | tests/conformance/6-ownership/outer_owner_in_inner_container.zc | 13 |
9 files changed, 140 insertions, 4 deletions
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; } diff --git a/src/compiler/base.zc b/src/compiler/base.zc index 7e4b5bc..de699aa 100644 --- a/src/compiler/base.zc +++ b/src/compiler/base.zc @@ -980,9 +980,16 @@ int cerr(char* msg) { } // how an owner ended, from end_owner's record: the use, the owner, what ended it and where. // "r: used after it was given away; r borrows t, and t was freed on line 5 (t declared on line 4)" -int ended_message(char* use, word info, char* root, int cause) { +int ended_message(char* use, word info, char* root, int cause, int own) { char* owner = root; int why = 0; char* whom = 0; if (info) { owner = info[0]; why = info[3]; whom = info[4]; dg_related(info[5], info[1], why, owner); } + if (why == 9) { + rt.put_s(use); rt.put_s(": "); + if (rt.eq_w(use, owner)) rt.put_s("was freed"); + else { if (own) rt.put_s("belongs to "); else rt.put_s("borrows from "); rt.put_s(owner); rt.put_s(", which was freed"); } + rt.put_s(" at the end of its block (line "); rt.put_n(cause & 0xFFFFFF); rt.put_c(')'); + return 0; + } rt.put_s(use); rt.put_s(": used after it was given away; "); if (!rt.eq_w(use, owner)) { rt.put_s(use); rt.put_s(" borrows "); rt.put_s(owner); rt.put_s(", and "); } rt.put_s(owner); @@ -1008,7 +1015,7 @@ int cerrn(int i, char* msg) { if (!info && local_end_info) info = local_end_info[root]; if (info) cause = info[2]; else if (local_ended) cause = local_ended[root]; } - if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause); errend(); } + if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause, lown[i]); errend(); } else serrn(lname[i], msg); } return 0; diff --git a/src/lsp/server.zc b/src/lsp/server.zc index 65881a9..0fda725 100644 --- a/src/lsp/server.zc +++ b/src/lsp/server.zc @@ -278,6 +278,7 @@ int diag_related(int at) { else if (why == 3) what = " was moved here"; else if (why == 4) what = " was stored into a container here"; else if (why == 5) what = " was returned here"; else if (why == 6) what = " was stored in a global here"; else if (why == 7) what = " was freed by this reassignment"; else if (why == 8) what = " was pushed into an array here"; + else if (why == 9) what = " was freed at the end of this block"; ws(",\"relatedInformation\":[{\"location\":"); wplace(e[1]); ws(",\"message\":\"owner "); ws(e[4]); ws(what); ws("\"}"); if (e[2]) { ws(",{\"location\":"); wplace(e[2]); ws(",\"message\":\"owner "); ws(e[4]); ws(" is declared here\"}"); } wc(']'); diff --git a/tests/conformance/6-ownership/borrow_outlives_block.zc b/tests/conformance/6-ownership/borrow_outlives_block.zc new file mode 100644 index 0000000..4d9b44a --- /dev/null +++ b/tests/conformance/6-ownership/borrow_outlives_block.zc @@ -0,0 +1,21 @@ +// expect error: second: borrows from head, which was freed at the end of its block (line 17) +// spec 6.6.3 +include rt; + +struct node { + int value; + node* next; +}; + +fn main() { + node* second; + { + node* head = rt.new(node); + head.next = rt.new(node); + head.next.value = 1; + second = head.next; + } + rt.put_n(second.value); + rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_item_after_move.zc b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc new file mode 100644 index 0000000..5346569 --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc @@ -0,0 +1,13 @@ +// expect error: item: used after it was given away; item was moved into head +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); item.next = head; head = item; rt.put_n(item.value); i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc new file mode 100644 index 0000000..a9fadcd --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc @@ -0,0 +1,13 @@ +// expect error: head: is given away inside a loop that comes back around to it +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); item.next = head; i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_ok.zc b/tests/conformance/6-ownership/loop_build_list_ok.zc new file mode 100644 index 0000000..5f3de5a --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_ok.zc @@ -0,0 +1,21 @@ +// expect: 7/0/ +// spec 6.8.5 +include rt; +memdebug on; +struct node { int value; node* next; }; +fn main() { + { + node* head = rt.new(node); + int i = 0; + while (i < 5) { + node* item = rt.new(node); + item.value = i; + item.next = head; + head = item; + i++; + } + rt.put_n(head.value + head.next.value); rt.put_c(10); + } + rt.put_n(rt.memreport()); rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_other_owner.zc b/tests/conformance/6-ownership/loop_build_list_other_owner.zc new file mode 100644 index 0000000..ff13262 --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_other_owner.zc @@ -0,0 +1,13 @@ +// expect error: head: still owns an object; overwriting it would leak it +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); node* other = rt.new(node); item.next = head; other.value = 1; head = other; i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/outer_owner_in_inner_container.zc b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc new file mode 100644 index 0000000..d9043c0 --- /dev/null +++ b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc @@ -0,0 +1,13 @@ +// expect error: head: belongs to item, which was freed at the end of its block (line 10) +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + { + node* item = rt.new(node); + item.next = head; + } + rt.put_n(head.value); +} +// zc0: skip |
