escape (fix): a node walked more than once is scratch only if every walk found it LOCAL

A field's default is one expression, walked at every 'new' of its record: marked LOCAL by a frame's
temporary, it stayed marked when a record a pool keeps was made from it, and that record's list came
from the frame's scratch. The marks are now taken off any node one walk found kept.
examples/lang/arena_defaults.ludic is the case (a pool's record made in frame 3, a temporary of the
same type every frame): foundations 1315baf crashes on it poisoned; this prints '497 124747', as the
heap does. In ludic-dev test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 17:17:33 +03:00
parent 1315baf332
commit bad7c4a255
5 changed files with 44624 additions and 44209 deletions

View file

@ -819,6 +819,14 @@ function alloc_fence_auto_case() -> void {
# 25.3: a pool that keeps records across frames through a generic push must not take them from the
# frame's scratch - poisoned on every reset it prints what the heap does
function arena_defaults_case() -> void {
let lbl = "arena_defaults.ludic (--arena: a field's default made for a kept record and a frame's temporary stays on the heap for both)"
let b = `{tmp_dir()}/e_arena_defaults`
if not shq(`bin/ludicc --arena examples/lang/arena_defaults.ludic -o {b} 2>/dev/null`) { bad2(lbl, "did not build"); return }
let poisoned = capture_line(`R3D_ARENA_CHECK=1 R3D_ALLOC_FENCE=off {b} < /dev/null`)
if (poisoned == "497 124747") { ok(lbl) } else { bad2(lbl, `poisoned [{poisoned}]`) }
}
function arena_pool_case() -> void {
let lbl = "arena_pool.ludic (--arena: a generic push into a kept pool keeps its records and its buffer on the heap; 0xDD poison changes nothing)"
let b = `{tmp_dir()}/e_arena_pool`
@ -1127,6 +1135,7 @@ function cmd_dev_test() -> int {
alloc_fence_leak_case()
alloc_fence_auto_case()
arena_pool_case()
arena_defaults_case()
feat_case("lang/nested_templates", "", "outer [inner 3 {x}] \"`}\" end", "nested_templates.ludic (a template literal inside another's hole)")
feat_case("lang/aliases", "", "10 5 3", "aliases.ludic (L6: a namespace method declared as an alias of a function, labelled or by its parameters)")
reject_case("rejected/alias_arity", "this call to Trail.length leaves out to, which has no default", "an alias's arguments are checked against its target")