Plan: Task 5 alloc/free via std::alloc, not Vec::from_raw_parts
Carrying pre-flight ruling R2 into the plan text rather than only the dispatch. Vec::with_capacity(n) guarantees at least n, so reconstructing with Vec::from_raw_parts(ptr, 0, len) is undefined behaviour whenever the allocator rounded up. std::alloc with an identical Layout on both sides has no such gap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011F5CivVm4CmL9ByfSSAaXA
1 file changed,
+16
−5
docs/superpowers/plans/2026-08-30-rust-core-wire-compat.md+16 −5
diff --git a/docs/superpowers/plans/2026-08-30-rust-core-wire-compat.md b/docs/superpowers/plans/2026-08-30-rust-core-wire-compat.md| index 2989035..ae24070 100644 |
| --- a/docs/superpowers/plans/2026-08-30-rust-core-wire-compat.md |
| +++ b/docs/superpowers/plans/2026-08-30-rust-core-wire-compat.md |
| @@ -689,17 +689,28 @@ pub fn text_string(handle: u32) -> String { |
| // --- wasm exports --- |
| +// Amended by pre-flight ruling R2. The obvious version — Vec::with_capacity |
| +// then Vec::from_raw_parts(ptr, 0, len) — is undefined behaviour: with_capacity |
| +// guarantees AT LEAST len, so reconstructing with capacity `len` is wrong |
| +// whenever the allocator rounded up. std::alloc with an identical Layout on |
| +// both sides has no such gap. |
| + |
| +fn layout_for(len: u32) -> std::alloc::Layout { |
| + // align 1: these are byte buffers the host writes into. |
| + std::alloc::Layout::from_size_align(len.max(1) as usize, 1) |
| + .expect("byte layout is always valid") |
| +} |
| + |
| #[no_mangle] |
| pub extern "C" fn docs_core_alloc(len: u32) -> u32 { |
| - let mut buf = Vec::<u8>::with_capacity(len as usize); |
| - let ptr = buf.as_mut_ptr() as u32; |
| - std::mem::forget(buf); |
| - ptr |
| + unsafe { std::alloc::alloc(layout_for(len)) as u32 } |
| } |
| #[no_mangle] |
| pub unsafe extern "C" fn docs_core_free(ptr: u32, len: u32) { |
| - drop(Vec::from_raw_parts(ptr as *mut u8, 0, len as usize)); |
| + if ptr != 0 { |
| + std::alloc::dealloc(ptr as *mut u8, layout_for(len)); |
| + } |
| } |
| #[no_mangle] |