oficina / docs Public

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
Paul Campbell pushed by paul@keymail.dev 6e8eefbe8edf83ca582665b460240ba6c88238c3 parent c52c139
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]