aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 02:37:51 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 02:40:51 +0330
commitfd18cb8fa5117b98939db553c2f61dd83b84d7d8 (patch)
tree73ffa71eb12442df94a1fb7c095e3eaed338787b
parentdestructors: hint: x: has drop and is live at spawn; the worker's copy drops ... (diff)
rt: memreport marks a leaked value whose drop never ran with (drop); spec 4.11, readme, memdebug_report_drop.zc
-rw-r--r--README.md2
-rw-r--r--docs/SPEC.md2
-rw-r--r--src/runtime/rt.zc4
-rw-r--r--tests/conformance/4-statements/memdebug_report_drop.zc15
4 files changed, 20 insertions, 3 deletions
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