red test: heap-Str slabs leak under RC discipline
Strengthen the two hs.4-shipped RC-stats E2E assertions back to the
plan-original `live == 0` + `allocs == frees` invariants:
- int_to_str_drop_balances_rc_stats: `let s = int_to_str(42) in
do io/print_str(s)` allocates a heap-Str (allocs=1) but the let-arm
dec at scope close does not fire (frees=0, live=1).
- str_field_in_adt_drops_heap_str_correctly: `Box(int_to_str(42))`
pattern-matched and printed allocates the ADT cell + the heap-Str
slab (allocs=2). The match-arm pattern walk calls
field_drop_call's Str arm on the extracted field (frees=1), but
the outer ADT cell's let-binder is not trackable (frees=1, live=1).
Root cause is the effect-op arg-mode gap surfaced during hs.4: the
uniqueness analyser at uniqueness.rs:289-292 walks every Term::Do arg
in Position::Consume, gating off the let-arm dec at lib.rs:1440
(consume_count == 0 required). EffectOpSig has no param_modes field
to override the Consume-by-default walk. The fix is structural and
queued as the next iter.
These tests were shipped GREEN in hs.4 (134441b) with weakened
`allocs >= N` asserts; that was a fixture-adapted-to-bug anti-pattern.
Asserts restored to the language invariant; tests now RED, locking
in the fix obligation.
This commit is contained in:
+25
-34
@@ -2588,46 +2588,37 @@ fn float_to_str_smoke() {
|
||||
assert_eq!(out, "3.5\n");
|
||||
}
|
||||
|
||||
/// Iter hs.4: `int_to_str` participates in RC bookkeeping under
|
||||
/// `--alloc=rc`. At least one heap-Str allocation must be recorded
|
||||
/// when an `int_to_str(...)` call's result is bound and consumed.
|
||||
/// Note: `live == 0` is NOT asserted yet — the let-binder for an
|
||||
/// `Implicit`-ret-mode App is not trackable in iter 18g.2's RC
|
||||
/// discipline (Implicit is the back-compat-leak lane); refining
|
||||
/// this to Own-ret-mode + a let-arm dec at scope close is queued
|
||||
/// as known debt in the iter hs.4 journal Concerns section. Stdout
|
||||
/// still pins the same `42\n` the smoke test asserts; the program
|
||||
/// runs to clean exit despite the leak.
|
||||
/// Heap-Str RC-discipline: `int_to_str(42)` bound to a let, consumed
|
||||
/// by `io/print_str`, must reach `live == 0` at program exit — every
|
||||
/// heap-Str allocation matched by a drop. RED pin for the
|
||||
/// effect-op-arg-mode gap surfaced by hs.4 (the uniqueness analyser
|
||||
/// walks `Term::Do` args as `Position::Consume`; combined with
|
||||
/// `EffectOpSig`'s missing `param_modes` field, the let-binder for
|
||||
/// the `int_to_str` result is not trackable and no scope-close
|
||||
/// `ailang_rc_dec` is emitted). The fix is on the planning queue;
|
||||
/// this test fails until it lands, locking in the invariant.
|
||||
#[test]
|
||||
fn int_to_str_records_rc_allocation_under_alloc_rc() {
|
||||
let (stdout, allocs, _frees, _live) =
|
||||
fn int_to_str_drop_balances_rc_stats() {
|
||||
let (stdout, allocs, frees, live) =
|
||||
build_and_run_with_rc_stats("int_to_str_drop_rc.ail.json");
|
||||
assert_eq!(stdout, "42\n");
|
||||
assert!(
|
||||
allocs >= 1,
|
||||
"expected at least one heap-Str allocation; got allocs={allocs}"
|
||||
);
|
||||
assert!(allocs >= 1, "expected at least one heap-Str allocation; got allocs={allocs}");
|
||||
assert_eq!(allocs, frees, "every heap-Str alloc must be freed; allocs={allocs}, frees={frees}");
|
||||
assert_eq!(live, 0, "no leaked heap-Str slabs allowed; live={live}");
|
||||
}
|
||||
|
||||
/// Iter hs.4: an ADT carrying a heap-Str field constructs cleanly
|
||||
/// and pattern-matches to the inner Str under `--alloc=rc`. Two
|
||||
/// allocations are observed (ADT cell + heap-Str slab); at least
|
||||
/// one free fires (the match-arm pattern walks the field through
|
||||
/// `field_drop_call`'s Str arm to `ailang_rc_dec`). `live == 0`
|
||||
/// is not yet asserted for the same Implicit-ret-mode reason as
|
||||
/// `int_to_str_records_rc_allocation_under_alloc_rc` — queued as
|
||||
/// known debt. Stdout pins the inner Str's bytes (`42\n`).
|
||||
/// Heap-Str RC-discipline through an ADT: `Box(int_to_str(42))`
|
||||
/// constructs (ADT cell + heap-Str slab allocs), gets pattern-matched
|
||||
/// to extract the inner Str, both reach `live == 0` at program exit.
|
||||
/// RED pin for the same effect-op-arg-mode gap as
|
||||
/// `int_to_str_drop_balances_rc_stats`; the outer ADT cell leaks via
|
||||
/// the same `Implicit`-ret-mode path applied to its own let-binder.
|
||||
#[test]
|
||||
fn str_field_in_adt_records_rc_allocations_under_alloc_rc() {
|
||||
let (stdout, allocs, frees, _live) =
|
||||
fn str_field_in_adt_drops_heap_str_correctly() {
|
||||
let (stdout, allocs, frees, live) =
|
||||
build_and_run_with_rc_stats("str_field_in_adt_heap.ail.json");
|
||||
assert_eq!(stdout, "42\n");
|
||||
assert!(
|
||||
allocs >= 2,
|
||||
"expected ADT cell + heap-Str slab; got allocs={allocs}"
|
||||
);
|
||||
assert!(
|
||||
frees >= 1,
|
||||
"expected at least the heap-Str slab freed via field_drop_call; got frees={frees}"
|
||||
);
|
||||
assert!(allocs >= 2, "expected ADT cell + heap-Str slab; got allocs={allocs}");
|
||||
assert_eq!(allocs, frees, "every RC slab must be freed; allocs={allocs}, frees={frees}");
|
||||
assert_eq!(live, 0, "no leaked RC slabs allowed; live={live}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user