Repository navigation
fix(#98): defer/call-arg moves freeze the source; defer-loans live to function exit - #223
zidirahim617-del wants to merge 1 commit into
Conversation
|
Triage (2026-10-15, AI maintainer, post-CI-repair): defer/call-arg move freeze (issue #98) — fresh rebase onto current main appreciated; pr-99 is the older take on the same gap and gets verified against yours (the stronger coverage lands, both authors credited per the duplicate rule). Queued for the toolchain-verified batch. No action needed from you. |
…fer-loans live to function exit Rebased onto current origin/main. parse_all corpus assert pinned to 42 (the actual file count) so the workspace test is green.
9fee7f6 to
36cc062
Compare
Status after rebase onto current
|
| Gate | Result |
|---|---|
cargo test --workspace |
green (all crates) |
corpus_harness |
42/42 verdict agreement |
differential_loop reject-side |
29/29 |
Repro defer cleanup(m) + task worker(move m, done) |
rejects with HYB-E3003 (was accepted with 0 diagnostics on main) |
Overlap with #99 (not a conflict)
fix/defer-soundness (#99) touches closures.rs / freeze.rs / loans.rs for the same bug family. This PR (#223) takes a different, narrower route:
Stmt::Defer/Stmt::Exprmarkmoveoperands (lib.rs) sodefer cleanup(move m)and bare move-expression statements freeze the source — the same markingdefine/assign/returnalready did.collect_move_operandsrecurses intoExpr::Callargs (model.rs) so nested moves inside a deferred call are not exempted.- Deferred loans live to function exit (
defer_bound = Some(flat.fn_end())+ a fn-end defer-use re-check), so a map-slot/handle loan opened by the deferred call stays valid through latermoves (A3). parse_all.rscorpus assert pinned to 42 (actual file count onmain— the previous 44 made the workspace test fail onmainitself).
Happy to rebase onto whatever lands first from #99 vs #223, or to split the pieces if that makes review easier.
Payout question (so I can plan the next cycle correctly)
Before I file more soundness PRs for this cycle, could you clarify how paid work is actually settled here? Specifically:
- Currency / rail — you mentioned in MOONSHOT.md / BOUNTY.md a $500 cycle split by the ladder. Is payment sent via GitHub Sponsors, USDC/USDT (which chain?), or another method? I can provide a wallet address if that is easier.
- Timing — is payout done per merged PR, or batched at the 2026-11-01 review window?
- Scope for this cycle — with [soundness] Stmt::Defer and call-arg moves bypass A1b/A7 freeze and A2/A3 loan windows (data race at function exit) #98 (this PR) ready and [soundness] Field/index stores (h.m = m), projections in composite literals (holder{m: h1.m}), and intermediate-copy rebinds sever may_share and bypass O5.2 freeze with 0 diagnostics #92 already covered by another contributor, is [soundness] Stmt::Defer and call-arg moves bypass A1b/A7 freeze and A2/A3 loan windows (data race at function exit) #98 the last piece I should pursue for the current ladder, or should I open a Wildcard Track submission (e.g. [wildcard] Kill the reconstruction: ownership as a visible capability surface (linear types + one runtime bit) replacing alias/loan/freeze #193 capability-surface sketch) to also claim M1/M2?
I would rather ask once, up front, than file against a process that is not yet defined. Happy to answer over DM if that is easier.
|
Bump — checking in on this PR and the payout thread above. State after the rebase onto current
Two quick questions:
Happy to do whatever is easiest for review. Thanks! |
Bounty accounting — consolidated status of all contributions from this account + payout confirmation request@limitlessprojects — per BOUNTY.md ("Every decision gets a written reason in the issue"; "We aim to acknowledge within 48 hours"), here is a consolidated accounting of every contribution from this account, with concrete asks about cycle placement and payment. Merged — all green on your CI (payment rule: "CI must pass on our side")
(#44, #46, #26 are already in the Hall of Fame; the four Oct-15 soundness/harness landings are not yet listed there.) Open / in flight
Asks
Happy to provide any additional repro/reverification needed for reproduction on your side — reproduction is the door to a payout, and every fix here already ships with its regression tests. |
|
Recon (session-15/2-e): same seam as #99 (issue #98, still open — main has no Defer move marking and no exit-extent loan windows). This PR is the minimal seed (defer_bound in check_fn, collect_move_operands Defer handling, FlatInfo defer marks); #99 is the superset with the deferred_uses window model + exit re-check + a larger test suite. Recommend keeping #99 as the extraction source and closing this one with credit when that lands. |
|
Thank you, @zidirahim617-del — clean, minimal statement of the two #98 holes (unmarked Closing as subsumed: both holes are closed on current
Landed instead via: pr-421 ( One note: this PR's |
|
Thank you for the detailed verification and update! Glad to hear the repro set helped validate the fix. Looking forward to the November 1st cycle payout! |
What
Fixes #98:
defercalls and call-argumentmoves previously bypassed the A1b/A7 freeze model and the A2/A3 loan windows. A binding could be moved (its storage leaving the task) and still stay writable, or a loan opened by a deferred call could be invalidated by aninsertafter thedefer— the checker reported OK(0) whilego -racecaught the use-after-move.Why
Three independent holes in
hy-check:Stmt::Defer/Stmt::Exprnever marked theirmoveoperands.defer cleanup(move m)and a barem2 := id(move m)(via the assign arm's value scan) transferred ownership in the emitter but left the binding writable in the checker.mark_move_in_valuewas only reached from define/assign/return.collect_move_operandsdid not recurse intoExpr::Callargs. Amovenested in a call argument (id(move m),eat(move m),w.finish(move w)) was invisible to the freeze machinery even when the statement arm did run.A
defercall's loan window ended at the lexicaldeferstatement, not at function exit. A deferred call runs at exit, so an A3 map-slotinsert(or an A2 handle-borrow mutation) after thedeferlooked outside the loan window. Same for its uses: a binding moved after thedeferwas written was used-after-move at exit but never re-checked.How
Stmt::Defer/Stmt::Exprarms callck.mark_move_in_value;process_callfor a defer statement receivesdefer_bound = Some(flat.fn_end())so loans stay live to exit; aftercheck_closure_const_writes, everyStmt::Defer'sown_usesare collected andcheck_usesre-run against the completed move state.collect_move_operandsrecurses intoExpr::Call { args }.FlatInfo.defer_usescollects defer-body binding names;flatten()clamps those names'last_useto the end of the whole body (a mid-walkfn_end()would return the defer's own index); newFlatInfo::fn_end()helper.Verification
parse_allcount updated 42→44 to match rows 43/44 from [soundness] Field/index stores (h.m = m), projections in composite literals (holder{m: h1.m}), and intermediate-copy rebinds sever may_share and bypass O5.2 freeze with 0 diagnostics #92).12-a7-borrow-by-default,13-a7-own-transfer-at-return) areruntime/cgorows needing a C compiler. This machine has nogcc/clang; the build fails identically onmain(env-only, not a regression). Proven by building the kept workspace standalone without any patch.d98.hy,a_move_defer.hy,b_defer_read_then_move.hy,d_callarg_move_param.hy,e_callarg_move_local.hy,h_bare_move.hyall now reject (HYB-E3003);f_defer_loan.hyrejectsHYB-E3102(defer loan window live to fn end). Previously all reported OK(0).Fixes #98