Skip to content

Commit 9e70be3

Browse files
committed
fix: handle empty/null Buffer in JsReturn::from_napi_value
napi_get_buffer_info returns data=null with len=0 for empty buffers. slice::from_raw_parts requires a non-null pointer even for zero-length slices, which caused a panic when returning empty buffers from host functions. - Handle len=0 case specially by returning Vec::new() directly - Add explicit null check with error for data=null with len > 0 - Add vitest for host returning Buffer.alloc(0) to guest
1 parent 827dde3 commit 9e70be3

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

src/js-host-api/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,20 @@ impl FromNapiValue for JsReturn {
613613
"Failed to get buffer info",
614614
));
615615
}
616+
// Handle empty buffers: napi_get_buffer_info returns data=null, len=0
617+
// for empty buffers. slice::from_raw_parts requires non-null pointer
618+
// even for zero-length slices, so we handle this case specially.
619+
if len == 0 {
620+
return Ok(JsReturn::Buffer(Vec::new()));
621+
}
622+
// Non-empty buffer: data must be valid and non-null.
623+
// If it's null with len > 0, the buffer's backing store was likely
624+
// garbage collected (e.g., a Buffer.subarray view whose parent died).
625+
if data.is_null() {
626+
return Err(napi::Error::from_reason(
627+
"Buffer has null data pointer with non-zero length - backing store may have been garbage collected"
628+
));
629+
}
616630
// SAFETY: data points to len bytes of valid buffer memory.
617631
let bytes = unsafe { std::slice::from_raw_parts(data as *const u8, len) }.to_vec();
618632
return Ok(JsReturn::Buffer(bytes));

src/js-host-api/tests/host-functions.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,28 @@ describe('Binary data support', () => {
627627
expect(result).toEqual({ len: 0 });
628628
});
629629

630+
it('should handle host returning empty Buffer', async () => {
631+
// Regression: napi_get_buffer_info returns data=null, len=0 for
632+
// empty buffers. JsReturn::from_napi_value must not panic on the
633+
// null pointer — it should return an empty Vec instead.
634+
const loaded = await buildLoadedSandbox(
635+
(proto) => {
636+
proto.hostModule('host').register('empty_response', () => {
637+
return Buffer.alloc(0);
638+
});
639+
},
640+
`
641+
import * as host from "host:host";
642+
function handler() {
643+
const data = host.empty_response();
644+
return { len: data.length, isUint8: data instanceof Uint8Array };
645+
}
646+
`
647+
);
648+
const result = await loaded.callHandler('handler', {});
649+
expect(result).toEqual({ len: 0, isUint8: true });
650+
});
651+
630652
it('should round-trip binary data (send and receive)', async () => {
631653
const loaded = await buildLoadedSandbox(
632654
(proto) => {

0 commit comments

Comments
 (0)