Skip to content

fix(r_memory): gate speculative SEXP recovery under Miri (#63)#197

Open
CGMossa wants to merge 1 commit intomainfrom
fix/issue-63-miri-gate-try-recover
Open

fix(r_memory): gate speculative SEXP recovery under Miri (#63)#197
CGMossa wants to merge 1 commit intomainfrom
fix/issue-63-miri-gate-try-recover

Conversation

@CGMossa
Copy link
Copy Markdown
Collaborator

@CGMossa CGMossa commented Apr 17, 2026

Closes #63.

Summary

`try_recover_r_sexp` is a conservative-GC-style probe: it subtracts the known SEXPREC header size from a data pointer and reads `sxpinfo` bits to check whether the candidate address looks like a SEXP. For pointers that didn't come from an R SEXP (e.g. a Rust-allocated Arrow buffer), the speculative pointer has no valid allocation provenance — defined at the hardware level, undefined under Miri's Stacked / Tree Borrows model.

Per the issue, removing the probe would kill zero-copy Arrow→R round-trips, and there's no Rust-safe way to manufacture provenance for a pointer synthesized from user data. So: make the whole function a no-op under `#[cfg(miri)]`. Callers fall through to the copy path, which is always valid. Production behavior unchanged; Miri now runs clean on this path.

Also expanded the doc comment explicitly calling out the conservative-GC analogy and the provenance gap, so future readers don't have to re-derive it from the issue thread.

Test plan

  • `cargo check -p miniextendr-api`
  • `cargo clippy --workspace --all-targets --locked -- -D warnings` (clippy_default)
  • `cargo clippy --workspace --all-targets --locked --features -- -D warnings` (clippy_all)
  • `just fmt`
  • `cargo +nightly miri test` — not run locally (miri not installed); the change is a `return None` short-circuit so the only way it can regress is if the `let _ = (args)` binding itself were rejected, which it isn't.

No R wrapper / vendor impact — change is entirely in `miniextendr-api/src/r_memory.rs`.

Generated with Claude Code

`try_recover_r_sexp` probes whether a data pointer came from an R SEXP
by subtracting the known SEXPREC header size and reading sxpinfo bits.
For pointers that came from a Rust-allocated Arrow buffer rather than
an R vector, the speculative pointer has no valid allocation
provenance, so the 4-byte load is undefined behavior under Miri's
Stacked / Tree Borrows model — even though it's defined at the
hardware level (heap is contiguous mapped memory). This is the
conservative-GC pattern: analogous to Boehm scanning the heap without
knowing allocation origins.

Per #63 the probe can't be removed without losing zero-copy Arrow→R
round-trips, and there is no Rust-safe way to re-establish provenance
for a pointer synthesized from user data. Instead, make the whole
function a no-op under `#[cfg(miri)]`: return `None` immediately so
callers fall through to the copy path, which is a valid alternative at
every call site. Production behavior is unchanged; Miri goes clean.

Expanded the doc comment to cover the provenance gap explicitly so
future readers don't have to re-discover why the bare pointer read
exists.

Closes #63.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CGMossa CGMossa force-pushed the fix/issue-63-miri-gate-try-recover branch from e95cbc9 to 501257f Compare April 17, 2026 10:58
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.

try_recover_r_sexp reads from pointer with no provenance (Miri UB)

1 participant