Skip to content

feat(ts)!: abort where Rust leaves behavior undefined, locate zip's and tokio's panics, and refuse erasing a pointer not shown an Arc's - #661

Merged
kixelated merged 14 commits into
mainfrom
quest/m3/ts-runtime-throws
Nov 8, 2026
Merged

kixelated merged 14 commits into
mainfrom
quest/m3/ts-runtime-throws

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Implements part of quest/m3/ts-runtime-throws.md and rewrites it to what remains.

Problem

The runtime threw a rust2-ts: error in places where native Rust would keep running. The quest asks to sort every such throw: guards against Rust UB stay, worded as std's precondition aborts; real gaps either translate exactly or get refused at translation.

Approach

Each git grep 'rust2-ts: ' ts/runtime site, sorted:

  • UB guards now abort as std's debug precondition checks do. rs.violated(check) throws rs.Abort with std's unsafe precondition(s) violated: ... text and runs no hook. It now covers:
    • a read or write through null, dangling, or a pointer moved off either (nowhere, plus NO_MEMORY, the empty array null and dangling now point into);
    • a write through str::as_ptr;
    • Arc/Rc::from_raw of a pointer no into_raw gave, or of a dropped value;
    • dealloc/realloc of memory alloc didn't give;
    • a TrustedLen zip that runs out early. This is std's unwrap_unchecked check, worded as std words it.
  • Panics a hook couldn't locate.
    • std's Zip::next_back unreachable!() (an ExactSizeIterator whose len lies) is now located at the zip_impl_general_defaults! call, the way native places a macro's panic.
    • tokio's oneshot called after complete: at a mapped <Receiver<T> as Future>::poll in a hook program, translation takes the span of the one panic! call in tokio's own MIR for that poll and passes its rs.Location to rs.oneshotPoll. rustc names the file as native does under any --remap-path-prefix, and a build without tokio's sync reaches no such call, so the source-file text search is gone.
    • A forced-hook survey found no other unlocated panic. The translator was forced into hook mode, and the runtime threw on any panic without a location, across all lang fixtures. The only hit was set_hook inside a hook, which an unpatched runtime aborts before it needs a location. The survey patch was local and is not committed.
  • Pointers whose origin translation can't see (decided below):
    • rs.arcErase is now refused at translation: a pointer to a value JS copies cast to *const () translates only where the body shows an Arc's or Rc's into_raw or as_ptr gave it, or that it is null or dangling. Its run-time check now guards the translator.
    • ptrUnerase, offset_from across arrays, AtIndex.reach, raw slices of a field's byte, and Bytes::slice_ref of copied bytes stay loud run-time checks. AGENTS.md's "never a runtime failure" rule now allows one for an unsafe pointer from elsewhere, citing m3's unsafe policy.
    • alloc's bytes (read, written, or cast to another type through a byte pointer from elsewhere) stay the quest's remaining work. Refusing them needs a use analysis of the byte pointer alloc returns, to show it never leaves the body untyped, which the translator doesn't have yet. That is more than a modest change, so it is left out of this PR.
  • Not gaps, left as they are:
    • a range with no Step and a fmt Count written as a placeholder: no translation produces either;
    • rs.u64Of and its kin given a number past 2^53: a JS caller's input;
    • the hook's location guard;
    • tokio's semaphore overflow, which the channels never reach;
    • a substring's read outside its bytes (#634's decision). A mutable slice through str::as_ptr stays with it, since only Stacked Borrows makes it UB.
  • ptr::eq of an array as_chunks split: an exact path exists (a copy that remembers its slice and offset). It stays in the quest, now building on the rs.ArraySlice that #662 landed.
  • *const [T] comparisons of values JS copies: no exact path exists while a &str is a JS string (#574), so the refusal stays.

Tests

  • ts/tests/pointers.ts now tells the UB aborts apart from the remaining rust2-ts: failures, including a null pointer moved by add.
  • lang/panics with the hook feature:
    • hooked::sited kind 86 zips a length-lying iterator from the back.
    • hooked::tokio_stock polls a oneshot receiver after it completes.
    • caller.ts now names every sited kind as an input, since 151 random inputs could miss one.
  • Every fixture build now also remaps tokio's src/sync directory to tokio/channels, round 3's case, which a text search by path keeps missing. The hook snapshot names tokio/channels/oneshot.rs:1291:13, and the differential matches native. tokio without sync needs no test of its own: no mapped poll exists there, so nothing is looked up.
  • reject: pointers::erased_unshown casts a parameter's pointer to *const (). lang/pointers drops its erase_flag case, which tested the old run-time path, and adds null pointers erased and compared, made inline, as a constant, and through casts to and from MaybeUninit, and an Arc's pointer erased through such a cast.

Impact

  • Runtime (breaking for code that catches these errors): the UB guards above throw rs.Abort with std's precondition text, where they used to throw an Error starting rust2-ts:.
  • New runtime exports: rs.violated, rs.nowhere, rs.NO_MEMORY.
  • Runtime signature: rs.oneshotPoll takes an optional at?: Location, its panic's location.
  • Translator output: a hook program's sites table gains zip_next_back. Its rs.oneshotPoll calls pass tokio's location. A program without a hook is unchanged.
  • Translation (breaking): casting a pointer to a value JS copies to *const () is refused unless the body shows an Arc's or Rc's raw pointer, null, or dangling. Migrate by casting the Arc::into_raw/as_ptr result where it is made.
  • No wire impact.

Alternatives

  • Calling the hook for UB aborts, as std's panic_nounwind does: rejected, since Rust defines nothing there and these sites have no location.
  • A Uint8Array(1) for each u8 field whose address is taken: rejected for now, since any &mut u8 a callee turns raw would need one.

Decisions

  • ✅ Locating tokio's oneshot panic (maintainer, 2026-11-07, at the round-3 design checkpoint): take the panic! call's position from tokio's own compiled code for Receiver::poll at the mapped call, so rustc names the file as native does under any --remap-path-prefix. Delete the source-file text search, and test a remapped build.

    • Rejected: skipping by module; failing whenever the file is missing; dropping tokio's site.
  • ✅ The pointers only run time sees (maintainer, 2026-11-07): refuse at translation, with a span, arcErase and reads of alloc's bytes as values. Keep loud run-time checks for ptrUnerase, offset_from across arrays, AtIndex.reach, raw slices of a field's byte, and Bytes::slice_ref of copied bytes. Amend AGENTS.md to allow a loud run-time check for an unsafe pointer from elsewhere whose origin translation can't see, citing m3's unsafe policy. Implement the two refusals here if modest: arcErase is done; alloc's bytes is left in the quest (see Approach).

    • Rejected: refusing all of them at translation, which refuses pointer arithmetic on any pointer parameter; keeping all as run-time checks.
  • ✅ bytes' panics (maintainer, 2026-11-07): locate them from bytes' compiled code, as tokio's now are, since a remap inside bytes fails translation of a hook program. This goes into the quest's remaining work, not a new quest.

Choices made without asking

  • Each UB guard aborts without running the hook.
  • A mutable slice through str::as_ptr stays a rust2-ts: error rather than an abort, to match feat(ts): translate str::as_ptr as a read-only pointer into its UTF-8 #634's call on Stacked-Borrows-only UB.
  • Unreachable guards (ranges, fmt Count, tokio's semaphore) are classified rather than changed.
  • bytes' sites keep main's source search. Moving them to compiled code is now the quest's remaining work (see Decisions).
  • A null or dangling pointer erased to *const () still translates, since the runtime's answer for it is exact.
  • Rc joins the census's verbatim words, as Arc is, for the new refusal's message.

Follow-ups

None. The bytes follow-up is now in the quest's remaining work.

Models

Size: L
Implementer: Claude Code, claude-opus-5-5, high effort

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, xhigh effort
  • Session: 01a1b8b4-1012-7200-b461-124a52955a9c, round 4
  • Head: d466e2a
  • Base: origin/main at b90529b
  • Score: 10/10, No defects found in the specified branch diff; focused pointer and panic differentials, runtime tests, and TypeScript checks pass.
  • Findings: none

Head d466e2a merges main (#662, #665). Conflicts: ts/runtime/rs/task.ts's import list, which now takes both sides, and the quest file, which keeps this PR's plan and drops the deleted rs.Slice quest's links. It also adds bytes' panics to the quest's remaining work. quest check --fix and just ci pass, with no snapshot changes.

(Written by Claude Opus 5.5 with high effort)

@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 3 commits November 7, 2026 14:36
…or undefined, and locate zip's and tokio's panics

A read or write through a pointer to no memory, a write through str::as_ptr,
Arc::from_raw of a pointer no into_raw gave or of a dropped value, and
dealloc or realloc of memory alloc didn't give now abort with std's
"unsafe precondition(s) violated" wording rather than a rust2-ts error.
A hook program now locates std's Zip::next_back unreachable!() and tokio's
oneshot "called after complete", checked against native.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…with the decision they wait on

Also keep a mutable slice through str::as_ptr a rust2-ts error, as Stacked
Borrows alone makes it undefined, and document the aborts and tokio's sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Throw only where Rust panics feat(ts)!: abort where Rust leaves behavior undefined, and locate zip's and tokio's panics Nov 7, 2026
kixelated and others added 2 commits November 7, 2026 15:50
A hook program whose tokio lacks the sync feature compiles no oneshot.rs, whose
panic nothing can reach, so its site is left out rather than refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x remap leaves it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 4 commits November 7, 2026 16:48
…rase of a pointer the body doesn't show is an Arc's

Tokio's `Receiver::poll` panic site now comes from the `panic!` call in
tokio's own MIR at the mapped call, so rustc names the file as native
does under any --remap-path-prefix; the source-file text search is gone.
The fixtures remap tokio's src/sync to cover it.

A pointer to a value JS copies cast to `*const ()` is refused unless the
body shows an Arc's or Rc's into_raw or as_ptr gave it, or that it is
null or dangling. AGENTS.md allows a loud run-time check for an unsafe
pointer from elsewhere, under m3's unsafe policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title feat(ts)!: abort where Rust leaves behavior undefined, and locate zip's and tokio's panics feat(ts)!: abort where Rust leaves behavior undefined, locate zip's and tokio's panics, and refuse erasing a pointer not shown an Arc's Nov 8, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: the maintainer settled the round-3 checkpoint on 2026-11-07.

  • tokio's oneshot panic is now located from tokio's own MIR: at a mapped <Receiver<T> as Future>::poll in a hook program, translation takes the span of the one panic! call in that poll's compiled body and passes its location to rs.oneshotPoll. rustc names the file as native does under any --remap-path-prefix, a build without sync looks nothing up, and the source-file text search (TOKIO, crate_sites) is gone; bytes keeps main's search. The fixtures now remap tokio's src/sync to tokio/channels, round 3's case, and the differential matches native at tokio/channels/oneshot.rs:1291:13.
  • rs.arcErase is refused at translation unless the body shows an Arc's or Rc's raw pointer, null, or dangling. ptrUnerase, offset_from across arrays, AtIndex.reach, a field byte's raw slice, and Bytes::slice_ref of copied bytes stay loud run-time checks, which AGENTS.md now allows for an unsafe pointer from elsewhere under m3's unsafe policy.
  • alloc's bytes stay in the quest: refusing them needs a use analysis of alloc's byte pointer, more than a modest change.

The review count restarts with quest review --new.

(Written by Claude Opus 5.5 with high effort)

@kixelated
kixelated marked this pull request as ready for review November 8, 2026 01:37
@coderabbitai

coderabbitai Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 17f459f2-c896-498b-be2f-df15f9f8e128
Commits

Reviewing files that changed from the base of the PR and between 2d402c5 and d466e2a.

Files ignored due to path filters: 1

  • ts/Cargo.lock is excluded by !**/*.lock
Files selected for processing: 33

  • AGENTS.md
  • quest/m3/README.md
  • quest/m3/ts-runtime-throws.md
  • quest/m3/ts-unsafe/README.md
  • ts/README.md
  • ts/fixtures/lang/panics/Cargo.toml
  • ts/fixtures/lang/panics/caller.ts
  • ts/fixtures/lang/panics/hook/expected/hooked.Short.ts
  • ts/fixtures/lang/panics/hook/expected/hooked.ts
  • ts/fixtures/lang/panics/hook/expected/lib.ts
  • ts/fixtures/lang/panics/hook/expected/tokio/sync/oneshot/error.RecvError.ts
  • ts/fixtures/lang/panics/src/hooked.rs
  • ts/fixtures/lang/panics/src/lib.rs
  • ts/fixtures/lang/pointers/expected/pointers.ts
  • ts/fixtures/lang/pointers/src/pointers.rs
  • ts/fixtures/reject/census.txt
  • ts/fixtures/reject/expected.txt
  • ts/fixtures/reject/src/pointers.rs
  • ts/fixtures/reject/ts.toml
  • ts/runtime/rs.ts
  • ts/runtime/rs/alloc.ts
  • ts/runtime/rs/collections.ts
  • ts/runtime/rs/iter.ts
  • ts/runtime/rs/task.ts
  • ts/runtime/rs/tokio.ts
  • ts/src/census.rs
  • ts/src/lower/caller.rs
  • ts/src/lower/mir/origin.rs
  • ts/src/lower/mir/rvalue.rs
  • ts/src/lower/pointer.rs
  • ts/src/lower/tokio.rs
  • ts/tests/fixtures.rs
  • ts/tests/pointers.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head 8280fdd5cc94a1d753bd56ae1962774c5b4939cd against base 8254acb90050f21f442be92f93e2caaaf3685dff.

No actionable defects found in the inspected changes and surrounding code. I checked the UB-abort paths, pointer-erasure provenance and refusal logic, zip and Tokio panic-site lowering, and the corresponding runtime assertions, rejection fixtures, cast-chain cases, and remapped hook snapshots. The deferred allocation-byte behavior remains explicitly tracked rather than treated as fixed.

Direction: the implementation is consistent with the documented scope. Preserve the differential coverage for null/dangling and Arc cast chains, zip's panic location, and repeated oneshot polling when changing these paths.

Validation limits: GitHub-only static review. I did not run builds, tests, type-checking, or CI. This is a COMMENT review, not an approval or merge recommendation.

(Written by review (OpenAI))

…hrows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit ed8ca86 Nov 8, 2026
10 checks passed
@kixelated
kixelated deleted the quest/m3/ts-runtime-throws branch November 8, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant