Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions crates/perry-stdlib/src/fetch/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ pub extern "C" fn js_response_body_init_ptr(value: f64) -> i64 {
return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) } as i64;
}
}
// #5437: a Node `IncomingMessage` body — the request-body bridge Next.js's
// `NextRequestAdapter.fromNodeNextRequest` relies on. It sets the web
// `Request` body to the `NodeNextRequest`'s `.body`, which is the underlying
// `IncomingMessage` (a native handle: `POINTER_TAG | small id`, not bytes).
// Stringifying it below yielded `"[object Object]"`, so `req.json()` /
// `req.text()` saw garbage and POST bodies were silently lost. Read the
// request's buffered bytes through the handle-property dispatch — the node
// http impl exposes them as a Buffer under `rawBody` (`js_node_http_im_raw_body`)
// — and materialize a lossless StringHeader from them. Only a small-handle
// POINTER value is probed, so string / heap-object / buffer bodies above are
// untouched. A handle without a buffered `rawBody` falls through to ToString.
{
let jsval = JSValue::from_bits(value.to_bits());
if jsval.is_pointer() {
let raw = jsval.as_pointer::<u8>() as usize;
if raw != 0 && raw < 0x10000 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Small-handle threshold deviates from the project convention (0x10000 vs 0x100000).

The guard uses raw < 0x10000 (65536), but the documented small-handle boundary is value < 0x100000 (1048576). Any IncomingMessage handle id allocated in the [0x10000, 0x100000) range is skipped here and falls through to ToString, reproducing the original "[object Object]" regression for those ids. Note this also straddles the bits >= 0x10000 heap-pointer boundary used in body_value_buffer_bytes (Line 141), so the two thresholds should be reconciled deliberately.

🔧 Align with the documented handle threshold
-            if raw != 0 && raw < 0x10000 {
+            if raw != 0 && raw < 0x100000 {

As per coding guidelines: "Detect small pointer handles using the comparison value < 0x100000 to distinguish between handles and other NaN-boxed values".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if raw != 0 && raw < 0x10000 {
if raw != 0 && raw < 0x100000 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/fetch/dispatch.rs` at line 72, The handle check in
dispatch logic uses the wrong small-handle cutoff, causing some IncomingMessage
ids to fall through to ToString instead of being treated as handles. Update the
guard in the dispatch path that compares raw values so it follows the project
convention of value < 0x100000, and make sure this threshold is consistent with
the related boundary used in body_value_buffer_bytes to avoid mismatched handle
classification.

Source: Coding guidelines

let key = unsafe { js_string_from_bytes(b"rawBody".as_ptr(), 7) };
let raw_body = perry_runtime::object::js_object_get_field_by_name_f64(
raw as *const perry_runtime::object::ObjectHeader,
key,
);
if let Some(bytes) = unsafe { body_value_buffer_bytes(raw_body) } {
return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) }
as i64;
}
}
}
}
// A heap-object body — a boxed `String` (hono's `raw()` / JSX `c.html()`
// returns `new String(value)` with an `isEscaped` expando), an array, or a
// plain object — is a `POINTER_TAG` value. `js_get_string_pointer_unified`
Expand Down
Loading