napi/bindgen_runtime/js_values/
buffer.rs

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