Skip to main content

napi/bindgen_runtime/js_values/
buffer.rs

1#[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
2use std::collections::HashSet;
3use std::ffi::c_void;
4use std::mem;
5use std::ops::{Deref, DerefMut};
6use std::ptr::{self, NonNull};
7use std::slice;
8#[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
9use std::sync::Mutex;
10
11#[cfg(all(feature = "napi4", not(feature = "noop")))]
12use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_DESTROYED, THREADS_CAN_ACCESS_ENV};
13use crate::{
14  bindgen_prelude::*, check_status, env::EMPTY_VEC, sys, JsValue, Result, Value, ValueType,
15};
16
17#[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
18thread_local! {
19  pub (crate) static BUFFER_DATA: Mutex<HashSet<*mut u8>> = Default::default();
20}
21
22/// Zero copy buffer slice shared between Rust and Node.js.
23///
24/// It can only be used in non-async context and the lifetime is bound to the fn closure.
25///
26/// If you want to use Node.js Buffer in async context or want to extend the lifetime, use `Buffer` instead.
27pub struct BufferSlice<'env> {
28  pub(crate) inner: &'env mut [u8],
29  pub(crate) raw_value: sys::napi_value,
30  #[allow(dead_code)]
31  pub(crate) env: sys::napi_env,
32}
33
34impl<'env> BufferSlice<'env> {
35  /// Create a new `BufferSlice` from a `Vec<u8>`.
36  ///
37  /// While this is still a fully-supported data structure, in most cases using a `Uint8Array` will suffice.
38  pub fn from_data<D: Into<Vec<u8>>>(env: &Env, data: D) -> Result<Self> {
39    let mut buf = ptr::null_mut();
40    let mut data = data.into();
41    let inner_ptr = data.as_mut_ptr();
42    #[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
43    {
44      let is_existed = BUFFER_DATA.with(|buffer_data| {
45        let buffer = buffer_data.lock().expect("Unlock buffer data failed");
46        buffer.contains(&inner_ptr)
47      });
48      if is_existed {
49        panic!("Share the same data between different buffers is not allowed, see: https://github.com/nodejs/node/issues/32463#issuecomment-631974747");
50      }
51    }
52    let len = data.len();
53    let cap = data.capacity();
54    let finalize_hint = Box::into_raw(Box::new((len, cap)));
55    let mut status = unsafe {
56      sys::napi_create_external_buffer(
57        env.0,
58        len,
59        inner_ptr.cast(),
60        Some(drop_buffer_slice),
61        finalize_hint.cast(),
62        &mut buf,
63      )
64    };
65    if status == sys::Status::napi_no_external_buffers_allowed {
66      unsafe {
67        let _ = Box::from_raw(finalize_hint);
68      }
69      status = unsafe {
70        sys::napi_create_buffer_copy(
71          env.0,
72          len,
73          data.as_mut_ptr().cast(),
74          ptr::null_mut(),
75          &mut buf,
76        )
77      };
78    } else {
79      mem::forget(data);
80    }
81    check_status!(status, "Failed to create buffer slice from data")?;
82
83    Ok(Self {
84      inner: if len == 0 {
85        &mut []
86      } else {
87        unsafe { slice::from_raw_parts_mut(buf.cast(), len) }
88      },
89      raw_value: buf,
90      env: env.0,
91    })
92  }
93
94  /// Mostly the same with `from_data`
95  ///
96  /// Provided `finalize_callback` will be called when `BufferSlice` got dropped.
97  /// You can pass in `noop_finalize` if you have nothing to do in finalize phase.
98  ///
99  /// ## Safety
100  ///
101  /// The caller must ensure that:
102  /// - The data pointer is valid for the lifetime of the buffer and points to a memory region of at least `len` bytes
103  /// - The finalize callback properly cleans up the data
104  ///
105  /// ### Notes
106  ///
107  /// JavaScript may mutate the data passed in to this buffer when writing the buffer.
108  /// However, some JavaScript runtimes do not support external buffers (notably electron!)
109  /// in which case modifications may be lost.
110  ///
111  /// If you need to support these runtimes, you should create a buffer by other means and then
112  /// later copy the data back out.
113  pub unsafe fn from_external<T: 'env, F: FnOnce(Env, T)>(
114    env: &Env,
115    data: *mut u8,
116    len: usize,
117    finalize_hint: T,
118    finalize_callback: F,
119  ) -> Result<Self> {
120    let mut buf = ptr::null_mut();
121    if data.is_null() || std::ptr::eq(data, EMPTY_VEC.as_ptr()) {
122      return Err(Error::new(
123        Status::InvalidArg,
124        "Borrowed data should not be null".to_owned(),
125      ));
126    }
127    #[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
128    {
129      let is_existed = BUFFER_DATA.with(|buffer_data| {
130        let buffer = buffer_data.lock().expect("Unlock buffer data failed");
131        buffer.contains(&data)
132      });
133      if is_existed {
134        panic!("Share the same data between different buffers is not allowed, see: https://github.com/nodejs/node/issues/32463#issuecomment-631974747");
135      }
136    }
137    let hint_ptr = Box::into_raw(Box::new((finalize_hint, finalize_callback)));
138    let mut status = unsafe {
139      sys::napi_create_external_buffer(
140        env.0,
141        len,
142        data.cast(),
143        Some(crate::env::raw_finalize_with_custom_callback::<T, F>),
144        hint_ptr.cast(),
145        &mut buf,
146      )
147    };
148    status = if status == sys::Status::napi_no_external_buffers_allowed {
149      let (hint, finalize) = *Box::from_raw(hint_ptr);
150      let status =
151        unsafe { sys::napi_create_buffer_copy(env.0, len, data.cast(), ptr::null_mut(), &mut buf) };
152      finalize(*env, hint);
153      status
154    } else {
155      status
156    };
157    check_status!(status, "Failed to create buffer slice from data")?;
158
159    Ok(Self {
160      inner: if len == 0 {
161        &mut []
162      } else {
163        unsafe { slice::from_raw_parts_mut(buf.cast(), len) }
164      },
165      raw_value: buf,
166      env: env.0,
167    })
168  }
169
170  /// Copy data from a `&[u8]` and create a `BufferSlice` from it.
171  pub fn copy_from<D: AsRef<[u8]>>(env: &Env, data: D) -> Result<Self> {
172    let data = data.as_ref();
173    let len = data.len();
174    let data_ptr = data.as_ptr();
175    let mut buf = ptr::null_mut();
176    let mut result_ptr = ptr::null_mut();
177    check_status!(
178      unsafe {
179        sys::napi_create_buffer_copy(env.0, len, data_ptr.cast(), &mut result_ptr, &mut buf)
180      },
181      "Faild to create a buffer from copied data"
182    )?;
183    Ok(Self {
184      inner: if len == 0 {
185        &mut []
186      } else {
187        unsafe { slice::from_raw_parts_mut(buf.cast(), len) }
188      },
189      raw_value: buf,
190      env: env.0,
191    })
192  }
193
194  /// Convert a `BufferSlice` to a `Buffer`
195  ///
196  /// This will perform a `napi_create_reference` internally.
197  pub fn into_buffer(self, env: &Env) -> Result<Buffer> {
198    unsafe { Buffer::from_napi_value(env.0, self.raw_value) }
199  }
200}
201
202impl<'env> JsValue<'env> for BufferSlice<'env> {
203  fn value(&self) -> Value {
204    Value {
205      env: self.env,
206      value: self.raw_value,
207      value_type: ValueType::Object,
208    }
209  }
210}
211
212impl<'env> JsObjectValue<'env> for BufferSlice<'env> {}
213
214impl FromNapiValue for BufferSlice<'_> {
215  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
216    let mut buf = ptr::null_mut();
217    let mut len = 0usize;
218    check_status!(
219      unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len) },
220      "Failed to get Buffer pointer and length"
221    )?;
222    // From the docs of `napi_get_buffer_info`:
223    // > [out] data: The underlying data buffer of the node::Buffer. If length is 0, this may be
224    // > NULL or any other pointer value.
225    //
226    // In order to guarantee that `slice::from_raw_parts` is sound, the pointer must be non-null, so
227    // let's make sure it always is, even in the case of `napi_get_buffer_info` returning a null
228    // ptr.
229    Ok(Self {
230      inner: if len == 0 {
231        &mut []
232      } else {
233        unsafe { slice::from_raw_parts_mut(buf.cast(), len) }
234      },
235      raw_value: napi_val,
236      env,
237    })
238  }
239}
240
241impl ToNapiValue for &BufferSlice<'_> {
242  #[allow(unused_variables)]
243  unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
244    Ok(val.raw_value)
245  }
246}
247
248impl TypeName for BufferSlice<'_> {
249  fn type_name() -> &'static str {
250    "Buffer"
251  }
252
253  fn value_type() -> ValueType {
254    ValueType::Object
255  }
256}
257
258impl ValidateNapiValue for BufferSlice<'_> {
259  unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
260    let mut is_buffer = false;
261    check_status!(
262      unsafe { sys::napi_is_buffer(env, napi_val, &mut is_buffer) },
263      "Failed to validate napi buffer"
264    )?;
265    if !is_buffer {
266      return Err(Error::new(
267        Status::InvalidArg,
268        "Expected a Buffer value".to_owned(),
269      ));
270    }
271    Ok(ptr::null_mut())
272  }
273}
274
275impl AsRef<[u8]> for BufferSlice<'_> {
276  fn as_ref(&self) -> &[u8] {
277    self.inner
278  }
279}
280
281impl Deref for BufferSlice<'_> {
282  type Target = [u8];
283
284  fn deref(&self) -> &Self::Target {
285    self.inner
286  }
287}
288
289impl DerefMut for BufferSlice<'_> {
290  fn deref_mut(&mut self) -> &mut Self::Target {
291    self.inner
292  }
293}
294
295/// Zero copy u8 vector shared between rust and napi.
296/// It's designed to be used in `async` context, so it contains overhead to ensure the underlying data is not dropped.
297/// For non-async context, use `BufferSlice` instead.
298///
299/// Auto reference the raw JavaScript value, and release it when dropped.
300/// So it is safe to use it in `async fn`, the `&[u8]` under the hood will not be dropped until the `drop` called.
301/// Clone will create a new `Reference` to the same underlying `JavaScript Buffer`.
302pub struct Buffer {
303  pub(crate) inner: NonNull<u8>,
304  pub(crate) len: usize,
305  pub(crate) capacity: usize,
306  raw: Option<(sys::napi_ref, sys::napi_env)>,
307}
308
309impl Drop for Buffer {
310  fn drop(&mut self) {
311    if let Some((ref_, env)) = self.raw {
312      if ref_.is_null() {
313        return;
314      }
315      // Buffer is sent to the other thread which is not the JavaScript thread
316      // This only happens with `napi4` feature enabled
317      // We send back the Buffer reference value into the `CustomGC` ThreadsafeFunction callback
318      // and destroy the reference in the thread where registered the `napi_register_module_v1`
319      #[cfg(all(feature = "napi4", not(feature = "noop")))]
320      {
321        if CUSTOM_GC_TSFN_DESTROYED.load(std::sync::atomic::Ordering::SeqCst) {
322          return;
323        }
324        // Check if the current thread is the JavaScript thread
325        if !THREADS_CAN_ACCESS_ENV.with(|cell| cell.get()) {
326          let status = unsafe {
327            sys::napi_call_threadsafe_function(
328              CUSTOM_GC_TSFN.load(std::sync::atomic::Ordering::SeqCst),
329              ref_.cast(),
330              1,
331            )
332          };
333          assert!(
334            status == sys::Status::napi_ok || status == sys::Status::napi_closing,
335            "Call custom GC in Buffer::drop failed {}",
336            Status::from(status)
337          );
338          return;
339        }
340      }
341      let mut ref_count = 0;
342      check_status_or_throw!(
343        env,
344        unsafe { sys::napi_reference_unref(env, ref_, &mut ref_count) },
345        "Failed to unref Buffer reference in drop"
346      );
347      debug_assert!(
348        ref_count == 0,
349        "Buffer reference count in Buffer::drop is not zero"
350      );
351      check_status_or_throw!(
352        env,
353        unsafe { sys::napi_delete_reference(env, ref_) },
354        "Failed to delete Buffer reference in drop"
355      );
356    } else {
357      unsafe { Vec::from_raw_parts(self.inner.as_ptr(), self.len, self.capacity) };
358    }
359  }
360}
361
362/// SAFETY: This is undefined behavior, as the JS side may always modify the underlying buffer,
363/// without synchronization. Also see the docs for the `AsMut` impl.
364unsafe impl Send for Buffer {}
365unsafe impl Sync for Buffer {}
366
367impl Default for Buffer {
368  fn default() -> Self {
369    Self::from(Vec::default())
370  }
371}
372
373impl From<Vec<u8>> for Buffer {
374  fn from(mut data: Vec<u8>) -> Self {
375    let inner_ptr = data.as_mut_ptr();
376    #[cfg(all(debug_assertions, not(windows), not(target_family = "wasm")))]
377    {
378      let is_existed = BUFFER_DATA.with(|buffer_data| {
379        let buffer = buffer_data.lock().expect("Unlock buffer data failed");
380        buffer.contains(&inner_ptr)
381      });
382      if is_existed {
383        panic!("Share the same data between different buffers is not allowed, see: https://github.com/nodejs/node/issues/32463#issuecomment-631974747");
384      }
385    }
386    let len = data.len();
387    let capacity = data.capacity();
388    mem::forget(data);
389    Buffer {
390      // SAFETY: `Vec`'s docs guarantee that its pointer is never null (it's a dangling ptr if not
391      // allocated):
392      // > The pointer will never be null, so this type is null-pointer-optimized.
393      inner: unsafe { NonNull::new_unchecked(inner_ptr) },
394      len,
395      capacity,
396      raw: None,
397    }
398  }
399}
400
401impl From<Buffer> for Vec<u8> {
402  fn from(buf: Buffer) -> Self {
403    buf.as_ref().to_vec()
404  }
405}
406
407impl From<&[u8]> for Buffer {
408  fn from(inner: &[u8]) -> Self {
409    Buffer::from(inner.to_owned())
410  }
411}
412
413impl From<String> for Buffer {
414  fn from(inner: String) -> Self {
415    Buffer::from(inner.into_bytes())
416  }
417}
418
419impl AsRef<[u8]> for Buffer {
420  fn as_ref(&self) -> &[u8] {
421    // SAFETY: the pointer is guaranteed to be non-null, and guaranteed to be valid if `len` is not 0.
422    unsafe { slice::from_raw_parts(self.inner.as_ptr(), self.len) }
423  }
424}
425
426impl AsMut<[u8]> for Buffer {
427  fn as_mut(&mut self) -> &mut [u8] {
428    // SAFETY: This is literally undefined behavior. `Buffer::clone` allows you to create shared
429    // access to the underlying data, but `as_mut` and `deref_mut` allow unsynchronized mutation of
430    // that data (not to speak of the JS side having write access as well, at the same time).
431    unsafe { slice::from_raw_parts_mut(self.inner.as_ptr(), self.len) }
432  }
433}
434
435impl Deref for Buffer {
436  type Target = [u8];
437
438  fn deref(&self) -> &Self::Target {
439    self.as_ref()
440  }
441}
442
443impl DerefMut for Buffer {
444  fn deref_mut(&mut self) -> &mut Self::Target {
445    self.as_mut()
446  }
447}
448
449impl TypeName for Buffer {
450  fn type_name() -> &'static str {
451    "Vec<u8>"
452  }
453
454  fn value_type() -> ValueType {
455    ValueType::Object
456  }
457}
458
459impl FromNapiValue for Buffer {
460  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
461    let mut buf = ptr::null_mut();
462    let mut len = 0;
463    let mut ref_ = ptr::null_mut();
464    check_status!(
465      unsafe { sys::napi_create_reference(env, napi_val, 1, &mut ref_) },
466      "Failed to create reference from Buffer"
467    )?;
468    check_status!(
469      unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize) },
470      "Failed to get Buffer pointer and length"
471    )?;
472
473    // From the docs of `napi_get_buffer_info`:
474    // > [out] data: The underlying data buffer of the node::Buffer. If length is 0, this may be
475    // > NULL or any other pointer value.
476    //
477    // In order to guarantee that `slice::from_raw_parts` is sound, the pointer must be non-null, so
478    // let's make sure it always is, even in the case of `napi_get_buffer_info` returning a null
479    // ptr.
480    let buf = NonNull::new(buf as *mut u8);
481    let inner = match buf {
482      Some(buf) if len != 0 => buf,
483      _ => NonNull::dangling(),
484    };
485
486    Ok(Self {
487      inner,
488      len,
489      capacity: len,
490      raw: Some((ref_, env)),
491    })
492  }
493}
494
495impl ToNapiValue for Buffer {
496  unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result<sys::napi_value> {
497    // From Node.js value, not from `Vec<u8>`
498    if let Some((ref_, _)) = val.raw {
499      let mut buf = ptr::null_mut();
500      check_status!(
501        unsafe { sys::napi_get_reference_value(env, ref_, &mut buf) },
502        "Failed to get Buffer value from reference"
503      )?;
504
505      check_status!(
506        unsafe { sys::napi_delete_reference(env, ref_) },
507        "Failed to delete Buffer reference in Buffer::to_napi_value"
508      )?;
509      val.raw = Some((ptr::null_mut(), ptr::null_mut()));
510      return Ok(buf);
511    }
512    let len = val.len;
513    let mut ret = ptr::null_mut();
514    check_status!(
515      if len == 0 {
516        // Rust uses 0x1 as the data pointer for empty buffers,
517        // but NAPI/V8 only allows multiple buffers to have
518        // the same data pointer if it's 0x0.
519        unsafe { sys::napi_create_buffer(env, len, ptr::null_mut(), &mut ret) }
520      } else {
521        let value_ptr = val.inner.as_ptr();
522        let val_box_ptr = Box::into_raw(Box::new(val));
523        let mut status = unsafe {
524          sys::napi_create_external_buffer(
525            env,
526            len,
527            value_ptr.cast(),
528            Some(drop_buffer),
529            val_box_ptr.cast(),
530            &mut ret,
531          )
532        };
533        if status == napi_sys::Status::napi_no_external_buffers_allowed {
534          let value = unsafe { Box::from_raw(val_box_ptr) };
535          status = unsafe {
536            sys::napi_create_buffer_copy(
537              env,
538              len,
539              value.inner.as_ptr() as *mut c_void,
540              ptr::null_mut(),
541              &mut ret,
542            )
543          };
544        }
545        status
546      },
547      "Failed to create napi buffer"
548    )?;
549
550    Ok(ret)
551  }
552}
553
554impl ValidateNapiValue for Buffer {
555  unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
556    let mut is_buffer = false;
557    check_status!(
558      unsafe { sys::napi_is_buffer(env, napi_val, &mut is_buffer) },
559      "Failed to validate napi buffer"
560    )?;
561    if !is_buffer {
562      return Err(Error::new(
563        Status::InvalidArg,
564        "Expected a Buffer value".to_owned(),
565      ));
566    }
567    Ok(ptr::null_mut())
568  }
569}