From fd18cb8fa5117b98939db553c2f61dd83b84d7d8 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 02:37:51 +0330 Subject: rt: memreport marks a leaked value whose drop never ran with (drop); spec 4.11, readme, memdebug_report_drop.zc --- README.md | 2 +- docs/SPEC.md | 2 +- src/runtime/rt.zc | 4 +++- tests/conformance/4-statements/memdebug_report_drop.zc | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 tests/conformance/4-statements/memdebug_report_drop.zc diff --git a/README.md b/README.md index 4ebbe0e..a90017d 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ zy: double free from line 12 zy: bad heap pointer ``` -the line is the `new` that made the block. a double free prints and returns; it does not smash the block. `rt.memreport()` prints one line per recorded object that is still live, `leak: line 12 3 words`, and returns how many. the word count is the block's payload, which is a power-of-two chunk minus the 5 word header, not the size you asked for. the table holds 64 sites. freed blocks stay in it until the storage is reused, which is how a double free still knows the line. nothing here changes programs that never say `memdebug`. +the line is the `new` that made the block. a double free prints and returns; it does not smash the block. `rt.memreport()` prints one line per recorded object that is still live, `leak: line 12 3 words` (with ` (drop)` after it when the object's drop never ran), and returns how many. the word count is the block's payload, which is a power-of-two chunk minus the 5 word header, not the size you asked for. the table holds 64 sites. freed blocks stay in it until the storage is reused, which is how a double free still knows the line. nothing here changes programs that never say `memdebug`. ### line tables and backtraces diff --git a/docs/SPEC.md b/docs/SPEC.md index 4406123..bb0f44f 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -629,7 +629,7 @@ o3 int sum(fixed int[?] a) { int s = 0; for (int i = 0; i < len(a); i++) s += a[ ### 4.11 memdebug -`memdebug on;` turns on heap header checks and allocation-site tracking from this point in the file (top level) or from this point in the function (statement); the runtime enables the checks before `main` for the top-level form. `memdebug scrub;` additionally fills freed payloads with 57005; `memdebug off;` stops. with checks on, a use after free prints `zy: use after free from line N`, a double free prints `zy: double free from line N` and returns, a stray address prints `zy: bad heap pointer`. `rt.memreport()` prints a line `leak: line N W words` per live recorded object (W is the n words new asked for, not the block's payload) and returns the count. +`memdebug on;` turns on heap header checks and allocation-site tracking from this point in the file (top level) or from this point in the function (statement); the runtime enables the checks before `main` for the top-level form. `memdebug scrub;` additionally fills freed payloads with 57005; `memdebug off;` stops. with checks on, a use after free prints `zy: use after free from line N`, a double free prints `zy: double free from line N` and returns, a stray address prints `zy: bad heap pointer`. `rt.memreport()` prints a line `leak: line N W words` per live recorded object (W is the n words new asked for, not the block's payload), with ` (drop)` after it when the object has a drop that hasn't run (6.15: a leaked resource), and returns the count. ### 4.12 hints diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc index 099ca2e..3d448b0 100644 --- a/src/runtime/rt.zc +++ b/src/runtime/rt.zc @@ -1627,7 +1627,9 @@ int memreport() { if (site_p[i]) { p = site_p[i] ^ MAGIC; if (isobj(p)) { - put_s("leak: line "); put_n(site_line[i]); put_s(" "); put_n(paylen(p)); put_s(" words\n"); // n, the words new asked for + put_s("leak: line "); put_n(site_line[i]); put_s(" "); put_n(paylen(p)); put_s(" words"); // n, the words new asked for + if ((p[-4] & (F_HASDROP | F_DROPPED)) == F_HASDROP) put_s(" (drop)"); // a leaked resource: its drop never ran + put_c(10); n++; } } diff --git a/tests/conformance/4-statements/memdebug_report_drop.zc b/tests/conformance/4-statements/memdebug_report_drop.zc new file mode 100644 index 0000000..cf33b69 --- /dev/null +++ b/tests/conformance/4-statements/memdebug_report_drop.zc @@ -0,0 +1,15 @@ +// expect: leak: line 9 1 words (drop)/leak: line 10 2 words/207/ +// spec 4.11, 6.15: rt.memreport() marks a leaked value whose drop never ran with (drop): a leaked resource, not just words +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +struct res { int fd; void drop() { rt.put_s("closed "); } } +memdebug on; +fn main() { + unchecked { + res* f = rt.new(res); + int* q = rt.new(2); + pn(rt.memreport() * 100 + 7); + } + return 0; +} +// zc0: skip -- cgit v1.2.3