diff --git a/crates/perry-stdlib/src/fetch/dispatch.rs b/crates/perry-stdlib/src/fetch/dispatch.rs index abab8b97f9..e77bd4d615 100644 --- a/crates/perry-stdlib/src/fetch/dispatch.rs +++ b/crates/perry-stdlib/src/fetch/dispatch.rs @@ -39,6 +39,23 @@ pub extern "C" fn js_response_body_init_ptr(value: f64) -> i64 { if let Some(bytes) = unsafe { body_value_buffer_bytes(value) } { return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) } as i64; } + // A Blob / File body contributes its raw bytes. Blob handles are handle-band + // ids (>= FETCH_HANDLE_BAND_START, 0x40000), NOT real pointers, so they must + // be recognized here: otherwise the object-body fallback below runs + // `js_jsvalue_to_string` on the id and dereferences it → SIGSEGV. This is the + // Response-constructor twin of the nested-Blob crash (#6231). + { + let jsval = JSValue::from_bits(value.to_bits()); + if jsval.is_pointer() { + let addr = jsval.as_pointer::() as usize; + if perry_runtime::value::addr_class::is_handle_band(addr) { + if let Some(bytes) = crate::fetch::blob_bytes_clone(addr) { + return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) } + as i64; + } + } + } + } // A non-integral or out-of-range value can't be a stream, so skip the // registry probe for the common cases. if value.is_finite() @@ -97,7 +114,13 @@ pub extern "C" fn js_response_body_init_ptr(value: f64) -> i64 { // `toString`/`[Symbol.toPrimitive]`) — for a boxed `String` that yields its // underlying primitive, the result is always a real `StringHeader`. if JSValue::from_bits(value.to_bits()).is_pointer() { - return js_jsvalue_to_string(value) as i64; + let addr = JSValue::from_bits(value.to_bits()).as_pointer::() as usize; + // A handle-band id that wasn't a Blob (a Headers/Request/… handle used + // as a body) has no byte payload and is not a real heap object — don't + // deref it via ToString (that was the crash path); fall through instead. + if !perry_runtime::value::addr_class::is_handle_band(addr) { + return js_jsvalue_to_string(value) as i64; + } } js_get_string_pointer_unified(value) } diff --git a/crates/perry-stdlib/src/fetch/request_ctor.rs b/crates/perry-stdlib/src/fetch/request_ctor.rs index 85af77d9a4..1bd526891d 100644 --- a/crates/perry-stdlib/src/fetch/request_ctor.rs +++ b/crates/perry-stdlib/src/fetch/request_ctor.rs @@ -47,8 +47,17 @@ pub unsafe extern "C" fn js_request_new( // typed-array/buffer registries first and copy the real bytes verbatim; a // genuine string body falls through to the lossless StringHeader read so its // UTF-8 bytes are preserved. - let body: Option> = dispatch::body_addr_buffer_bytes(body_ptr as usize) - .or_else(|| dispatch::body_bytes_from_header(body_ptr)); + // A Blob / File body is a handle-band id (>= 0x40000), NOT a real + // StringHeader/Buffer pointer, so it must be resolved via the blob registry + // first: reading it through `body_bytes_from_header` would dereference the + // synthetic id → SIGSEGV (#6231, the Request-constructor twin). + let body: Option> = + if perry_runtime::value::addr_class::is_handle_band(body_ptr as usize) { + crate::fetch::blob_bytes_clone(body_ptr as usize) + } else { + dispatch::body_addr_buffer_bytes(body_ptr as usize) + .or_else(|| dispatch::body_bytes_from_header(body_ptr)) + }; // GET/HEAD requests may not carry a body (WHATWG fetch). Refs #2643. if body.is_some() && (method == "GET" || method == "HEAD") { throw_fetch_type_error("Request with GET/HEAD method cannot have body."); diff --git a/crates/perry-stdlib/src/fetch_blob.rs b/crates/perry-stdlib/src/fetch_blob.rs index 78c2642ad1..e2f71d15b0 100644 --- a/crates/perry-stdlib/src/fetch_blob.rs +++ b/crates/perry-stdlib/src/fetch_blob.rs @@ -61,7 +61,12 @@ unsafe fn append_blob_parts(parts: f64, out: &mut Vec) { let top16 = bits >> 48; if top16 == 0x7FFD { let addr = (bits & 0x0000_FFFF_FFFF_FFFF) as usize; - if addr >= 0x10000 && !perry_runtime::buffer::is_registered_buffer(addr) { + // A real heap pointer (array) lives above the synthetic handle band; + // handle-band ids (blobs/headers/… start at 0x40000) must NOT be read + // as an ArrayHeader or we dereference a fake pointer. + if perry_runtime::value::addr_class::is_above_handle_band(addr) + && !perry_runtime::buffer::is_registered_buffer(addr) + { let arr_ptr = addr as *const perry_runtime::array::ArrayHeader; if !arr_ptr.is_null() { let gc_header = (arr_ptr as *const u8).sub(perry_runtime::gc::GC_HEADER_SIZE) @@ -96,15 +101,22 @@ unsafe fn append_one_blob_part(part: f64, out: &mut Vec) { // POINTER_TAG ─ either a Blob handle or a Buffer/Uint8Array. if top16 == 0x7FFD { let addr = (bits & 0x0000_FFFF_FFFF_FFFF) as usize; - // Small id → registered Blob handle. - if addr != 0 && addr < 0x10000 { + // Handle-band id (a Blob handle or another synthetic fetch handle) — + // NOT a real pointer. #6231: this previously tested `addr < 0x10000`, + // but blob handle ids start at 0x40000 (FETCH_HANDLE_BAND_START), so a + // `Blob` used as a BlobPart never matched here and fell through to + // `js_jsvalue_to_string`, which dereferenced the id as a pointer and + // SIGSEGV'd. Recognize the whole handle band and copy the blob's bytes. + if perry_runtime::value::addr_class::is_handle_band(addr) { if let Some(body) = blob_bytes_clone(addr) { out.extend_from_slice(&body); - return; } + // A non-blob handle as a BlobPart: skip it rather than deref a + // synthetic id. (Spec would ToString it; skipping is crash-safe.) + return; } // BufferHeader (Buffer / Uint8Array / ArrayBuffer)? - if addr >= 0x1000 && perry_runtime::buffer::is_registered_buffer(addr) { + if perry_runtime::buffer::is_registered_buffer(addr) { let buf = addr as *const perry_runtime::buffer::BufferHeader; let len = (*buf).length as usize; let data = perry_runtime::buffer::buffer_data(buf);