From 1995de5c2890c03fdc825841ab21ab97b3741c32 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Thu, 20 Aug 2026 03:32:31 +0200 Subject: [PATCH] fix(compiler): return of a Text interp of a place handed out the borrow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - emit_return's place test matched only bare Ident/Field/Index, so `return "${p.content}"` (p a loop borrow) returned the part's own string; the caller's eventual drop freed it under the container — arena corruption surfacing two requests later (multipart slice) - the return test now sees through Interp exactly as copy_place_text does (is_borrowed_value_t, container reads excluded); bare Ident/Field/Index behavior at return unchanged - interp-borrowed-field fixture grows the return flavor (fn first), 50 iterations exact - gates: oop-e2e 89/0 (ASan stage), woc-test green Co-Authored-By: Claude Opus 5 (1M context) --- compiler/src/CODE-LOGIC.md | 8 ++++++-- compiler/src/emit.ml | 11 ++++++++++- tests/corpus/run/interp-borrowed-field/fixture.wo | 11 +++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/compiler/src/CODE-LOGIC.md b/compiler/src/CODE-LOGIC.md index 2cfc085..21b5877 100644 --- a/compiler/src/CODE-LOGIC.md +++ b/compiler/src/CODE-LOGIC.md @@ -140,8 +140,12 @@ so a Text-typed SINGLE-SEGMENT interpolation of a place register through a `let`/assignment boundary uncopied — the binding aliased the row's field and its overwrite freed it (release-build crash the arena hid from ASan). It now asks `is_borrowed_value_t && not is_container_read`, -exactly `drop_fresh_text`'s place test. Pinned by -`tests/corpus/run/interp-borrowed-field`. +exactly `drop_fresh_text`'s place test. The RETURN boundary had the same +hole (`return "${p.content}"` handed the caller the part's own string — +the multipart slice's arena corruption, two requests removed from the +crash): emit_return's place test now sees through `Interp` the same way, +while bare Ident/Field/Index behavior there is unchanged. Both flavors +pinned by `tests/corpus/run/interp-borrowed-field`. Two rules the measurements imposed, both easy to get backwards: diff --git a/compiler/src/emit.ml b/compiler/src/emit.ml index 58971d8..4a28fac 100644 --- a/compiler/src/emit.ml +++ b/compiler/src/emit.ml @@ -3756,7 +3756,16 @@ and emit_return (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) (opt : Ast.ex `return lv` inside `for lv in [...]` is WO-E304 and the workload's own level classifier cannot be written at all. *) let t = - let is_place = match e.Ast.kind with Ast.Ident _ | Ast.Field _ | Ast.Index _ -> true | _ -> false in + (* an interpolation is seen through exactly as copy_place_text sees + it: `return "${p.content}"` (p a loop borrow) handed the caller the + part's own string — the caller's drop then freed it under the + container, the multipart-400 arena corruption *) + let is_place = + match e.Ast.kind with + | Ast.Ident _ | Ast.Field _ | Ast.Index _ -> true + | Ast.Interp _ -> is_borrowed_value_t p f e && not (is_container_read e) + | _ -> false + in let is_text = match ty_of_expr p f e with Some ty -> field_kind p ty = 3 | None -> false in if is_place && is_text then begin let w = alloc_temps p f e.pos 1 in diff --git a/tests/corpus/run/interp-borrowed-field/fixture.wo b/tests/corpus/run/interp-borrowed-field/fixture.wo index 33779ef..d7635ca 100644 --- a/tests/corpus/run/interp-borrowed-field/fixture.wo +++ b/tests/corpus/run/interp-borrowed-field/fixture.wo @@ -16,6 +16,15 @@ class Tab { } return out; } + + -- the RETURN flavor: an interpolation of a borrowed field handed straight + -- to the caller must be the caller's own copy, not the row's string + fn first() -> Text { + for r in self.rows { + return "${r.method}"; + } + return ""; + } } fn main() -> Int { @@ -26,6 +35,8 @@ fn main() -> Int { while i < 50 { let s = t.scan(); if s != "GET, POST" { print("bad: ${s} at ${i}"); return 1; } + let h = t.first(); + if h != "GET" { print("bad first: ${h} at ${i}"); return 1; } i = i + 1; } print("ok");