Skip to content

fix(fetch): ToString-coerce object Response bodies (boxed String) (#5453)#5457

Merged
proggeramlug merged 2 commits into
mainfrom
fix/response-boxed-string-body-5453
Jun 19, 2026
Merged

fix(fetch): ToString-coerce object Response bodies (boxed String) (#5453)#5457
proggeramlug merged 2 commits into
mainfrom
fix/response-boxed-string-body-5453

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5453js_response_new SIGSEGV'd in _platform_memmove when constructing the Response for a Hono c.html(...) render. Every server-rendered admin HTML page killed the process on first hit.

Root cause

Hono's raw() / hono/html / JSX c.html() return new String(value) — a boxed String object (with an isEscaped expando), not a primitive string. That body reaches js_response_body_init_ptr as a POINTER_TAG value.

The fall-through coerced it with js_get_string_pointer_unified, which for a POINTER_TAG value returns the object's raw address verbatim. js_response_new then read a bogus byte_len off the ObjectHeader (offset 4 → ~0xFFFF0011) and memmove'd ~4 GB of it → EXC_BAD_ACCESS.

This was a latent bug exposed by #5435: 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. 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_bytes correctly returns None for 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 boxed String yields its underlying primitive and always returns a real StringHeader.

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

  • New gap test test_gap_response_boxed_string_body_5453.ts — boxed String body (20 KB + short) matches node --experimental-strip-types byte-for-byte.
  • Direct repro of the issue's exact crash signature (js_response_new+116memmove, address …fffe4) now passes.
  • Array / object / number / string bodies all match Node.
  • #5435 binary-body gap test still passes (Uint8Array / ArrayBuffer / non-UTF-8 / string round-trip intact).
  • Existing fetch/response gap tests pass (the one pre-existing fetch_response_json_init diff is an unrelated Headers.get() null-vs-empty gap, untouched by this change).
  • cargo test -p perry-stdlib fetch green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Response objects to correctly handle and extract text content from boxed String bodies, including cases where frameworks wrap strings in custom objects.

)

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).
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 309a8bb7-226b-4491-95ba-7d2579a355fd

📥 Commits

Reviewing files that changed from the base of the PR and between 4a73b0e and ce9be88.

📒 Files selected for processing (2)
  • crates/perry-stdlib/src/fetch/dispatch.rs
  • test-files/test_gap_response_boxed_string_body_5453.ts

📝 Walkthrough

Walkthrough

js_response_body_init_ptr in dispatch.rs gains a pointer-tag detection branch that routes pointer-tagged heap objects (such as boxed String values) through js_jsvalue_to_string instead of the generic string-pointer path. A new TypeScript test validates this for Hono-style isEscaped boxed String bodies.

Changes

Boxed String body coercion fix

Layer / File(s) Summary
Pointer-tag detection and string coercion in dispatch
crates/perry-stdlib/src/fetch/dispatch.rs, test-files/test_gap_response_boxed_string_body_5453.ts
Imports js_jsvalue_to_string; adds a branch in js_response_body_init_ptr to detect pointer-tagged heap objects and coerce them via js_jsvalue_to_string rather than the unified path. New end-to-end test constructs boxed String bodies (with and without isEscaped) and asserts correct text output, length, and HTTP status from Response.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5446: Modifies the same js_response_body_init_ptr function in dispatch.rs to handle binary typed-array/Buffer/ArrayBuffer bodies, making it a direct predecessor to this PR's boxed-string coercion logic.

Poem

🐇 A boxed little string went astray,
Its pointer misread on that day.
Now a tag-check appears,
Banishing crashes and fears —
js_jsvalue_to_string saves the day! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: ToString-coercing object Response bodies (boxed Strings), with a reference to the issue number.
Description check ✅ Passed The description includes a summary, root cause analysis, detailed fix explanation, and comprehensive validation results. It addresses the template sections effectively despite some being checklist items.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/response-boxed-string-body-5453

Comment @coderabbitai help to get the list of available commands and usage tips.

@proggeramlug proggeramlug merged commit f30a2f5 into main Jun 19, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/response-boxed-string-body-5453 branch June 19, 2026 14:05
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.

Regression (0.5.1192): js_response_new SIGSEGVs in _platform_memmove on Hono c.html() render (worked on 0.5.1189)

1 participant