1use crate::{
2 markers::ParallelSend, qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Object, Result, Value,
3};
4use alloc::boxed::Box;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::{
8 ffi::c_void,
9 fmt,
10 mem::{self, size_of, ManuallyDrop, MaybeUninit},
11 ops::Deref,
12 ptr::NonNull,
13 result::Result as StdResult,
14 slice,
15};
16
17use super::typed_array::TypedArrayItem;
18
19pub unsafe trait ArrayBufferSource {
30 fn as_ptr(&self) -> *mut u8;
31 fn len(&self) -> usize;
32 fn is_empty(&self) -> bool {
33 self.len() == 0
34 }
35}
36
37macro_rules! impl_array_buffer_source {
38 ($($t:ty),* $(,)?) => {
39 $(
40 unsafe impl ArrayBufferSource for $t {
41 fn as_ptr(&self) -> *mut u8 {
42 <[u8]>::as_ptr(self) as *mut u8
43 }
44 fn len(&self) -> usize {
45 <[u8]>::len(self)
46 }
47 }
48 )*
49 };
50}
51
52impl_array_buffer_source!(Vec<u8>, alloc::boxed::Box<[u8]>, Arc<[u8]>, Arc<Vec<u8>>);
53
54#[cfg(feature = "bytes")]
55impl_array_buffer_source!(bytes::Bytes);
56
57pub struct RawArrayBuffer {
58 pub len: usize,
59 pub ptr: NonNull<u8>,
60}
61
62#[derive(Debug, Clone, Copy, Eq, PartialEq)]
63pub enum AsSliceError {
64 BufferUsed,
65 InvalidAlignment,
66}
67
68impl fmt::Display for AsSliceError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 AsSliceError::BufferUsed => write!(f, "Buffer was already used"),
72 AsSliceError::InvalidAlignment => {
73 write!(f, "Buffer had a different alignment than was requested")
74 }
75 }
76 }
77}
78
79#[derive(Debug, PartialEq, Clone, Eq, Hash)]
82#[repr(transparent)]
83pub struct ArrayBuffer<'js>(pub(crate) Object<'js>);
84
85unsafe impl<'js> JsLifetime<'js> for ArrayBuffer<'js> {
86 type Changed<'to> = ArrayBuffer<'to>;
87}
88
89impl<'js> ArrayBuffer<'js> {
90 pub fn new<T: Copy>(ctx: Ctx<'js>, src: impl Into<Vec<T>>) -> Result<Self> {
92 let mut src = ManuallyDrop::new(src.into());
93 let ptr = src.as_mut_ptr();
94 let capacity = src.capacity();
95 let size = src.len() * size_of::<T>();
96
97 extern "C" fn drop_raw<T>(_rt: *mut qjs::JSRuntime, opaque: *mut c_void, ptr: *mut c_void) {
98 let ptr = ptr as *mut T;
99 let capacity = opaque as usize;
100 unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
103 }
104
105 Ok(Self(Object(unsafe {
106 let val = qjs::JS_NewArrayBuffer(
107 ctx.as_ptr(),
108 ptr as _,
109 size as _,
110 Some(drop_raw::<T>),
111 capacity as _,
112 false,
113 );
114 ctx.handle_exception(val).inspect_err(|_| {
115 Vec::from_raw_parts(ptr, capacity, capacity);
117 })?;
118 Value::from_js_value(ctx, val)
119 })))
120 }
121
122 pub fn new_copy<T: Copy>(ctx: Ctx<'js>, src: impl AsRef<[T]>) -> Result<Self> {
124 let src = src.as_ref();
125 let ptr = src.as_ptr();
126 let size = core::mem::size_of_val(src);
127
128 Ok(Self(Object(unsafe {
129 let val = qjs::JS_NewArrayBufferCopy(ctx.as_ptr(), ptr as _, size as _);
130 ctx.handle_exception(val)?;
131 Value::from_js_value(ctx.clone(), val)
132 })))
133 }
134
135 pub fn from_source<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
142 where
143 S: ArrayBufferSource + ParallelSend + 'static,
144 {
145 let ptr = src.as_ptr();
146 let len = src.len();
147 unsafe { Self::from_external(ctx, ptr, len, false, false, move || drop(src)) }
148 }
149
150 pub fn from_source_shared<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
154 where
155 S: ArrayBufferSource + ParallelSend + 'static,
156 {
157 let ptr = src.as_ptr();
158 let len = src.len();
159 unsafe { Self::from_external(ctx, ptr, len, true, false, move || drop(src)) }
160 }
161
162 pub fn from_source_immutable<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
169 where
170 S: ArrayBufferSource + ParallelSend + 'static,
171 {
172 let ptr = src.as_ptr();
173 let len = src.len();
174 unsafe { Self::from_external(ctx, ptr, len, false, true, move || drop(src)) }
175 }
176
177 unsafe fn from_external<F>(
186 ctx: Ctx<'js>,
187 ptr: *mut u8,
188 len: usize,
189 is_shared: bool,
190 immutable: bool,
191 drop_fn: F,
192 ) -> Result<Self>
193 where
194 F: FnOnce() + ParallelSend + 'static,
195 {
196 extern "C" fn shim<F: FnOnce()>(
197 _rt: *mut qjs::JSRuntime,
198 opaque: *mut c_void,
199 _ptr: *mut c_void,
200 ) {
201 unsafe {
202 let boxed: Box<F> = Box::from_raw(opaque as *mut F);
203 (*boxed)();
204 }
205 }
206
207 let opaque = Box::into_raw(Box::new(drop_fn)) as *mut c_void;
208
209 Ok(Self(Object(unsafe {
210 let val = qjs::JS_NewArrayBuffer(
211 ctx.as_ptr(),
212 ptr,
213 len as _,
214 Some(shim::<F>),
215 opaque,
216 is_shared,
217 );
218 if let Err(e) = ctx.handle_exception(val) {
219 shim::<F>(qjs::JS_GetRuntime(ctx.as_ptr()), opaque, ptr as *mut c_void);
220 return Err(e);
221 }
222 if immutable {
223 qjs::JS_SetImmutableArrayBuffer(val, true);
224 }
225 Value::from_js_value(ctx, val)
226 })))
227 }
228
229 pub fn len(&self) -> usize {
231 Self::get_raw(&self.0).expect("Not an ArrayBuffer").len
232 }
233
234 pub fn is_empty(&self) -> bool {
236 self.len() == 0
237 }
238
239 pub fn as_bytes(&self) -> Option<&[u8]> {
243 let raw = Self::get_raw(self.as_value())?;
244 Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) })
245 }
246
247 pub fn as_slice<T: TypedArrayItem>(&self) -> StdResult<&[T], AsSliceError> {
250 let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?;
251 if raw.ptr.as_ptr().align_offset(mem::align_of::<T>()) != 0 {
252 return Err(AsSliceError::InvalidAlignment);
253 }
254 let len = raw.len / size_of::<T>();
255 Ok(unsafe { slice::from_raw_parts(raw.ptr.as_ptr().cast(), len) })
256 }
257
258 pub fn detach(&mut self) {
260 unsafe { qjs::JS_DetachArrayBuffer(self.0.ctx.as_ptr(), self.0.as_js_value()) }
261 }
262
263 #[inline]
265 pub fn as_value(&self) -> &Value<'js> {
266 self.0.as_value()
267 }
268
269 #[inline]
271 pub fn into_value(self) -> Value<'js> {
272 self.0.into_value()
273 }
274
275 pub fn from_value(value: Value<'js>) -> Option<Self> {
277 Self::from_object(Object::from_value(value).ok()?)
278 }
279
280 #[inline]
282 pub fn as_object(&self) -> &Object<'js> {
283 &self.0
284 }
285
286 #[inline]
288 pub fn into_object(self) -> Object<'js> {
289 self.0
290 }
291
292 pub fn from_object(object: Object<'js>) -> Option<Self> {
294 if Self::get_raw(&object.0).is_some() {
295 Some(Self(object))
296 } else {
297 None
298 }
299 }
300
301 pub fn as_raw(&self) -> Option<RawArrayBuffer> {
305 Self::get_raw(self.as_value())
306 }
307
308 pub(crate) fn get_raw(val: &Value<'js>) -> Option<RawArrayBuffer> {
309 let ctx = val.ctx();
310 let val = val.as_js_value();
311 let mut size = MaybeUninit::<qjs::size_t>::uninit();
312 let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) };
313
314 if let Some(ptr) = NonNull::new(ptr) {
315 let len = unsafe { size.assume_init() }
316 .try_into()
317 .expect(qjs::SIZE_T_ERROR);
318 Some(RawArrayBuffer { len, ptr })
319 } else {
320 None
321 }
322 }
323}
324
325impl<'js, T: TypedArrayItem> AsRef<[T]> for ArrayBuffer<'js> {
326 fn as_ref(&self) -> &[T] {
327 self.as_slice().expect("ArrayBuffer was detached")
328 }
329}
330
331impl<'js> Deref for ArrayBuffer<'js> {
332 type Target = Object<'js>;
333
334 fn deref(&self) -> &Self::Target {
335 self.as_object()
336 }
337}
338
339impl<'js> AsRef<Object<'js>> for ArrayBuffer<'js> {
340 fn as_ref(&self) -> &Object<'js> {
341 self.as_object()
342 }
343}
344
345impl<'js> AsRef<Value<'js>> for ArrayBuffer<'js> {
346 fn as_ref(&self) -> &Value<'js> {
347 self.as_value()
348 }
349}
350
351impl<'js> FromJs<'js> for ArrayBuffer<'js> {
352 fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
353 let ty_name = value.type_name();
354 if let Some(v) = Self::from_value(value) {
355 Ok(v)
356 } else {
357 Err(Error::new_from_js(ty_name, "ArrayBuffer"))
358 }
359 }
360}
361
362impl<'js> IntoJs<'js> for ArrayBuffer<'js> {
363 fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
364 Ok(self.into_value())
365 }
366}
367
368impl<'js> Object<'js> {
369 pub fn is_array_buffer(&self) -> bool {
371 ArrayBuffer::get_raw(&self.0).is_some()
372 }
373
374 pub unsafe fn ref_array_buffer(&self) -> &ArrayBuffer {
379 mem::transmute(self)
380 }
381
382 pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
384 self.is_array_buffer()
385 .then_some(unsafe { self.ref_array_buffer() })
386 }
387}
388
389#[cfg(test)]
390mod test {
391 use crate::*;
392 use alloc::sync::Arc;
393
394 #[test]
395 fn from_javascript_i8() {
396 test_with(|ctx| {
397 let val: ArrayBuffer = ctx
398 .eval(
399 r#"
400 new Int8Array([0, -5, 1, 11]).buffer
401 "#,
402 )
403 .unwrap();
404 assert_eq!(val.len(), 4);
405 assert_eq!(val.as_ref() as &[i8], &[0i8, -5, 1, 11]);
406 });
407 }
408
409 #[test]
410 fn into_javascript_i8() {
411 test_with(|ctx| {
412 let val = ArrayBuffer::new(ctx.clone(), [-1i8, 0, 22, 5]).unwrap();
413 ctx.globals().set("a", val).unwrap();
414 let res: i8 = ctx
415 .eval(
416 r#"
417 let v = new Int8Array(a);
418 v.length != 4 ? 1 :
419 v[0] != -1 ? 2 :
420 v[1] != 0 ? 3 :
421 v[2] != 22 ? 4 :
422 v[3] != 5 ? 5 :
423 0
424 "#,
425 )
426 .unwrap();
427 assert_eq!(res, 0);
428 })
429 }
430
431 #[test]
432 fn from_javascript_f32() {
433 test_with(|ctx| {
434 let val: ArrayBuffer = ctx
435 .eval(
436 r#"
437 new Float32Array([0.5, -5.25, 123.125]).buffer
438 "#,
439 )
440 .unwrap();
441 assert_eq!(val.len(), 12);
442 assert_eq!(val.as_ref() as &[f32], &[0.5f32, -5.25, 123.125]);
443 });
444 }
445
446 #[test]
447 fn into_javascript_f32() {
448 test_with(|ctx| {
449 let val = ArrayBuffer::new(ctx.clone(), [-1.5f32, 0.0, 2.25]).unwrap();
450 ctx.globals().set("a", val).unwrap();
451 let res: i8 = ctx
452 .eval(
453 r#"
454 let v = new Float32Array(a);
455 a.byteLength != 12 ? 1 :
456 v.length != 3 ? 2 :
457 v[0] != -1.5 ? 3 :
458 v[1] != 0 ? 4 :
459 v[2] != 2.25 ? 5 :
460 0
461 "#,
462 )
463 .unwrap();
464 assert_eq!(res, 0);
465 })
466 }
467
468 #[test]
469 fn as_bytes() {
470 test_with(|ctx| {
471 let val: ArrayBuffer = ctx
472 .eval(
473 r#"
474 new Uint32Array([0xCAFEDEAD,0xFEEDBEAD]).buffer
475 "#,
476 )
477 .unwrap();
478 let mut res = [0; 8];
479 let bytes_0 = 0xCAFEDEADu32.to_ne_bytes();
480 res[..4].copy_from_slice(&bytes_0);
481 let bytes_1 = 0xFEEDBEADu32.to_ne_bytes();
482 res[4..].copy_from_slice(&bytes_1);
483
484 assert_eq!(val.as_bytes().unwrap(), &res)
485 });
486 }
487
488 #[test]
489 fn from_source_external_buffer() {
490 use core::sync::atomic::{AtomicBool, Ordering};
491
492 static DROPPED: AtomicBool = AtomicBool::new(false);
493
494 struct Tracker(alloc::boxed::Box<[u8]>);
495 unsafe impl ArrayBufferSource for Tracker {
496 fn as_ptr(&self) -> *mut u8 {
497 self.0.as_ptr()
498 }
499 fn len(&self) -> usize {
500 self.0.len()
501 }
502 }
503 impl Drop for Tracker {
504 fn drop(&mut self) {
505 DROPPED.store(true, Ordering::SeqCst);
506 }
507 }
508
509 let rt = crate::Runtime::new().unwrap();
510 let c = crate::Context::full(&rt).unwrap();
511 c.with(|ctx| {
512 let src = Tracker(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
513 let ab = ArrayBuffer::from_source(ctx.clone(), src).unwrap();
514 assert_eq!(ab.len(), 4);
515 assert_eq!(ab.as_bytes().unwrap(), &[1, 2, 3, 4]);
516 });
517 rt.run_gc();
518 assert!(DROPPED.load(Ordering::SeqCst));
519 }
520
521 #[test]
522 fn from_source_error_invokes_drop_fn() {
523 use core::sync::atomic::{AtomicBool, Ordering};
524
525 static DROPPED: AtomicBool = AtomicBool::new(false);
526
527 struct BadSource(#[allow(dead_code)] alloc::boxed::Box<[u8]>);
530 unsafe impl ArrayBufferSource for BadSource {
531 fn as_ptr(&self) -> *mut u8 {
532 self.0.as_ptr()
533 }
534 fn len(&self) -> usize {
535 i64::MAX as usize
536 }
537 }
538 impl Drop for BadSource {
539 fn drop(&mut self) {
540 DROPPED.store(true, Ordering::SeqCst);
541 }
542 }
543
544 let rt = crate::Runtime::new().unwrap();
545 let c = crate::Context::full(&rt).unwrap();
546 c.with(|ctx| {
547 let src = BadSource(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
548 let err = ArrayBuffer::from_source(ctx.clone(), src);
549 assert!(err.is_err());
550 });
551 assert!(DROPPED.load(Ordering::SeqCst));
552 }
553
554 #[test]
555 fn from_source_immutable_arc_slices() {
556 struct ArcSlice {
557 arc: Arc<Vec<u8>>,
558 offset: usize,
559 len: usize,
560 }
561 unsafe impl ArrayBufferSource for ArcSlice {
562 fn as_ptr(&self) -> *mut u8 {
563 unsafe { self.arc.as_ptr().add(self.offset) }
564 }
565 fn len(&self) -> usize {
566 self.len
567 }
568 }
569
570 let buf: Arc<Vec<u8>> = Arc::new((0u8..16).collect());
571 let weak = Arc::downgrade(&buf);
572
573 let rt = crate::Runtime::new().unwrap();
574 let c = crate::Context::full(&rt).unwrap();
575 c.with(|ctx| {
576 let mk = |offset: usize, len: usize| -> ArrayBuffer<'_> {
577 ArrayBuffer::from_source_immutable(
578 ctx.clone(),
579 ArcSlice {
580 arc: buf.clone(),
581 offset,
582 len,
583 },
584 )
585 .unwrap()
586 };
587 let full = mk(0, 16);
588 let head = mk(0, 4);
589 let tail = mk(12, 4);
590 let middle = mk(4, 8);
591
592 assert_eq!(full.as_bytes().unwrap(), (0u8..16).collect::<Vec<_>>());
593 assert_eq!(head.as_bytes().unwrap(), &[0, 1, 2, 3]);
594 assert_eq!(tail.as_bytes().unwrap(), &[12, 13, 14, 15]);
595 assert_eq!(middle.as_bytes().unwrap(), (4u8..12).collect::<Vec<_>>());
596 assert_eq!(Arc::strong_count(&buf), 5);
597
598 ctx.globals().set("buf", full).unwrap();
599
600 let after: u8 = ctx
601 .eval::<u8, _>(
602 r#"
603 const arr = new Uint8Array(buf);
604 arr[0] = 99;
605 arr[0];
606 "#,
607 )
608 .unwrap();
609 assert_eq!(after, 0, "immutable ArrayBuffer must not accept writes");
610
611 let writer = ctx.eval::<(), _>(
612 r#"
613 "use strict";
614 new DataView(buf).setUint8(0, 99);
615 "#,
616 );
617 assert!(
618 writer.is_err(),
619 "DataView write on immutable buffer must throw"
620 );
621 });
622
623 drop(buf);
624 rt.run_gc();
625 drop(c);
626 drop(rt);
627 assert!(weak.upgrade().is_none());
628 }
629
630 #[test]
631 fn from_source_vec() {
632 let rt = crate::Runtime::new().unwrap();
633 let c = crate::Context::full(&rt).unwrap();
634 c.with(|ctx| {
635 let ab = ArrayBuffer::from_source(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
636 assert_eq!(ab.as_bytes().unwrap(), &[1, 2, 3, 4]);
637 });
638 }
639
640 #[test]
641 fn from_source_immutable_arc() {
642 let arc: Arc<[u8]> = Arc::from((0u8..8).collect::<Vec<_>>().into_boxed_slice());
643 let weak = Arc::downgrade(&arc);
644 assert_eq!(Arc::strong_count(&arc), 1);
645
646 let rt = crate::Runtime::new().unwrap();
647 let c = crate::Context::full(&rt).unwrap();
648
649 c.with(|ctx| {
651 let _ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
652 assert_eq!(Arc::strong_count(&arc), 2, "Arc cloned into drop closure");
653 });
655 assert_eq!(
656 Arc::strong_count(&arc),
657 1,
658 "drop closure must release its Arc clone when ArrayBuffer is freed"
659 );
660 assert!(
661 weak.upgrade().is_some(),
662 "allocation must stay alive while Rust still holds the Arc"
663 );
664
665 c.with(|ctx| {
667 let ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
668 ctx.globals().set("buf", ab).unwrap();
669 assert_eq!(Arc::strong_count(&arc), 2);
670 });
671 drop(arc);
672 assert!(
673 weak.upgrade().is_some(),
674 "allocation must stay alive: JS still holds the buffer via the Arc clone in the closure"
675 );
676 assert_eq!(
677 weak.strong_count(),
678 1,
679 "only the closure's Arc clone should remain"
680 );
681
682 drop(c);
684 drop(rt);
685 assert!(
686 weak.upgrade().is_none(),
687 "allocation must be freed after both Rust and JS release their handles"
688 );
689 }
690
691 #[cfg(feature = "bytes")]
692 #[test]
693 fn from_source_immutable_bytes() {
694 let data: bytes::Bytes = (0u8..8).collect::<Vec<_>>().into();
695 let rt = crate::Runtime::new().unwrap();
696 let c = crate::Context::full(&rt).unwrap();
697 c.with(|ctx| {
698 let ab = ArrayBuffer::from_source_immutable(ctx.clone(), data.clone()).unwrap();
699 assert_eq!(ab.as_bytes().unwrap(), (0u8..8).collect::<Vec<_>>());
700 });
701 }
702}