1#![deny(unsafe_op_in_unsafe_fn)]
11#![deny(clippy::undocumented_unsafe_blocks)]
12
13pub(crate) mod shared;
14pub(crate) mod utils;
15
16#[cfg(test)]
17mod tests;
18
19use std::ops::{Deref, DerefMut};
20
21use aligned_vec::{ABox, AVec, ConstAlign};
22pub use shared::SharedArrayBuffer;
23use std::sync::atomic::Ordering;
24
25use crate::{
26 Context, JsArgs, JsData, JsResult, JsString, JsValue,
27 builtins::BuiltInObject,
28 context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
29 error::JsNativeError,
30 js_string,
31 object::{JsObject, internal_methods::get_prototype_from_constructor},
32 property::Attribute,
33 realm::Realm,
34 string::StaticJsStrings,
35 symbol::JsSymbol,
36};
37use boa_gc::{Finalize, GcRef, GcRefMut, Trace};
38
39use self::utils::{SliceRef, SliceRefMut};
40
41use super::{
42 Array, BuiltInBuilder, BuiltInConstructor, DataView, IntrinsicObject, typed_array::TypedArray,
43};
44
45pub type AlignedVec<T> = AVec<T, ConstAlign<64>>;
47pub(crate) type AlignedBox<T> = ABox<T, ConstAlign<64>>;
48
49#[derive(Debug, Clone, Copy)]
50pub(crate) enum BufferRef<B, S> {
51 Buffer(B),
52 SharedBuffer(S),
53}
54
55impl<B, S> BufferRef<B, S>
56where
57 B: Deref<Target = ArrayBuffer>,
58 S: Deref<Target = SharedArrayBuffer>,
59{
60 pub(crate) fn bytes(&self, ordering: Ordering) -> Option<SliceRef<'_>> {
62 match self {
63 Self::Buffer(buf) => buf.deref().bytes().map(SliceRef::Slice),
64 Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes(ordering))),
65 }
66 }
67
68 #[track_caller]
73 pub(crate) fn bytes_with_len(&self, len: usize) -> Option<SliceRef<'_>> {
74 match self {
75 Self::Buffer(buf) => buf.deref().bytes_with_len(len).map(SliceRef::Slice),
76 Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes_with_len(len))),
77 }
78 }
79
80 pub(crate) fn is_fixed_len(&self) -> bool {
81 match self {
82 Self::Buffer(buf) => buf.is_fixed_len(),
83 Self::SharedBuffer(buf) => buf.is_fixed_len(),
84 }
85 }
86}
87
88#[derive(Debug)]
89pub(crate) enum BufferRefMut<B, S> {
90 Buffer(B),
91 SharedBuffer(S),
92}
93
94impl<B, S> BufferRefMut<B, S>
95where
96 B: DerefMut<Target = ArrayBuffer>,
97 S: DerefMut<Target = SharedArrayBuffer>,
98{
99 pub(crate) fn bytes(&mut self, ordering: Ordering) -> Option<SliceRefMut<'_>> {
100 match self {
101 Self::Buffer(buf) => buf.deref_mut().bytes_mut().map(SliceRefMut::Slice),
102 Self::SharedBuffer(buf) => {
103 Some(SliceRefMut::AtomicSlice(buf.deref_mut().bytes(ordering)))
104 }
105 }
106 }
107
108 pub(crate) fn bytes_with_len(&mut self, len: usize) -> Option<SliceRefMut<'_>> {
113 match self {
114 Self::Buffer(buf) => buf
115 .deref_mut()
116 .bytes_with_len_mut(len)
117 .map(SliceRefMut::Slice),
118 Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(
119 buf.deref_mut().bytes_with_len(len),
120 )),
121 }
122 }
123}
124
125#[derive(Debug, Clone, Trace, Finalize)]
127#[boa_gc(unsafe_no_drop)]
128pub(crate) enum BufferObject {
129 Buffer(JsObject<ArrayBuffer>),
130 SharedBuffer(JsObject<SharedArrayBuffer>),
131}
132
133impl From<BufferObject> for JsObject {
134 fn from(value: BufferObject) -> Self {
135 match value {
136 BufferObject::Buffer(buf) => buf.upcast(),
137 BufferObject::SharedBuffer(buf) => buf.upcast(),
138 }
139 }
140}
141
142impl From<BufferObject> for JsValue {
143 fn from(value: BufferObject) -> Self {
144 JsValue::from(JsObject::from(value))
145 }
146}
147
148impl BufferObject {
149 #[inline]
151 #[must_use]
152 pub(crate) fn as_buffer(
153 &self,
154 ) -> BufferRef<GcRef<'_, ArrayBuffer>, GcRef<'_, SharedArrayBuffer>> {
155 match self {
156 Self::Buffer(buf) => BufferRef::Buffer(GcRef::map(buf.borrow(), |o| o.data())),
157 Self::SharedBuffer(buf) => {
158 BufferRef::SharedBuffer(GcRef::map(buf.borrow(), |o| o.data()))
159 }
160 }
161 }
162
163 #[inline]
165 #[track_caller]
166 pub(crate) fn as_buffer_mut(
167 &self,
168 ) -> BufferRefMut<GcRefMut<'_, ArrayBuffer>, GcRefMut<'_, SharedArrayBuffer>> {
169 match self {
170 Self::Buffer(buf) => {
171 BufferRefMut::Buffer(GcRefMut::map(buf.borrow_mut(), |o| o.data_mut()))
172 }
173 Self::SharedBuffer(buf) => {
174 BufferRefMut::SharedBuffer(GcRefMut::map(buf.borrow_mut(), |o| o.data_mut()))
175 }
176 }
177 }
178
179 #[inline]
181 #[track_caller]
182 pub(crate) fn equals(lhs: &Self, rhs: &Self) -> bool {
183 match (lhs, rhs) {
184 (BufferObject::Buffer(lhs), BufferObject::Buffer(rhs)) => JsObject::equals(lhs, rhs),
185 (BufferObject::SharedBuffer(lhs), BufferObject::SharedBuffer(rhs)) => {
186 if JsObject::equals(lhs, rhs) {
187 return true;
188 }
189
190 let lhs = lhs.borrow();
191 let rhs = rhs.borrow();
192
193 std::ptr::eq(lhs.data().as_ptr(), rhs.data().as_ptr())
194 }
195 _ => false,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Trace, Finalize, JsData)]
202pub struct ArrayBuffer {
203 #[unsafe_ignore_trace]
205 data: Option<AlignedVec<u8>>,
206
207 max_byte_len: Option<u64>,
209
210 detach_key: JsValue,
212}
213
214impl ArrayBuffer {
215 pub(crate) fn from_data(data: AlignedVec<u8>, detach_key: JsValue) -> Self {
216 Self {
217 data: Some(data),
218 max_byte_len: None,
219 detach_key,
220 }
221 }
222
223 pub(crate) fn len(&self) -> usize {
224 self.data.as_ref().map_or(0, AlignedVec::len)
225 }
226
227 pub(crate) fn bytes(&self) -> Option<&[u8]> {
228 self.data.as_deref()
229 }
230
231 pub(crate) fn bytes_mut(&mut self) -> Option<&mut [u8]> {
232 self.data.as_deref_mut()
233 }
234
235 pub(crate) fn vec_mut(&mut self) -> Option<&mut AlignedVec<u8>> {
236 self.data.as_mut()
237 }
238
239 pub(crate) fn set_max_byte_length(&mut self, max_byte_len: u64) -> Option<u64> {
241 self.max_byte_len.replace(max_byte_len)
242 }
243
244 #[track_caller]
246 pub(crate) fn bytes_with_len(&self, len: usize) -> Option<&[u8]> {
247 if let Some(s) = self.data.as_deref() {
248 Some(&s[..len])
249 } else {
250 None
251 }
252 }
253
254 #[track_caller]
256 pub(crate) fn bytes_with_len_mut(&mut self, len: usize) -> Option<&mut [u8]> {
257 if let Some(s) = self.data.as_deref_mut() {
258 Some(&mut s[..len])
259 } else {
260 None
261 }
262 }
263
264 #[must_use]
266 pub fn data(&self) -> Option<&[u8]> {
267 self.data.as_deref()
268 }
269
270 pub fn resize(&mut self, new_byte_length: u64) -> JsResult<()> {
272 let Some(max_byte_len) = self.max_byte_len else {
273 return Err(JsNativeError::typ()
274 .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer")
275 .into());
276 };
277
278 let Some(buf) = self.vec_mut() else {
279 return Err(JsNativeError::typ()
280 .with_message("ArrayBuffer.resize: cannot resize a detached buffer")
281 .into());
282 };
283
284 if new_byte_length > max_byte_len {
285 return Err(JsNativeError::range()
286 .with_message(
287 "ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length",
288 )
289 .into());
290 }
291
292 buf.resize(new_byte_length as usize, 0);
293 Ok(())
294 }
295
296 pub fn detach(&mut self, key: &JsValue) -> JsResult<Option<AlignedVec<u8>>> {
303 if !JsValue::same_value(&self.detach_key, key) {
304 return Err(JsNativeError::typ()
305 .with_message("Cannot detach array buffer with different key")
306 .into());
307 }
308
309 Ok(self.data.take())
310 }
311
312 pub(crate) const fn is_detached(&self) -> bool {
319 self.data.is_none()
322 }
323
324 pub(crate) fn is_fixed_len(&self) -> bool {
325 self.max_byte_len.is_none()
326 }
327}
328
329impl IntrinsicObject for ArrayBuffer {
330 fn init(realm: &Realm) {
331 let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
332
333 let get_species = BuiltInBuilder::callable(realm, Self::get_species)
334 .name(js_string!("get [Symbol.species]"))
335 .build();
336
337 let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length)
338 .name(js_string!("get byteLength"))
339 .build();
340
341 let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable)
342 .name(js_string!("get resizable"))
343 .build();
344
345 let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length)
346 .name(js_string!("get maxByteLength"))
347 .build();
348
349 #[cfg(feature = "experimental")]
350 let get_detached = BuiltInBuilder::callable(realm, Self::get_detached)
351 .name(js_string!("get detached"))
352 .build();
353
354 let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
355 .static_accessor(
356 JsSymbol::species(),
357 Some(get_species),
358 None,
359 Attribute::CONFIGURABLE,
360 )
361 .static_method(Self::is_view, js_string!("isView"), 1)
362 .accessor(
363 js_string!("byteLength"),
364 Some(get_byte_length),
365 None,
366 flag_attributes,
367 )
368 .accessor(
369 js_string!("resizable"),
370 Some(get_resizable),
371 None,
372 flag_attributes,
373 )
374 .accessor(
375 js_string!("maxByteLength"),
376 Some(get_max_byte_length),
377 None,
378 flag_attributes,
379 )
380 .method(Self::js_resize, js_string!("resize"), 1)
381 .method(Self::slice, js_string!("slice"), 2)
382 .property(
383 JsSymbol::to_string_tag(),
384 Self::NAME,
385 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
386 );
387
388 #[cfg(feature = "experimental")]
389 let builder = builder
390 .accessor(
391 js_string!("detached"),
392 Some(get_detached),
393 None,
394 flag_attributes,
395 )
396 .method(Self::transfer::<false>, js_string!("transfer"), 0)
397 .method(
398 Self::transfer::<true>,
399 js_string!("transferToFixedLength"),
400 0,
401 );
402
403 builder.build();
404 }
405
406 fn get(intrinsics: &Intrinsics) -> JsObject {
407 Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
408 }
409}
410
411impl BuiltInObject for ArrayBuffer {
412 const NAME: JsString = StaticJsStrings::ARRAY_BUFFER;
413}
414
415impl BuiltInConstructor for ArrayBuffer {
416 const PROTOTYPE_STORAGE_SLOTS: usize = 13;
417 const CONSTRUCTOR_STORAGE_SLOTS: usize = 3;
418 const CONSTRUCTOR_ARGUMENTS: usize = 1;
419
420 const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
421 StandardConstructors::array_buffer;
422
423 fn constructor(
430 new_target: &JsValue,
431 args: &[JsValue],
432 context: &mut Context,
433 ) -> JsResult<JsValue> {
434 if new_target.is_undefined() {
436 return Err(JsNativeError::typ()
437 .with_message("ArrayBuffer.constructor called with undefined new target")
438 .into());
439 }
440
441 let byte_len = args.get_or_undefined(0).to_index(context)?;
443
444 let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?;
446
447 Ok(Self::allocate(new_target, byte_len, max_byte_len, context)?
449 .upcast()
450 .into())
451 }
452}
453
454impl ArrayBuffer {
455 #[allow(clippy::unnecessary_wraps)]
462 fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
463 Ok(args
467 .get_or_undefined(0)
468 .as_object()
469 .is_some_and(|obj| obj.is::<TypedArray>() || obj.is::<DataView>())
470 .into())
471 }
472
473 #[allow(clippy::unnecessary_wraps)]
480 fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
481 Ok(this.clone())
483 }
484
485 pub(crate) fn get_byte_length(
492 this: &JsValue,
493 _args: &[JsValue],
494 _: &mut Context,
495 ) -> JsResult<JsValue> {
496 let object = this.as_object();
500 let buf = object
501 .as_ref()
502 .and_then(JsObject::downcast_ref::<Self>)
503 .ok_or_else(|| {
504 JsNativeError::typ()
505 .with_message("get ArrayBuffer.prototype.byteLength called with invalid `this`")
506 })?;
507
508 Ok(buf.len().into())
512 }
513
514 pub(crate) fn get_max_byte_length(
518 this: &JsValue,
519 _args: &[JsValue],
520 _context: &mut Context,
521 ) -> JsResult<JsValue> {
522 let object = this.as_object();
526 let buf = object
527 .as_ref()
528 .and_then(JsObject::downcast_ref::<Self>)
529 .ok_or_else(|| {
530 JsNativeError::typ().with_message(
531 "get ArrayBuffer.prototype.maxByteLength called with invalid `this`",
532 )
533 })?;
534
535 let Some(data) = buf.bytes() else {
537 return Ok(JsValue::from(0));
538 };
539
540 Ok(buf.max_byte_len.unwrap_or(data.len() as u64).into())
546 }
547
548 pub(crate) fn get_resizable(
552 this: &JsValue,
553 _args: &[JsValue],
554 _context: &mut Context,
555 ) -> JsResult<JsValue> {
556 let object = this.as_object();
560 let buf = object
561 .as_ref()
562 .and_then(JsObject::downcast_ref::<Self>)
563 .ok_or_else(|| {
564 JsNativeError::typ()
565 .with_message("get ArrayBuffer.prototype.resizable called with invalid `this`")
566 })?;
567
568 Ok(JsValue::from(!buf.is_fixed_len()))
570 }
571
572 #[cfg(feature = "experimental")]
576 fn get_detached(
577 this: &JsValue,
578 _args: &[JsValue],
579 _context: &mut Context,
580 ) -> JsResult<JsValue> {
581 let object = this.as_object();
585 let buf = object
586 .as_ref()
587 .and_then(JsObject::downcast_ref::<Self>)
588 .ok_or_else(|| {
589 JsNativeError::typ()
590 .with_message("get ArrayBuffer.prototype.detached called with invalid `this`")
591 })?;
592
593 Ok(buf.is_detached().into())
595 }
596
597 pub(crate) fn js_resize(
601 this: &JsValue,
602 args: &[JsValue],
603 context: &mut Context,
604 ) -> JsResult<JsValue> {
605 let buf = this
609 .as_object()
610 .and_then(|o| o.clone().downcast::<Self>().ok())
611 .ok_or_else(|| {
612 JsNativeError::typ()
613 .with_message("ArrayBuffer.prototype.resize called with invalid `this`")
614 })?;
615
616 let new_byte_length = args.get_or_undefined(0).to_index(context)?;
618
619 buf.borrow_mut().data_mut().resize(new_byte_length)?;
637
638 Ok(JsValue::undefined())
640 }
641
642 fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
649 let buf = this
653 .as_object()
654 .and_then(|o| o.clone().downcast::<Self>().ok())
655 .ok_or_else(|| {
656 JsNativeError::typ()
657 .with_message("ArrayBuffer.slice called with invalid `this` value")
658 })?;
659
660 let len = {
661 let buf = buf.borrow();
662 if buf.data().is_detached() {
664 return Err(JsNativeError::typ()
665 .with_message("ArrayBuffer.slice called with detached buffer")
666 .into());
667 }
668 buf.data().len() as u64
670 };
671
672 let first = Array::get_relative_start(context, args.get_or_undefined(0), len)?;
677
678 let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len)?;
683
684 let new_len = final_.saturating_sub(first);
686
687 let ctor = buf
689 .clone()
690 .upcast()
691 .species_constructor(StandardConstructors::array_buffer, context)?;
692
693 let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?;
695
696 let Ok(new) = new.downcast::<Self>() else {
699 return Err(JsNativeError::typ()
700 .with_message("ArrayBuffer constructor returned invalid object")
701 .into());
702 };
703
704 if JsObject::equals(&buf, &new) {
706 return Err(JsNativeError::typ()
707 .with_message("new ArrayBuffer is the same as this ArrayBuffer")
708 .into());
709 }
710
711 {
712 let mut new = new.borrow_mut();
715 let Some(to_buf) = new.data_mut().bytes_mut() else {
716 return Err(JsNativeError::typ()
717 .with_message("ArrayBuffer constructor returned detached ArrayBuffer")
718 .into());
719 };
720
721 if (to_buf.len() as u64) < new_len {
723 return Err(JsNativeError::typ()
724 .with_message("new ArrayBuffer length too small")
725 .into());
726 }
727
728 let buf = buf.borrow();
732 let Some(from_buf) = buf.data().bytes() else {
733 return Err(JsNativeError::typ()
734 .with_message("ArrayBuffer detached while ArrayBuffer.slice was running")
735 .into());
736 };
737
738 let first = first as usize;
740 let new_len = new_len as usize;
741 to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]);
742 }
743
744 Ok(new.upcast().into())
746 }
747
748 #[cfg(feature = "experimental")]
754 fn transfer<const TO_FIXED_LENGTH: bool>(
755 this: &JsValue,
756 args: &[JsValue],
757 context: &mut Context,
758 ) -> JsResult<JsValue> {
759 let new_length = args.get_or_undefined(0);
766
767 let buf = this
770 .as_object()
771 .and_then(|o| o.clone().downcast::<Self>().ok())
772 .ok_or_else(|| {
773 JsNativeError::typ().with_message(if TO_FIXED_LENGTH {
774 "ArrayBuffer.prototype.transferToFixedLength called with invalid `this`"
775 } else {
776 "ArrayBuffer.prototype.transfer called with invalid `this`"
777 })
778 })?;
779
780 let new_len = if new_length.is_undefined() {
782 buf.borrow().data().len() as u64
784 } else {
785 new_length.to_index(context)?
788 };
789
790 let Some(mut bytes) = buf.borrow_mut().data_mut().data.take() else {
792 return Err(JsNativeError::typ()
793 .with_message("cannot transfer a detached buffer")
794 .into());
795 };
796
797 let new_max_len = buf
803 .borrow()
804 .data()
805 .max_byte_len
806 .filter(|_| !TO_FIXED_LENGTH);
807
808 if !buf.borrow().data().detach_key.is_undefined() {
810 buf.borrow_mut().data_mut().data = Some(bytes);
811 return Err(JsNativeError::typ()
812 .with_message("cannot transfer a buffer with a detach key")
813 .into());
814 }
815
816 if let Some(new_max_len) = new_max_len {
829 if new_len > new_max_len {
830 buf.borrow_mut().data_mut().data = Some(bytes);
831 return Err(JsNativeError::range()
832 .with_message("`length` cannot be bigger than `maxByteLength`")
833 .into());
834 }
835 bytes.resize(new_len as usize, 0);
837 } else {
838 bytes.resize(new_len as usize, 0);
839
840 bytes.shrink_to_fit();
842 }
843
844 let prototype = context
845 .intrinsics()
846 .constructors()
847 .array_buffer()
848 .prototype();
849
850 Ok(JsObject::from_proto_and_data_with_shared_shape(
851 context.root_shape(),
852 prototype,
853 ArrayBuffer {
854 data: Some(bytes),
855 max_byte_len: new_max_len,
856 detach_key: JsValue::undefined(),
857 },
858 )
859 .into())
860 }
861
862 pub(crate) fn allocate(
869 constructor: &JsValue,
870 byte_len: u64,
871 max_byte_len: Option<u64>,
872 context: &mut Context,
873 ) -> JsResult<JsObject<ArrayBuffer>> {
874 if let Some(max_byte_len) = max_byte_len
880 && byte_len > max_byte_len
881 {
882 return Err(JsNativeError::range()
883 .with_message("`length` cannot be bigger than `maxByteLength`")
884 .into());
885 }
886
887 let prototype = get_prototype_from_constructor(
889 constructor,
890 StandardConstructors::array_buffer,
891 context,
892 )?;
893
894 let block = create_byte_data_block(byte_len, max_byte_len, context)?;
900
901 let obj = JsObject::new(
902 context.root_shape(),
903 prototype,
904 Self {
905 data: Some(block),
908 max_byte_len,
911 detach_key: JsValue::undefined(),
912 },
913 );
914
915 Ok(obj)
917 }
918}
919
920fn get_max_byte_len(options: &JsValue, context: &mut Context) -> JsResult<Option<u64>> {
924 let Some(options) = options.as_object() else {
926 return Ok(None);
927 };
928
929 let max_byte_len = options.get(js_string!("maxByteLength"), context)?;
931
932 if max_byte_len.is_undefined() {
934 return Ok(None);
935 }
936
937 max_byte_len.to_index(context).map(Some)
939}
940
941pub(crate) fn create_byte_data_block(
948 size: u64,
949 max_buffer_size: Option<u64>,
950 context: &mut Context,
951) -> JsResult<AlignedVec<u8>> {
952 let alloc_size = max_buffer_size.unwrap_or(size);
953
954 assert!(size <= alloc_size);
955
956 if alloc_size > context.host_hooks().max_buffer_size(context) {
957 return Err(JsNativeError::range()
958 .with_message("cannot allocate a buffer that exceeds the maximum buffer size")
959 .into());
960 }
961
962 let alloc_size = alloc_size.try_into().map_err(|e| {
965 JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}"))
966 })?;
967
968 let mut data_block = AlignedVec::<u8>::new(64);
969 data_block.try_reserve_exact(alloc_size).map_err(|e| {
970 let message = match e {
971 aligned_vec::TryReserveError::CapacityOverflow => {
972 format!("capacity overflow for size {size} while allocating data block")
973 }
974 aligned_vec::TryReserveError::AllocError { layout } => {
975 format!("invalid layout {layout:?} while allocating data block")
976 }
977 };
978 JsNativeError::range().with_message(message)
979 })?;
980
981 let size = size as usize;
983
984 data_block.resize(size, 0);
986
987 Ok(data_block)
989}