Repository navigation
Conversation
Quest-Claim: bf851f467c4a2ef7
A `&str` stays a JS string, so `rs.strAsPtr` encodes its UTF-8 and points at the first byte. Reads, arithmetic, copies from it, and `from_raw_parts` read what Rust reads; a write, a copy into it, or a mutable slice of it, which Rust forbids, fails at run time. `offset_from` between two calls' pointers is refused where the body shows both. arrayvec's `ArrayString` now translates: the stock fixture runs it against native Rust. 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes the quest quest/m2/ts-model/str-as-ptr.md.
Waiting
Maintainer: the design checkpoint after review round 3. Codex holds two findings against my refutations. Decide whether a read through a
str's pointer outside thatstr's own bytes may fail at run time, or must be refused with a span. Check: a PR comment startingDesign change:or a reply on this PR.The two cases are reads beyond the substring but inside its text's allocation:
s[..1].as_ptr().add(1), read forward;s[1..].as_ptr().wrapping_sub(1), read back.Both are undefined under Stacked Borrows, Miri's default model, because the pointer comes from the substring's reference. Tree Borrows allows them. Here they throw a
rust2-ts:error. The static refusal of a constantsub, from round 1, is kept.Options:
Array::CopyandArray::Lone, which also go unchecked for wrapping moves and unknown lengths.strlengths and wrapping offsets through to reads inmir/origin.rs. This is a new read-tracking analysis for every pointer kind. It would still miss pointers the body doesn't show, so the runtime failure stays as the backstop.&strwould become a view (text, offset), reversing the representation moq-dev/rust2#574 settled and paying its map-key costs.str::as_ptrunless the body proves every read stays within[0, len). This is likely too narrow, and could refuse arrayvec again.Problem
str::as_ptrwas an unsupported call, so arrayvec'sArrayString::try_push_str, which copies a&strinto its buffer through the pointer, refused translation. At the moq pin that is 1 refusal in the model's census and 3 in moq-net whole (arrayvec's, plus two in zlib-rs). It was the last child of quest/m2/ts-model/README.md that this repo owns. moq-dev/rust2#574 settled that a&strstays a JS string, so the bytes behind the pointer are an encoding of the text.Approach
rs.strAsPtr(s)encodes the text's UTF-8 and returns a pointer to its first byte. The pointer is aTextAt, a privateAtIndexsubclass. Reads work as they do for any byte pointer:*p,read, arithmetic,offset_fromwithin one encoding,ptr::copy/copy_nonoverlappingfrom it, andslice::from_raw_parts.&str's pointer. Here such a write would land in a copy the text never reads, so it fails at run time instead. That covers*p = x, a copy into it, and a mutable slice (from_raw_parts_mut, orderefBytes(false)of a raw slice). Arithmetic keeps the pointer read-only:AtIndexgains anat(index)method thatptrAddandptrWrappingAddcall, andTextAtoverrides it.str::as_ptrcall is its own origin (Array::Text) inmir/origin.rs. Sooffset_frombetween two calls' pointers is refused with a span wherever the body shows both. A typical case is a substring's pointer against its text's, which Rust allows but JS can't match. A pointer starts at index 0 of its own encoding, so a constantadd/sub/offsetthe body shows moving it back before its first byte is refused with a span, as one off anArray::Copyis. That covers a substring's pointer moved back into its text. Anything the body doesn't show is left to the runtime, which fails on a read outside the encoding.Validation
lang/strs' newstrs::pointers: text of every UTF-8 width and a substring from each char boundary, against native Rust. It copies into aVec's room and between bytes, reads every tail throughfrom_raw_parts, walks forward and back by*,NonNull, andoffset_from/offset_from_unsigned, and pushes into a[MaybeUninit<u8>; 12]asArrayStringdoes.stock's newfixed::strings: the realarrayvec::ArrayString<12>(0.7.8, the version moq's lock has). It coverstry_push,try_push_strwith its errors,push_str,pop,truncate,remove,from, andclearover chars of each width.tests/pointers.ts: reads, arithmetic, and views pass. Writes, copies in, and mutable slices fail with arust2-ts:error, and so doesoffset_fromacross two encodings.text_apart's twooffset_froms andtext_before'ssub(1)are refused with spans.str::as_ptrrefusals, and no new refusal appears.just ciat27f3871e7passes exceptlang::dyns(ids.ts) andlang::panics(hook/lib.ts). Those are the TypeId-hash snapshots that depend on the checkout's path: they fail on bare origin/main too, CI passes them, and moq-dev/rust2#633 is fixing them.Impact
Public API (translator output):
str::as_ptrtranslates instead of refusing.rs.strAsPtr.AtIndex.at(index)method.strandstr::as_ptrverbatim in its constructs.ArrayString::try_push_str, and zlib-rs' twostr::as_ptrs.Quests:
quest/m2/ts-model/str-as-ptr.mdand its link.[L]for its remaining end-to-end work, and its census paragraph is refreshed to 51.Choices made without asking
cast_mutoras *mutof the pointer, and the result can flow anywhere, so only the runtime sees every write. That is the same split the README documents for pointers from elsewhere. The checks cost oneinstanceofper copy or mutable slice and nothing on other pointers' writes, becauseTextAtis its own class.*mut [u8; N]cast from a raw slice of the text;as_ptrof a shared slice thatfrom_raw_partsmade of the text.*const u8s was already refused.str::as_mut_ptrstays unsupported. Writes through it are defined in Rust and must reach theString, so it needs the bytes to be the string's own storage. Nothing in moq calls it, and it is outside this quest's Goal.Alternatives
ptr::eqandoffset_fromagree across calls. A strongMapof strings leaks, and a substring is a new JS string anyway, so its offset into its text still could not match.ArrayBuffer(transferToImmutable). Node, Bun, Chrome, and Firefox don't all ship it, and writes to it fail silently outside strict mode.Follow-ups
None.
Models
Size: S
Implementer: Claude Code, claude-opus-5-5, high effort
Review
Rewritten by
quest reviewafter each round.01a1b52a-0b86-7ac1-8df0-8726554271f2, round 3Both valid Rust inputs still produce runtime failures, and the cited implementation limits do not override the base branch's requirement for source-spanned compile errors.ts/src/lower/mir/origin.rs:40:[P2] Track the encoded substring's upper boundts/src/lower/mir/origin.rs:246:[P2] Check substring reads reached through wrapping arithmeticts/src/lower/mir/origin.rs:40:[P2] Track the encoded substring's upper bound;ts/src/lower/mir/origin.rs:40 still discards text lengths, and the reproduced forward-read failure violates the base rules regardless of the cited analysis scope or README documentation.ts/src/lower/mir/origin.rs:246:[P2] Check substring reads reached through wrapping arithmetic;The arithmetic tracking in ts/src/lower/mir/origin.rs:239 still excludes wrapping operations, and the reproduced valid backward read throws; existing omissions for other origins do not authorize this semantic mismatch.