diff --git a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs index ce092d31..99bdfe54 100644 --- a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs @@ -180,7 +180,14 @@ macro_rules! impl_typed_array { unsafe { sys::napi_create_external_arraybuffer( env, - val.data as *mut c_void, + if length == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + val.data as *mut c_void + }, length, Some(finalizer::<$rust_type>), hint_ptr as *mut c_void, diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index 0565aaad..a3135890 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -176,7 +176,14 @@ impl ToNapiValue for Buffer { sys::napi_create_external_buffer( env, len, - val.inner.as_mut_ptr() as *mut _, + if len == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + val.inner.as_mut_ptr() as *mut _ + }, Some(drop_buffer), Box::into_raw(Box::new((len, val.capacity))) as *mut _, &mut ret, diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index b239d1e7..97c552e7 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -264,7 +264,14 @@ impl Env { sys::napi_create_external_buffer( self.0, length, - data_ptr as *mut c_void, + if length == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + data_ptr as *mut c_void + }, Some(drop_buffer), Box::into_raw(Box::new((length, data.capacity()))) as *mut c_void, &mut raw_value, @@ -301,7 +308,14 @@ impl Env { sys::napi_create_external_buffer( self.0, length, - data as *mut c_void, + if length == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + data as *mut c_void + }, Some( raw_finalize_with_custom_callback:: as unsafe extern "C" fn( @@ -386,7 +400,14 @@ impl Env { check_status!(unsafe { sys::napi_create_external_arraybuffer( self.0, - data_ptr as *mut c_void, + if length == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + data_ptr as *mut c_void + }, length, Some(drop_buffer), Box::into_raw(Box::new((length, data.capacity()))) as *mut c_void, @@ -426,7 +447,14 @@ impl Env { check_status!(unsafe { sys::napi_create_external_arraybuffer( self.0, - data as *mut c_void, + if length == 0 { + // Rust uses 0x1 as the data pointer for empty buffers, + // but NAPI/V8 only allows multiple buffers to have + // the same data pointer if it's 0x0. + ptr::null_mut() + } else { + data as *mut c_void + }, length, Some( raw_finalize_with_custom_callback:: diff --git a/examples/napi-compat-mode/__test__/buffer.spec.ts b/examples/napi-compat-mode/__test__/buffer.spec.ts index b41faee6..506ba4c8 100644 --- a/examples/napi-compat-mode/__test__/buffer.spec.ts +++ b/examples/napi-compat-mode/__test__/buffer.spec.ts @@ -33,6 +33,16 @@ test('should create borrowed buffer with finalize', (t) => { ) }) +test('should create empty borrowed buffer with finalize', (t) => { + t.is(bindings.createEmptyBorrowedBufferWithFinalize().toString(), '') + t.is(bindings.createEmptyBorrowedBufferWithFinalize().toString(), '') +}) + +test('should create empty buffer', (t) => { + t.is(bindings.createEmptyBuffer().toString(), '') + t.is(bindings.createEmptyBuffer().toString(), '') +}) + test('should be able to mutate buffer', (t) => { const fixture = Buffer.from([0, 1]) bindings.mutateBuffer(fixture) diff --git a/examples/napi-compat-mode/src/buffer.rs b/examples/napi-compat-mode/src/buffer.rs index 24be7ded..9c5fa9d5 100644 --- a/examples/napi-compat-mode/src/buffer.rs +++ b/examples/napi-compat-mode/src/buffer.rs @@ -57,6 +57,35 @@ pub fn create_borrowed_buffer_with_finalize(env: Env) -> ContextlessResult ContextlessResult { + let data = vec![]; + let data_ptr = data.as_ptr(); + let length = data.len(); + let manually_drop = ManuallyDrop::new(data); + + unsafe { + env.create_buffer_with_borrowed_data( + data_ptr, + length, + manually_drop, + |mut hint: ManuallyDrop>, _| { + ManuallyDrop::drop(&mut hint); + }, + ) + } + .map(|b| Some(b.into_raw())) +} + +#[contextless_function] +pub fn create_empty_buffer(env: Env) -> ContextlessResult { + let data = vec![]; + + env + .create_buffer_with_data(data) + .map(|b| Some(b.into_raw())) +} + #[js_function(1)] fn mutate_buffer(ctx: CallContext) -> Result { let buffer = &mut ctx.get::(0)?.into_value()?; @@ -76,6 +105,11 @@ pub fn register_js(exports: &mut JsObject) -> Result<()> { "createBorrowedBufferWithFinalize", create_borrowed_buffer_with_finalize, )?; + exports.create_named_method( + "createEmptyBorrowedBufferWithFinalize", + create_empty_borrowed_buffer_with_finalize, + )?; + exports.create_named_method("createEmptyBuffer", create_empty_buffer)?; exports.create_named_method("mutateBuffer", mutate_buffer)?; Ok(()) } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index beea5282..5f236e26 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -168,6 +168,7 @@ Generated by [AVA](https://avajs.dev). export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊ export function getBuffer(): Buffer␊ export function appendBuffer(buf: Buffer): Buffer␊ + export function getEmptyBuffer(): Buffer␊ export function convertU32Array(input: Uint32Array): Array␊ export function createExternalTypedArray(): Uint32Array␊ export function mutateTypedArray(input: Float32Array): void␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 0274c678..b96a5c92 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 3cda09af..98c5aa51 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -33,6 +33,7 @@ import { readPackageJson, getPackageJsonName, getBuffer, + getEmptyBuffer, readFileAsync, eitherStringOrNumber, returnEither, @@ -369,6 +370,11 @@ test('buffer', (t) => { t.is(buf.toString('utf-8'), 'Hello world') buf = appendBuffer(buf) t.is(buf.toString('utf-8'), 'Hello world!') + + const a = getEmptyBuffer() + const b = getEmptyBuffer() + t.is(a.toString(), '') + t.is(b.toString(), '') }) test('convert typedarray to vec', (t) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index ceab0ab7..50da5905 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -158,6 +158,7 @@ export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void export function getBuffer(): Buffer export function appendBuffer(buf: Buffer): Buffer +export function getEmptyBuffer(): Buffer export function convertU32Array(input: Uint32Array): Array export function createExternalTypedArray(): Uint32Array export function mutateTypedArray(input: Float32Array): void diff --git a/examples/napi/src/typed_array.rs b/examples/napi/src/typed_array.rs index e2180925..8f24e494 100644 --- a/examples/napi/src/typed_array.rs +++ b/examples/napi/src/typed_array.rs @@ -12,6 +12,11 @@ fn append_buffer(buf: Buffer) -> Buffer { buf.into() } +#[napi] +fn get_empty_buffer() -> Buffer { + vec![].into() +} + #[napi] fn convert_u32_array(input: Uint32Array) -> Vec { input.to_vec()