fix(fetch): ToString-coerce object Response bodies (boxed String) (#5453)#5457
Conversation
) new Response(<heap object>) — most notably a boxed String (new String(value) with an isEscaped expando, exactly what hono's raw() / hono/html / JSX c.html() return) — SIGSEGV'd in js_response_new -> _platform_memmove on the first server-rendered page. Root cause: js_response_body_init_ptr's fall-through coerced the body with js_get_string_pointer_unified, which for a POINTER_TAG value hands back the object's raw address verbatim. js_response_new then read a bogus byte_len off the ObjectHeader (offset 4) and memmove'd ~4 GB of it. The pre-#5435 string_from_header path survived only by accident: its str::from_utf8 bailed on the first non-UTF-8 byte and returned an empty body; #5435's lossless raw-byte read (body_bytes_from_header) removed that accidental guard, turning a silent wrong-but-survivable read into a crash. Fix: per the Fetch spec a non-stream/non-buffer body is coerced with ToString, so route POINTER_TAG (heap object) bodies through the runtime's ToString (valueOf/toString/[Symbol.toPrimitive]). For a boxed String that yields its underlying primitive; the result is always a real StringHeader. Plain string, SSO, number, raw-pointer, buffer/typed-array and stream bodies are unchanged. Also fixes array bodies ([1,2,3] -> "1,2,3") and plain-object bodies ({} -> "[object Object]") to match Node, which previously hit the same mis-read. Adds test_gap_response_boxed_string_body_5453.ts (matches Node byte-for-byte).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
ChangesBoxed String body coercion fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Summary
Fixes #5453 —
js_response_newSIGSEGV'd in_platform_memmovewhen constructing theResponsefor a Honoc.html(...)render. Every server-rendered admin HTML page killed the process on first hit.Root cause
Hono's
raw()/hono/html/ JSXc.html()returnnew String(value)— a boxedStringobject (with anisEscapedexpando), not a primitive string. That body reachesjs_response_body_init_ptras aPOINTER_TAGvalue.The fall-through coerced it with
js_get_string_pointer_unified, which for aPOINTER_TAGvalue returns the object's raw address verbatim.js_response_newthen read a bogusbyte_lenoff theObjectHeader(offset 4 →~0xFFFF0011) andmemmove'd ~4 GB of it →EXC_BAD_ACCESS.This was a latent bug exposed by #5435: the pre-#5435
string_from_headerpath survived only by accident — itsstr::from_utf8bailed on the first non-UTF-8 byte and returned an empty body. #5435's lossless raw-byte read (body_bytes_from_header) removed that accidental guard, turning a silent wrong-but-survivable read into a crash. That's why the regression appeared exactly at 0.5.1192. (The new buffer-detection branch added in #5435 is not involved — confirmed via instrumentation:body_value_buffer_bytescorrectly returnsNonefor the boxed String.)Fix
Per the Fetch spec, a non-stream/non-buffer body is coerced with ToString. Route
POINTER_TAG(heap object) bodies through the runtime's ToString (valueOf/toString/[Symbol.toPrimitive]), which for a boxedStringyields its underlying primitive and always returns a realStringHeader.Plain string, SSO, number, raw-pointer, buffer/typed-array and stream bodies are unchanged. As a bonus this also makes array bodies (
[1,2,3]→"1,2,3") and plain-object bodies ({}→"[object Object]") match Node, since they previously hit the same mis-read.Validation
test_gap_response_boxed_string_body_5453.ts— boxedStringbody (20 KB + short) matchesnode --experimental-strip-typesbyte-for-byte.js_response_new+116→memmove, address…fffe4) now passes.#5435binary-body gap test still passes (Uint8Array/ArrayBuffer/ non-UTF-8 / string round-trip intact).fetch_response_json_initdiff is an unrelatedHeaders.get()null-vs-empty gap, untouched by this change).cargo test -p perry-stdlib fetchgreen.🤖 Generated with Claude Code
Summary by CodeRabbit