diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index 0b9a358271..a8834fc045 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -16,6 +16,8 @@ pub(crate) mod utils; #[cfg(test)] mod tests; +use std::ops::{Deref, DerefMut}; + pub use shared::SharedArrayBuffer; use crate::{ @@ -23,7 +25,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject}, + object::{internal_methods::get_prototype_from_constructor, JsObject, Object}, property::Attribute, realm::Realm, string::common::StaticJsStrings, @@ -31,7 +33,7 @@ use crate::{ value::IntegerOrInfinity, Context, JsArgs, JsData, JsResult, JsString, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, GcRef, GcRefMut, Trace}; use boa_profiler::Profiler; use self::utils::{SliceRef, SliceRefMut}; @@ -41,16 +43,20 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) enum BufferRef<'a> { - Buffer(&'a ArrayBuffer), - SharedBuffer(&'a SharedArrayBuffer), +pub(crate) enum BufferRef { + Buffer(B), + SharedBuffer(S), } -impl BufferRef<'_> { +impl BufferRef +where + B: Deref, + S: Deref, +{ pub(crate) fn data(&self) -> Option> { match self { - Self::Buffer(buf) => buf.data().map(SliceRef::Slice), - Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.data())), + Self::Buffer(buf) => buf.deref().data().map(SliceRef::Slice), + Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().data())), } } @@ -60,16 +66,96 @@ impl BufferRef<'_> { } #[derive(Debug)] -pub(crate) enum BufferRefMut<'a> { - Buffer(&'a mut ArrayBuffer), - SharedBuffer(&'a mut SharedArrayBuffer), +pub(crate) enum BufferRefMut { + Buffer(B), + SharedBuffer(S), } -impl BufferRefMut<'_> { +impl BufferRefMut +where + B: DerefMut, + S: DerefMut, +{ pub(crate) fn data_mut(&mut self) -> Option> { match self { - Self::Buffer(buf) => buf.data_mut().map(SliceRefMut::Slice), - Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.data())), + Self::Buffer(buf) => buf.deref_mut().data_mut().map(SliceRefMut::Slice), + Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.deref_mut().data())), + } + } +} + +/// A `JsObject` containing a bytes buffer as its inner data. +#[derive(Debug, Clone, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] +pub(crate) enum BufferObject { + Buffer(JsObject), + SharedBuffer(JsObject), +} + +impl From for JsObject { + fn from(value: BufferObject) -> Self { + match value { + BufferObject::Buffer(buf) => buf.upcast(), + BufferObject::SharedBuffer(buf) => buf.upcast(), + } + } +} + +impl From for JsValue { + fn from(value: BufferObject) -> Self { + JsValue::from(JsObject::from(value)) + } +} + +impl BufferObject { + /// Gets the buffer data of the object. + #[inline] + #[must_use] + pub(crate) fn as_buffer( + &self, + ) -> BufferRef, GcRef<'_, SharedArrayBuffer>> { + match self { + Self::Buffer(buf) => BufferRef::Buffer(GcRef::map(buf.borrow(), |o| &o.data)), + Self::SharedBuffer(buf) => { + BufferRef::SharedBuffer(GcRef::map(buf.borrow(), |o| &o.data)) + } + } + } + + /// Gets the mutable buffer data of the object + #[inline] + pub(crate) fn as_buffer_mut( + &self, + ) -> BufferRefMut< + GcRefMut<'_, Object, ArrayBuffer>, + GcRefMut<'_, Object, SharedArrayBuffer>, + > { + match self { + Self::Buffer(buf) => { + BufferRefMut::Buffer(GcRefMut::map(buf.borrow_mut(), |o| &mut o.data)) + } + Self::SharedBuffer(buf) => { + BufferRefMut::SharedBuffer(GcRefMut::map(buf.borrow_mut(), |o| &mut o.data)) + } + } + } + + /// Returns `true` if the buffer objects point to the same buffer. + #[inline] + pub(crate) fn equals(lhs: &Self, rhs: &Self) -> bool { + match (lhs, rhs) { + (BufferObject::Buffer(lhs), BufferObject::Buffer(rhs)) => JsObject::equals(lhs, rhs), + (BufferObject::SharedBuffer(lhs), BufferObject::SharedBuffer(rhs)) => { + if JsObject::equals(lhs, rhs) { + return true; + } + + let lhs = lhs.borrow(); + let rhs = rhs.borrow(); + + std::ptr::eq(lhs.data.data().as_ptr(), rhs.data.data().as_ptr()) + } + _ => false, } } } diff --git a/core/engine/src/builtins/array_buffer/shared.rs b/core/engine/src/builtins/array_buffer/shared.rs index 128bb4daa5..f4fcea1070 100644 --- a/core/engine/src/builtins/array_buffer/shared.rs +++ b/core/engine/src/builtins/array_buffer/shared.rs @@ -125,7 +125,9 @@ impl BuiltInConstructor for SharedArrayBuffer { let byte_length = args.get_or_undefined(0).to_index(context)?; // 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). - Ok(Self::allocate(new_target, byte_length, context)?.into()) + Ok(Self::allocate(new_target, byte_length, context)? + .upcast() + .into()) } } @@ -256,7 +258,7 @@ impl SharedArrayBuffer { constructor: &JsValue, byte_length: u64, context: &mut Context, - ) -> JsResult { + ) -> JsResult> { // TODO: // 1. Let slots be « [[ArrayBufferData]] ». // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer @@ -291,11 +293,7 @@ impl SharedArrayBuffer { // 10. Else, // a. Set obj.[[ArrayBufferByteLength]] to byteLength. - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - Self { data }, - ); + let obj = JsObject::new(context.root_shape(), prototype, Self { data }); // 11. Return obj. Ok(obj) diff --git a/core/engine/src/builtins/array_buffer/utils.rs b/core/engine/src/builtins/array_buffer/utils.rs index 85c3076e6c..31d1263152 100644 --- a/core/engine/src/builtins/array_buffer/utils.rs +++ b/core/engine/src/builtins/array_buffer/utils.rs @@ -3,7 +3,6 @@ use std::{ptr, slice::SliceIndex, sync::atomic}; use portable_atomic::AtomicU8; -use sptr::Strict; use crate::{ builtins::typed_array::{ClampedU8, Element, TypedArrayElement, TypedArrayKind}, @@ -41,7 +40,9 @@ impl SliceRef<'_> { } /// Gets the starting address of this `SliceRef`. + #[cfg(debug_assertions)] pub(crate) fn addr(&self) -> usize { + use sptr::Strict; match self { Self::Slice(buf) => buf.as_ptr().addr(), Self::AtomicSlice(buf) => buf.as_ptr().addr(), @@ -68,7 +69,8 @@ impl SliceRef<'_> { // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(buffer.len() >= std::mem::size_of::()); assert_eq!(buffer.addr() % std::mem::align_of::(), 0); } @@ -178,6 +180,7 @@ pub(crate) enum SliceRefMut<'a> { impl SliceRefMut<'_> { /// Gets the byte length of this `SliceRefMut`. + #[cfg(debug_assertions)] pub(crate) fn len(&self) -> usize { match self { Self::Slice(buf) => buf.len(), @@ -201,7 +204,9 @@ impl SliceRefMut<'_> { } /// Gets the starting address of this `SliceRefMut`. + #[cfg(debug_assertions)] pub(crate) fn addr(&self) -> usize { + use sptr::Strict; match self { Self::Slice(buf) => buf.as_ptr().addr(), Self::AtomicSlice(buf) => buf.as_ptr().addr(), @@ -236,7 +241,8 @@ impl SliceRefMut<'_> { // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number. - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(buffer.len() >= std::mem::size_of::()); assert_eq!(buffer.addr() % std::mem::align_of::(), 0); } @@ -345,7 +351,8 @@ unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], c /// (you cannot borrow and mutably borrow a slice at the same time), but cannot be guaranteed /// for atomic slices. pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usize) { - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(src.len() >= count); assert!(dest.len() >= count); let src_range = src.addr()..src.addr() + src.len(); @@ -387,7 +394,8 @@ pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usi /// - `buffer` must contain at least `from + count` bytes to be read. /// - `buffer` must contain at least `to + count` bytes to be written. pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, count: usize) { - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(from + count <= buffer.len()); assert!(to + count <= buffer.len()); } diff --git a/core/engine/src/builtins/atomics/mod.rs b/core/engine/src/builtins/atomics/mod.rs index 246ed7ad27..9556d67281 100644 --- a/core/engine/src/builtins/atomics/mod.rs +++ b/core/engine/src/builtins/atomics/mod.rs @@ -24,7 +24,7 @@ use boa_gc::GcRef; use boa_profiler::Profiler; use super::{ - array_buffer::{BufferRef, SharedArrayBuffer}, + array_buffer::BufferRef, typed_array::{Atomic, ContentType, Element, TypedArray, TypedArrayElement, TypedArrayKind}, BuiltInBuilder, IntrinsicObject, }; @@ -80,10 +80,7 @@ macro_rules! atomic_op { let value = ii.kind().get_element(value, context)?; // revalidate - let mut buffer = ii.viewed_array_buffer().borrow_mut(); - let mut buffer = buffer - .as_buffer_mut() - .expect("integer indexed object must contain a valid buffer"); + let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); let Some(mut data) = buffer.data_mut() else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") @@ -168,10 +165,7 @@ impl Atomics { let pos = validate_atomic_access(&ii, index, context)?; // 2. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let buffer = ii.viewed_array_buffer().borrow(); - let buffer = buffer - .as_buffer() - .expect("integer indexed object must contain a valid buffer"); + let buffer = ii.viewed_array_buffer().as_buffer(); let Some(data) = buffer.data() else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") @@ -217,10 +211,7 @@ impl Atomics { let value = ii.kind().get_element(&converted, context)?; // 4. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let mut buffer = ii.viewed_array_buffer().borrow_mut(); - let mut buffer = buffer - .as_buffer_mut() - .expect("integer indexed object must contain a valid buffer"); + let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); let Some(mut buffer) = buffer.data_mut() else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") @@ -269,10 +260,7 @@ impl Atomics { .to_bytes(); // 6. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let mut buffer = ii.viewed_array_buffer().borrow_mut(); - let mut buffer = buffer - .as_buffer_mut() - .expect("integer indexed object must contain a valid buffer"); + let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); let Some(mut data) = buffer.data_mut() else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") @@ -403,10 +391,7 @@ impl Atomics { // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true). let ii = validate_integer_typed_array(array, true)?; - let buffer = ii.viewed_array_buffer().borrow(); - let buffer = buffer - .as_buffer() - .expect("integer indexed object must contain a valid buffer"); + let buffer = ii.viewed_array_buffer().as_buffer(); // 2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception. let BufferRef::SharedBuffer(buffer) = buffer else { @@ -451,10 +436,10 @@ impl Atomics { // SAFETY: the validity of `addr` is verified by our call to `validate_atomic_access`. let result = unsafe { if ii.kind() == TypedArrayKind::BigInt64 { - futex::wait(buffer, offset, value, timeout)? + futex::wait(&buffer, offset, value, timeout)? } else { // value must fit into `i32` since it came from an `i32` above. - futex::wait(buffer, offset, value as i32, timeout)? + futex::wait(&buffer, offset, value as i32, timeout)? } }; @@ -496,13 +481,11 @@ impl Atomics { // 4. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 5. Let block be buffer.[[ArrayBufferData]]. // 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽. - let buffer = ii.viewed_array_buffer(); - let buffer = buffer.borrow(); - let Some(shared) = buffer.downcast_ref::() else { + let BufferRef::SharedBuffer(shared) = ii.viewed_array_buffer().as_buffer() else { return Ok(0.into()); }; - let count = futex::notify(shared, offset, count)?; + let count = futex::notify(&shared, offset, count)?; // 12. Let n be the number of elements in S. // 13. Return 𝔽(n). diff --git a/core/engine/src/builtins/dataview/mod.rs b/core/engine/src/builtins/dataview/mod.rs index 2b7c6d3125..84a9861134 100644 --- a/core/engine/src/builtins/dataview/mod.rs +++ b/core/engine/src/builtins/dataview/mod.rs @@ -26,7 +26,10 @@ use boa_gc::{Finalize, Trace}; use bytemuck::{bytes_of, bytes_of_mut}; use super::{ - array_buffer::utils::{memcpy, SliceRef, SliceRefMut}, + array_buffer::{ + utils::{memcpy, SliceRef, SliceRefMut}, + BufferObject, + }, typed_array::{self, TypedArrayElement}, BuiltInBuilder, BuiltInConstructor, IntrinsicObject, }; @@ -34,7 +37,7 @@ use super::{ /// The internal representation of a `DataView` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct DataView { - pub(crate) viewed_array_buffer: JsObject, + pub(crate) viewed_array_buffer: BufferObject, pub(crate) byte_length: u64, pub(crate) byte_offset: u64, } @@ -135,11 +138,6 @@ impl BuiltInConstructor for DataView { ) -> JsResult { let byte_length = args.get_or_undefined(2); - let buffer_obj = args - .get_or_undefined(0) - .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; - // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() @@ -147,15 +145,19 @@ impl BuiltInConstructor for DataView { .into()); } + // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). + let buffer_obj = args + .get_or_undefined(0) + .as_object() + .and_then(|o| o.clone().into_buffer_object().ok()) + .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; + let (offset, view_byte_length) = { - // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). - let buffer_borrow = buffer_obj.borrow(); - let buffer = buffer_borrow.as_buffer().ok_or_else(|| { - JsNativeError::typ().with_message("buffer must be an ArrayBuffer") - })?; + let buffer = buffer_obj.as_buffer(); // 3. Let offset be ? ToIndex(byteOffset). let offset = args.get_or_undefined(1).to_index(context)?; + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. let Some(buffer) = buffer.data() else { return Err(JsNativeError::typ() @@ -195,12 +197,7 @@ impl BuiltInConstructor for DataView { get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?; // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer_obj - .borrow() - .as_buffer() - .expect("already checked that `buffer_obj` was a buffer") - .is_detached() - { + if buffer_obj.as_buffer().is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer can't be detached") .into()); @@ -211,7 +208,7 @@ impl BuiltInConstructor for DataView { prototype, Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. - viewed_array_buffer: buffer_obj.clone(), + viewed_array_buffer: buffer_obj, // 12. Set O.[[ByteLength]] to viewByteLength. byte_length: view_byte_length, // 13. Set O.[[ByteOffset]] to offset. @@ -277,12 +274,9 @@ impl DataView { .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer_borrow = view.viewed_array_buffer.borrow(); - let borrow = buffer_borrow - .as_buffer() - .expect("DataView must be constructed with a Buffer"); + let buffer = view.viewed_array_buffer.as_buffer(); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if borrow.is_detached() { + if buffer.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); @@ -317,12 +311,9 @@ impl DataView { .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer_borrow = view.viewed_array_buffer.borrow(); - let borrow = buffer_borrow - .as_buffer() - .expect("DataView must be constructed with a Buffer"); + let buffer = view.viewed_array_buffer.as_buffer(); // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if borrow.is_detached() { + if buffer.is_detached() { return Err(JsNativeError::typ() .with_message("Buffer is detached") .into()); @@ -362,9 +353,7 @@ impl DataView { let is_little_endian = is_little_endian.to_boolean(); // 5. Let buffer be view.[[ViewedArrayBuffer]]. - let buffer = &view.viewed_array_buffer; - let buffer_borrow = buffer.borrow(); - let buffer = buffer_borrow.as_buffer().expect("Should be unreachable"); + let buffer = view.viewed_array_buffer.as_buffer(); // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. let Some(data) = buffer.data() else { @@ -676,11 +665,7 @@ impl DataView { // 6. Set isLittleEndian to ! ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); // 7. Let buffer be view.[[ViewedArrayBuffer]]. - let buffer = &view.viewed_array_buffer; - let mut buffer_borrow = buffer.borrow_mut(); - let mut buffer = buffer_borrow - .as_buffer_mut() - .expect("Should be unreachable"); + let mut buffer = view.viewed_array_buffer.as_buffer_mut(); // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. let Some(mut data) = buffer.data_mut() else { diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index decef0dd02..9679b92b9d 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -1,14 +1,17 @@ -use std::{cmp, ptr, sync::atomic}; +use std::{cmp, sync::atomic}; use boa_macros::utf16; use num_traits::Zero; +use super::{ + object::typed_array_set_element, ContentType, TypedArray, TypedArrayKind, TypedArrayMarker, +}; use crate::{ builtins::{ array::{find_via_predicate, ArrayIterator, Direction}, array_buffer::{ utils::{memcpy, memmove, SliceRefMut}, - ArrayBuffer, BufferRef, + ArrayBuffer, BufferObject, }, iterable::iterable_to_list, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, @@ -23,11 +26,6 @@ use crate::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use super::{ - integer_indexed_object::integer_indexed_element_set, ContentType, TypedArray, TypedArrayKind, - TypedArrayMarker, -}; - /// The JavaScript `%TypedArray%` object. /// /// @@ -427,7 +425,7 @@ impl BuiltinTypedArray { // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. Return buffer. - Ok(typed_array.viewed_array_buffer().clone().into()) + Ok(JsObject::from(typed_array.viewed_array_buffer().clone()).into()) } /// `23.2.3.3 get %TypedArray%.prototype.byteLength` @@ -574,10 +572,7 @@ impl BuiltinTypedArray { // b. Let buffer be O.[[ViewedArrayBuffer]]. // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. let buffer_obj = o.viewed_array_buffer(); - let mut buffer_obj_borrow = buffer_obj.borrow_mut(); - let mut buffer = buffer_obj_borrow - .as_buffer_mut() - .expect("Already checked for detached buffer"); + let mut buffer = buffer_obj.as_buffer_mut(); let Some(buffer) = buffer.data_mut() else { return Err(JsNativeError::typ() .with_message("Buffer of the typed array is detached") @@ -2060,34 +2055,14 @@ impl BuiltinTypedArray { // 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true, // and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let // sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false. - let same = if JsObject::equals(&src_buffer_obj, &target_buffer_obj) { - true - } else { - let src_buffer_obj = src_buffer_obj.borrow(); - let src_buffer = src_buffer_obj.as_buffer().expect("Must be an array buffer"); - - let target_buffer_obj = target_buffer_obj.borrow(); - let target_buffer = target_buffer_obj - .as_buffer() - .expect("Must be an array buffer"); - - match (src_buffer, target_buffer) { - (BufferRef::SharedBuffer(src), BufferRef::SharedBuffer(dest)) => { - ptr::eq(src.data(), dest.data()) - } - (_, _) => false, - } - }; - // 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then - let src_byte_index = if same { + let src_byte_index = if BufferObject::equals(&src_buffer_obj, &target_buffer_obj) { // a. Let srcByteLength be source.[[ByteLength]]. let src_byte_offset = src_byte_offset as usize; let src_byte_length = source_array.byte_length() as usize; let s = { - let slice = src_buffer_obj.borrow(); - let slice = slice.as_buffer().expect("Must be an array buffer"); + let slice = src_buffer_obj.as_buffer(); let slice = slice.data().expect("Already checked for detached buffer"); // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). @@ -2096,8 +2071,7 @@ impl BuiltinTypedArray { .subslice(src_byte_offset..src_byte_offset + src_byte_length) .clone(context)? }; - // TODO: skip this upcast - src_buffer_obj = s.upcast(); + src_buffer_obj = BufferObject::Buffer(s); // d. Let srcByteIndex be 0. 0 @@ -2112,16 +2086,12 @@ impl BuiltinTypedArray { // 22. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. let target_byte_index = target_offset * target_element_size + target_byte_offset; - let src_buffer = src_buffer_obj.borrow(); - let src_buffer = src_buffer.as_buffer().expect("Must be an array buffer"); + let src_buffer = src_buffer_obj.as_buffer(); let src_buffer = src_buffer .data() .expect("Already checked for detached buffer"); - let mut target_buffer = target_buffer_obj.borrow_mut(); - let mut target_buffer = target_buffer - .as_buffer_mut() - .expect("Must be an array buffer"); + let mut target_buffer = target_buffer_obj.as_buffer_mut(); let mut target_buffer = target_buffer .data_mut() .expect("Already checked for detached buffer"); @@ -2255,7 +2225,7 @@ impl BuiltinTypedArray { let target_index = target_offset + k; // d. Perform ? IntegerIndexedElementSet(target, targetIndex, value). - integer_indexed_element_set(target, target_index as f64, &value, &mut context.into())?; + typed_array_set_element(target, target_index as f64, &value, &mut context.into())?; // e. Set k to k + 1. } @@ -2377,18 +2347,12 @@ impl BuiltinTypedArray { } else { // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. let src_buffer_obj = o.viewed_array_buffer(); - let src_buffer_obj_borrow = src_buffer_obj.borrow(); - let src_buffer = src_buffer_obj_borrow - .as_buffer() - .expect("view must be a buffer"); + let src_buffer = src_buffer_obj.as_buffer(); let src_buffer = src_buffer.data().expect("cannot be detached here"); // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. let target_buffer_obj = a_array.viewed_array_buffer(); - let mut target_buffer_obj_borrow = target_buffer_obj.borrow_mut(); - let mut target_buffer = target_buffer_obj_borrow - .as_buffer_mut() - .expect("view must be a buffer"); + let mut target_buffer = target_buffer_obj.as_buffer_mut(); let mut target_buffer = target_buffer.data_mut().expect("cannot be detached here"); // iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType. @@ -2713,7 +2677,7 @@ impl BuiltinTypedArray { obj, o.kind(), &[ - buffer.clone().into(), + JsObject::from(buffer.clone()).into(), begin_byte_offset.into(), new_length.into(), ], @@ -3069,9 +3033,8 @@ impl BuiltinTypedArray { // 9. Set O.[[ArrayLength]] to length. // 10. Return O. - // TODO: skip this upcast. Ok(TypedArray::new( - data.upcast(), + BufferObject::Buffer(data), T::ERASED, 0, byte_length, @@ -3153,18 +3116,14 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray pub(super) fn initialize_from_typed_array( proto: JsObject, - src_array: &JsObject, + src_array: &JsObject, context: &mut Context, ) -> JsResult { let src_array = src_array.borrow(); - let src_array = src_array - .downcast_ref::() - .expect("this must be a typed array"); + let src_array = &src_array.data; + let src_data = src_array.viewed_array_buffer(); - let src_data = src_data.borrow(); - let src_data = src_data - .as_buffer() - .expect("integer indexed must have a buffer"); + let src_data = src_data.as_buffer(); // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. @@ -3288,12 +3247,11 @@ impl BuiltinTypedArray { // 13. Set O.[[ByteLength]] to byteLength. // 14. Set O.[[ByteOffset]] to 0. // 15. Set O.[[ArrayLength]] to elementLength. - // TODO: Skip this upcast. let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, TypedArray::new( - new_buffer.upcast(), + BufferObject::Buffer(new_buffer), element_type, 0, byte_length, @@ -3313,7 +3271,7 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer pub(super) fn initialize_from_array_buffer( proto: JsObject, - buffer: JsObject, + buffer: BufferObject, byte_offset: &JsValue, length: &JsValue, context: &mut Context, @@ -3342,10 +3300,9 @@ impl BuiltinTypedArray { // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. let buffer_byte_length = { - let buffer_borrow = buffer.borrow(); - let buffer_array = buffer_borrow.as_buffer().expect("Must be a buffer"); + let buffer = buffer.as_buffer(); - let Some(data) = buffer_array.data() else { + let Some(data) = buffer.data() else { return Err(JsNativeError::typ() .with_message("Cannot construct typed array from detached buffer") .into()); diff --git a/core/engine/src/builtins/typed_array/element/mod.rs b/core/engine/src/builtins/typed_array/element/mod.rs index 1d0383766a..51e570ea73 100644 --- a/core/engine/src/builtins/typed_array/element/mod.rs +++ b/core/engine/src/builtins/typed_array/element/mod.rs @@ -277,7 +277,8 @@ macro_rules! element { } unsafe fn read(buffer: SliceRef<'_>) -> ElementRef<'_, Self> { - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(buffer.len() >= std::mem::size_of::()); assert!(buffer.addr() % std::mem::align_of::() == 0); } @@ -293,7 +294,8 @@ macro_rules! element { } unsafe fn read_mut(buffer: SliceRefMut<'_>) -> ElementRefMut<'_, Self> { - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { assert!(buffer.len() >= std::mem::size_of::()); assert!(buffer.addr() % std::mem::align_of::() == 0); } diff --git a/core/engine/src/builtins/typed_array/mod.rs b/core/engine/src/builtins/typed_array/mod.rs index 9729b24cda..34b6924610 100644 --- a/core/engine/src/builtins/typed_array/mod.rs +++ b/core/engine/src/builtins/typed_array/mod.rs @@ -28,15 +28,16 @@ use crate::{ value::{JsValue, Numeric}, Context, JsArgs, JsResult, JsString, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; mod builtin; mod element; -mod integer_indexed_object; +mod object; pub(crate) use builtin::{is_valid_integer_index, BuiltinTypedArray}; pub(crate) use element::{Atomic, ClampedU8, Element}; -pub use integer_indexed_object::{IntegerIndexed, TypedArray}; +pub use object::TypedArray; pub(crate) trait TypedArrayMarker { type Element: Element; @@ -137,18 +138,39 @@ impl BuiltInConstructor for T { let first_argument = &args[0]; // b. If Type(firstArgument) is Object, then - if let Some(first_argument) = first_argument.as_object() { - // i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto). - let proto = - get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?; + let Some(first_argument) = first_argument.as_object() else { + // c. Else, + // i. Assert: firstArgument is not an Object. + // Ensured by the let-else + + // ii. Let elementLength be ? ToIndex(firstArgument). + let element_length = first_argument.to_index(context)?; + + // iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength). + return BuiltinTypedArray::allocate::(new_target, element_length, context) + .map(JsValue::from); + }; - // ii. If firstArgument has a [[TypedArrayName]] internal slot, then - let o = if first_argument.is::() { + let first_argument = first_argument.clone(); + + // i. Let O be ? AllocateTypedArray(constructorName, NewTarget, proto). + let proto = get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?; + + // ii. If firstArgument has a [[TypedArrayName]] internal slot, then + let first_argument = match first_argument.downcast::() { + Ok(arr) => { // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). - BuiltinTypedArray::initialize_from_typed_array::(proto, first_argument, context)? - } else if first_argument.is_buffer() { - // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then + // v. Return O. + return BuiltinTypedArray::initialize_from_typed_array::(proto, &arr, context) + .map(JsValue::from); + } + Err(obj) => obj, + }; + + // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then + let first_argument = match first_argument.into_buffer_object() { + Ok(buf) => { // 1. If numberOfArgs > 1, let byteOffset be args[1]; else let byteOffset be undefined. let byte_offset = args.get_or_undefined(1); @@ -156,59 +178,46 @@ impl BuiltInConstructor for T { let length = args.get_or_undefined(2); // 3. Perform ? InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length). - BuiltinTypedArray::initialize_from_array_buffer::( + + // v. Return O. + return BuiltinTypedArray::initialize_from_array_buffer::( proto, - first_argument.clone(), + buf, byte_offset, length, context, - )? - } else { - // iv. Else, - - // 1. Assert: Type(firstArgument) is Object and firstArgument does not have - // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. - - // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). - - let first_argument_v = JsValue::from(first_argument.clone()); - let using_iterator = first_argument_v.get_method(JsSymbol::iterator(), context)?; - - // 3. If usingIterator is not undefined, then - if let Some(using_iterator) = using_iterator { - // a. Let values be ? IterableToList(firstArgument, usingIterator). - let values = - iterable_to_list(context, &first_argument_v, Some(using_iterator))?; - - // b. Perform ? InitializeTypedArrayFromList(O, values). - BuiltinTypedArray::initialize_from_list::(proto, values, context)? - } else { - // 4. Else, - - // a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object. - // b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument). - BuiltinTypedArray::initialize_from_array_like::( - proto, - first_argument, - context, - )? - } - }; - - // v. Return O. - Ok(o.into()) - } else { - // c. Else, + ) + .map(JsValue::from); + } + Err(obj) => obj, + }; - // i. Assert: firstArgument is not an Object. - assert!(!first_argument.is_object(), "firstArgument was an object"); + // iv. Else, - // ii. Let elementLength be ? ToIndex(firstArgument). - let element_length = first_argument.to_index(context)?; + // 1. Assert: Type(firstArgument) is Object and firstArgument does not have + // either a [[TypedArrayName]] or an [[ArrayBufferData]] internal slot. - // iii. Return ? AllocateTypedArray(constructorName, NewTarget, proto, elementLength). - Ok(BuiltinTypedArray::allocate::(new_target, element_length, context)?.into()) + // 2. Let usingIterator be ? GetMethod(firstArgument, @@iterator). + + let using_iterator = first_argument.get_method(JsSymbol::iterator(), context)?; + + // 3. If usingIterator is not undefined, then + if let Some(using_iterator) = using_iterator { + // a. Let values be ? IterableToList(firstArgument, usingIterator). + let values = iterable_to_list(context, &first_argument.into(), Some(using_iterator))?; + + // b. Perform ? InitializeTypedArrayFromList(O, values). + BuiltinTypedArray::initialize_from_list::(proto, values, context) + } else { + // 4. Else, + + // a. NOTE: firstArgument is not an Iterable so assume it is already an array-like object. + // b. Perform ? InitializeTypedArrayFromArrayLike(O, firstArgument). + BuiltinTypedArray::initialize_from_array_like::(proto, &first_argument, context) } + .map(JsValue::from) + + // v. Return O. } } @@ -330,7 +339,8 @@ pub(crate) enum ContentType { } /// List of all typed array kinds. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize)] +#[boa_gc(empty_trace)] pub(crate) enum TypedArrayKind { Int8, Uint8, diff --git a/core/engine/src/builtins/typed_array/integer_indexed_object.rs b/core/engine/src/builtins/typed_array/object.rs similarity index 75% rename from core/engine/src/builtins/typed_array/integer_indexed_object.rs rename to core/engine/src/builtins/typed_array/object.rs index ad526d3732..86881399bb 100644 --- a/core/engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -1,9 +1,9 @@ -//! This module implements the `Integer-Indexed` exotic object. +//! This module implements the `TypedArray` exotic object. use std::sync::atomic; use crate::{ - builtins::Number, + builtins::{array_buffer::BufferObject, Number}, object::{ internal_methods::{ ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, @@ -20,22 +20,16 @@ use boa_macros::utf16; use super::{is_valid_integer_index, TypedArrayKind}; -/// An `IntegerIndexed` object is just an alias for a `TypedArray` object. -pub type IntegerIndexed = TypedArray; - -/// A `TypedArrayObject` is an exotic object that performs special handling of integer +/// A `TypedArray` object is an exotic object that performs special handling of integer /// index property keys. /// -/// This is also called an `IntegerIndexed` object in the specification. -/// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-exotic-objects #[derive(Debug, Clone, Trace, Finalize)] pub struct TypedArray { - viewed_array_buffer: JsObject, - #[unsafe_ignore_trace] + viewed_array_buffer: BufferObject, kind: TypedArrayKind, byte_offset: u64, byte_length: u64, @@ -45,13 +39,13 @@ pub struct TypedArray { impl JsData for TypedArray { fn internal_methods(&self) -> &'static InternalObjectMethods { static METHODS: InternalObjectMethods = InternalObjectMethods { - __get_own_property__: integer_indexed_exotic_get_own_property, - __has_property__: integer_indexed_exotic_has_property, - __define_own_property__: integer_indexed_exotic_define_own_property, - __get__: integer_indexed_exotic_get, - __set__: integer_indexed_exotic_set, - __delete__: integer_indexed_exotic_delete, - __own_property_keys__: integer_indexed_exotic_own_property_keys, + __get_own_property__: typed_array_exotic_get_own_property, + __has_property__: typed_array_exotic_has_property, + __define_own_property__: typed_array_exotic_define_own_property, + __get__: typed_array_exotic_get, + __set__: typed_array_exotic_set, + __delete__: typed_array_exotic_delete, + __own_property_keys__: typed_array_exotic_own_property_keys, ..ORDINARY_INTERNAL_METHODS }; @@ -61,7 +55,7 @@ impl JsData for TypedArray { impl TypedArray { pub(crate) const fn new( - viewed_array_buffer: JsObject, + viewed_array_buffer: BufferObject, kind: TypedArrayKind, byte_offset: u64, byte_length: u64, @@ -85,37 +79,33 @@ impl TypedArray { /// /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer pub(crate) fn is_detached(&self) -> bool { - self.viewed_array_buffer - .borrow() - .as_buffer() - .expect("Typed array must have internal array buffer object") - .is_detached() + self.viewed_array_buffer.as_buffer().is_detached() } - /// Get the integer indexed object's byte offset. + /// Get the `TypedArray` object's byte offset. #[must_use] pub const fn byte_offset(&self) -> u64 { self.byte_offset } - /// Get the integer indexed object's typed array kind. + /// Get the `TypedArray` object's typed array kind. pub(crate) const fn kind(&self) -> TypedArrayKind { self.kind } - /// Get a reference to the integer indexed object's viewed array buffer. + /// Get a reference to the `TypedArray` object's viewed array buffer. #[must_use] - pub const fn viewed_array_buffer(&self) -> &JsObject { + pub(crate) const fn viewed_array_buffer(&self) -> &BufferObject { &self.viewed_array_buffer } - /// Get the integer indexed object's byte length. + /// Get the `TypedArray` object's byte length. #[must_use] pub const fn byte_length(&self) -> u64 { self.byte_length } - /// Get the integer indexed object's array length. + /// Get the `TypedArray` object's array length. #[must_use] pub const fn array_length(&self) -> u64 { self.array_length @@ -146,13 +136,13 @@ fn canonical_numeric_index_string(argument: &JsString) -> Option { None } -/// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects. +/// `[[GetOwnProperty]]` internal method for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p -pub(crate) fn integer_indexed_exotic_get_own_property( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-getownproperty +pub(crate) fn typed_array_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, context: &mut InternalMethodContext<'_>, @@ -170,7 +160,7 @@ pub(crate) fn integer_indexed_exotic_get_own_property( // 1.b. If numericIndex is not undefined, then if let Some(numeric_index) = p { // i. Let value be IntegerIndexedElementGet(O, numericIndex). - let value = integer_indexed_element_get(obj, numeric_index); + let value = typed_array_get_element(obj, numeric_index); // ii. If value is undefined, return undefined. // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. @@ -188,13 +178,13 @@ pub(crate) fn integer_indexed_exotic_get_own_property( ordinary_get_own_property(obj, key, context) } -/// `[[HasProperty]]` internal method for Integer-Indexed exotic objects. +/// `[[HasProperty]]` internal method for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p -pub(crate) fn integer_indexed_exotic_has_property( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-hasproperty +pub(crate) fn typed_array_exotic_has_property( obj: &JsObject, key: &PropertyKey, context: &mut InternalMethodContext<'_>, @@ -218,13 +208,13 @@ pub(crate) fn integer_indexed_exotic_has_property( ordinary_has_property(obj, key, context) } -/// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects. +/// `[[DefineOwnProperty]]` internal method for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc -pub(crate) fn integer_indexed_exotic_define_own_property( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-defineownproperty +pub(crate) fn typed_array_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, @@ -269,7 +259,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property( // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). if let Some(value) = desc.value() { - integer_indexed_element_set(obj, numeric_index, value, context)?; + typed_array_set_element(obj, numeric_index, value, context)?; } // vii. Return true. @@ -280,13 +270,13 @@ pub(crate) fn integer_indexed_exotic_define_own_property( ordinary_define_own_property(obj, key, desc, context) } -/// Internal method `[[Get]]` for Integer-Indexed exotic objects. +/// Internal method `[[Get]]` for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver -pub(crate) fn integer_indexed_exotic_get( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-get +pub(crate) fn typed_array_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, @@ -305,20 +295,20 @@ pub(crate) fn integer_indexed_exotic_get( // 1.b. If numericIndex is not undefined, then if let Some(numeric_index) = p { // i. Return IntegerIndexedElementGet(O, numericIndex). - return Ok(integer_indexed_element_get(obj, numeric_index).unwrap_or_default()); + return Ok(typed_array_get_element(obj, numeric_index).unwrap_or_default()); } // 2. Return ? OrdinaryGet(O, P, Receiver). ordinary_get(obj, key, receiver, context) } -/// Internal method `[[Set]]` for Integer-Indexed exotic objects. +/// Internal method `[[Set]]` for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver -pub(crate) fn integer_indexed_exotic_set( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-set +pub(crate) fn typed_array_exotic_set( obj: &JsObject, key: PropertyKey, value: JsValue, @@ -340,7 +330,7 @@ pub(crate) fn integer_indexed_exotic_set( // i. If SameValue(O, Receiver) is true, then if JsValue::same_value(&obj.clone().into(), &receiver) { // 1. Perform ? IntegerIndexedElementSet(O, numericIndex, V). - integer_indexed_element_set(obj, numeric_index, &value, context)?; + typed_array_set_element(obj, numeric_index, &value, context)?; // 2. Return true. return Ok(true); @@ -356,13 +346,13 @@ pub(crate) fn integer_indexed_exotic_set( ordinary_set(obj, key, value, receiver, context) } -/// Internal method `[[Delete]]` for Integer-Indexed exotic objects. +/// Internal method `[[Delete]]` for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p -pub(crate) fn integer_indexed_exotic_delete( +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-delete +pub(crate) fn typed_array_exotic_delete( obj: &JsObject, key: &PropertyKey, context: &mut InternalMethodContext<'_>, @@ -387,21 +377,21 @@ pub(crate) fn integer_indexed_exotic_delete( ordinary_delete(obj, key, context) } -/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects. +/// Internal method `[[OwnPropertyKeys]]` for `TypedArray` exotic objects. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys +/// [spec]: https://tc39.es/ecma262/#sec-typedarray-ownpropertykeys #[allow(clippy::unnecessary_wraps)] -pub(crate) fn integer_indexed_exotic_own_property_keys( +pub(crate) fn typed_array_exotic_own_property_keys( obj: &JsObject, _context: &mut Context, ) -> JsResult> { let obj = obj.borrow(); - let inner = obj.downcast_ref::().expect( - "integer indexed exotic method should only be callable from integer indexed objects", - ); + let inner = obj + .downcast_ref::() + .expect("TypedArray exotic method should only be callable from TypedArray objects"); // 1. Let keys be a new empty List. let mut keys = if inner.is_detached() { @@ -424,13 +414,13 @@ pub(crate) fn integer_indexed_exotic_own_property_keys( Ok(keys) } -/// Abstract operation `IntegerIndexedElementGet ( O, index )`. +/// Abstract operation `TypedArrayGetElement ( O, index )`. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget -fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { +/// [spec]: https://tc39.es/ecma262/sec-typedarraygetelement +fn typed_array_get_element(obj: &JsObject, index: f64) -> Option { // 1. If ! IsValidIntegerIndex(O, index) is false, return undefined. if !is_valid_integer_index(obj, index) { return None; @@ -438,10 +428,9 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { let inner = obj .downcast_ref::() - .expect("Must be an integer indexed object"); + .expect("Must be an TypedArray object"); let buffer = inner.viewed_array_buffer(); - let buffer = buffer.borrow(); - let buffer = buffer.as_buffer().expect("Must be a buffer"); + let buffer = buffer.as_buffer(); let buffer = buffer .data() .expect("already checked that it's not detached"); @@ -461,7 +450,7 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). - // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // SAFETY: The TypedArray object guarantees that the buffer is aligned. // The call to `is_valid_integer_index` guarantees that the index is in-bounds. let value = unsafe { buffer @@ -472,22 +461,22 @@ fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { Some(value.into()) } -/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`. +/// Abstract operation `TypedArraySetElement ( O, index, value )`. /// /// More information: /// - [ECMAScript reference][spec] /// -/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset -pub(crate) fn integer_indexed_element_set( +/// [spec]: https://tc39.es/ecma262/#sec-typedarraysetelement +pub(crate) fn typed_array_set_element( obj: &JsObject, index: f64, value: &JsValue, context: &mut InternalMethodContext<'_>, ) -> JsResult<()> { let obj_borrow = obj.borrow(); - let inner = obj_borrow.downcast_ref::().expect( - "integer indexed exotic method should only be callable from integer indexed objects", - ); + let inner = obj_borrow + .downcast_ref::() + .expect("TypedArray exotic method should only be callable from TypedArray objects"); // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). // 2. Otherwise, let numValue be ? ToNumber(value). @@ -512,15 +501,14 @@ pub(crate) fn integer_indexed_element_set( let indexed_position = ((index as u64 * size) + offset) as usize; let buffer = inner.viewed_array_buffer(); - let mut buffer = buffer.borrow_mut(); - let mut buffer = buffer.as_buffer_mut().expect("Must be a buffer"); + let mut buffer = buffer.as_buffer_mut(); let mut buffer = buffer .data_mut() .expect("already checked that it's not detached"); // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). - // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // SAFETY: The TypedArray object guarantees that the buffer is aligned. // The call to `is_valid_integer_index` guarantees that the index is in-bounds. unsafe { buffer diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index 90f1075cd4..e378ccde07 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -17,6 +17,20 @@ pub struct JsArrayBuffer { inner: JsObject, } +impl From for JsObject { + #[inline] + fn from(value: JsArrayBuffer) -> Self { + value.inner + } +} + +impl From> for JsArrayBuffer { + #[inline] + fn from(value: JsObject) -> Self { + Self { inner: value } + } +} + // TODO: Add constructors that also take the `detach_key` as argument. impl JsArrayBuffer { /// Create a new array buffer with byte length. @@ -157,7 +171,7 @@ impl JsArrayBuffer { /// /// # Note /// - /// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detached + /// This tries to detach the pre-existing `JsArrayBuffer`, meaning the original detach /// key is required. By default, the key is set to `undefined`. /// /// ``` diff --git a/core/engine/src/object/builtins/jsdataview.rs b/core/engine/src/object/builtins/jsdataview.rs index d242fc7ab9..4e6bcdfcd4 100644 --- a/core/engine/src/object/builtins/jsdataview.rs +++ b/core/engine/src/object/builtins/jsdataview.rs @@ -1,6 +1,6 @@ //! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object use crate::{ - builtins::DataView, + builtins::{array_buffer::BufferObject, DataView}, context::intrinsics::StandardConstructors, object::{ internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType, @@ -33,8 +33,23 @@ use std::ops::Deref; /// # } /// ``` #[derive(Debug, Clone, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] pub struct JsDataView { - inner: JsObject, + inner: JsObject, +} + +impl From for JsObject { + #[inline] + fn from(value: JsDataView) -> Self { + value.inner + } +} + +impl From> for JsDataView { + #[inline] + fn from(value: JsObject) -> Self { + Self { inner: value } + } } impl JsDataView { @@ -90,12 +105,11 @@ impl JsDataView { let prototype = get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?; - let obj = JsObject::from_proto_and_data_with_shared_shape( + let obj = JsObject::new( context.root_shape(), prototype, DataView { - // TODO: Remove upcast. - viewed_array_buffer: array_buffer.into(), + viewed_array_buffer: BufferObject::Buffer(array_buffer.into()), byte_length, byte_offset, }, @@ -107,32 +121,33 @@ impl JsDataView { /// Create a new `JsDataView` object from an existing object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is::() { - Ok(Self { inner: object }) - } else { - Err(JsNativeError::typ() - .with_message("object is not a DataView") - .into()) - } + object + .downcast::() + .map(|inner| Self { inner }) + .map_err(|_| { + JsNativeError::typ() + .with_message("object is not a DataView") + .into() + }) } /// Returns the `viewed_array_buffer` field for [`JsDataView`] #[inline] pub fn buffer(&self, context: &mut Context) -> JsResult { - DataView::get_buffer(&self.inner.clone().into(), &[], context) + DataView::get_buffer(&self.inner.clone().upcast().into(), &[], context) } /// Returns the `byte_length` property of [`JsDataView`] as a u64 integer #[inline] pub fn byte_length(&self, context: &mut Context) -> JsResult { - DataView::get_byte_length(&self.inner.clone().into(), &[], context) + DataView::get_byte_length(&self.inner.clone().upcast().into(), &[], context) .map(|v| v.as_number().expect("value should be a number") as u64) } /// Returns the `byte_offset` field property of [`JsDataView`] as a u64 integer #[inline] pub fn byte_offset(&self, context: &mut Context) -> JsResult { - DataView::get_byte_offset(&self.inner.clone().into(), &[], context) + DataView::get_byte_offset(&self.inner.clone().upcast().into(), &[], context) .map(|v| v.as_number().expect("byte_offset value must be a number") as u64) } @@ -145,7 +160,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_big_int64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -161,7 +176,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_big_uint64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -177,7 +192,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_float32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -193,7 +208,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_float64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -209,7 +224,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_int8( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -225,7 +240,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_int16( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -241,7 +256,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_int32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -257,7 +272,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_uint8( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -273,7 +288,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_uint16( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -289,7 +304,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::get_uint32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), is_little_endian.into()], context, ) @@ -306,7 +321,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_big_int64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -322,7 +337,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_big_uint64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -338,7 +353,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_float32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -354,7 +369,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_float64( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -370,7 +385,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_int8( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -386,7 +401,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_int16( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -402,7 +417,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_int32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -418,7 +433,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_uint8( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -434,7 +449,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_uint16( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -450,7 +465,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { DataView::set_uint32( - &self.inner.clone().into(), + &self.inner.clone().upcast().into(), &[byte_offset.into(), value.into(), is_little_endian.into()], context, ) @@ -460,19 +475,19 @@ impl JsDataView { impl From for JsObject { #[inline] fn from(o: JsDataView) -> Self { - o.inner.clone() + o.inner.upcast() } } impl From for JsValue { #[inline] fn from(o: JsDataView) -> Self { - o.inner.clone().into() + o.inner.upcast().into() } } impl Deref for JsDataView { - type Target = JsObject; + type Target = JsObject; #[inline] fn deref(&self) -> &Self::Target { diff --git a/core/engine/src/object/builtins/jssharedarraybuffer.rs b/core/engine/src/object/builtins/jssharedarraybuffer.rs index b9fea88761..1a892a6f14 100644 --- a/core/engine/src/object/builtins/jssharedarraybuffer.rs +++ b/core/engine/src/object/builtins/jssharedarraybuffer.rs @@ -11,8 +11,23 @@ use std::ops::Deref; /// `JsSharedArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object #[derive(Debug, Clone, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] pub struct JsSharedArrayBuffer { - inner: JsObject, + inner: JsObject, +} + +impl From for JsObject { + #[inline] + fn from(value: JsSharedArrayBuffer) -> Self { + value.inner + } +} + +impl From> for JsSharedArrayBuffer { + #[inline] + fn from(value: JsObject) -> Self { + JsSharedArrayBuffer { inner: value } + } } impl JsSharedArrayBuffer { @@ -42,8 +57,7 @@ impl JsSharedArrayBuffer { .shared_array_buffer() .prototype(); - let inner = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buffer); + let inner = JsObject::new(context.root_shape(), proto, buffer); Self { inner } } @@ -55,50 +69,47 @@ impl JsSharedArrayBuffer { /// the object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is::() { - Ok(Self { inner: object }) - } else { - Err(JsNativeError::typ() - .with_message("object is not an ArrayBuffer") - .into()) - } + object + .downcast::() + .map(|inner| Self { inner }) + .map_err(|_| { + JsNativeError::typ() + .with_message("object is not a SharedArrayBuffer") + .into() + }) } /// Returns the byte length of the array buffer. #[inline] #[must_use] pub fn byte_length(&self) -> usize { - self.downcast_ref::() - .expect("should be an array buffer") - .len() + self.borrow().data.len() } /// Gets the raw buffer of this `JsSharedArrayBuffer`. #[inline] #[must_use] pub fn inner(&self) -> SharedArrayBuffer { - self.downcast_ref::() - .expect("should be an array buffer") - .clone() + self.borrow().data.clone() } } impl From for JsObject { #[inline] fn from(o: JsSharedArrayBuffer) -> Self { - o.inner.clone() + o.inner.upcast() } } impl From for JsValue { #[inline] fn from(o: JsSharedArrayBuffer) -> Self { - o.inner.clone().into() + o.inner.upcast().into() } } impl Deref for JsSharedArrayBuffer { - type Target = JsObject; + type Target = JsObject; #[inline] fn deref(&self) -> &Self::Target { diff --git a/core/engine/src/object/jsobject.rs b/core/engine/src/object/jsobject.rs index 364dae04f1..fbc517cfde 100644 --- a/core/engine/src/object/jsobject.rs +++ b/core/engine/src/object/jsobject.rs @@ -8,7 +8,11 @@ use super::{ JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; use crate::{ - builtins::{array::ARRAY_EXOTIC_INTERNAL_METHODS, object::OrdinaryObject}, + builtins::{ + array::ARRAY_EXOTIC_INTERNAL_METHODS, + array_buffer::{ArrayBuffer, BufferObject, SharedArrayBuffer}, + object::OrdinaryObject, + }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, @@ -212,6 +216,24 @@ impl JsObject { } } + /// Downcasts the object's inner data to `T` without verifying the inner type of `T`. + /// + /// # Safety + /// + /// For this cast to be sound, `self` must contain an instance of `T` inside its inner data. + #[must_use] + pub unsafe fn downcast_unchecked(self) -> JsObject { + let ptr: NonNull>> = Gc::into_raw(self.inner).cast(); + + // SAFETY: The caller guarantees `T` is the original inner data type of the underlying + // object. + unsafe { + JsObject { + inner: Gc::from_raw(ptr), + } + } + } + /// Downcasts a reference to the object, /// if the object is of type `T`. /// @@ -268,18 +290,6 @@ impl JsObject { std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS) } - /// Checks if it's an `ArrayBuffer` or `SharedArrayBuffer` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_buffer(&self) -> bool { - self.borrow().as_buffer().is_some() - } - /// Converts an object to a primitive. /// /// Diverges from the spec to prevent a stack overflow when the object is recursive. @@ -535,6 +545,30 @@ Cannot both specify accessors and a value or writable attribute", } None } + + /// Casts to a `BufferObject` if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. + #[inline] + pub(crate) fn into_buffer_object(self) -> Result { + let obj = self.borrow(); + + if obj.is::() { + drop(obj); + // SAFETY: We have verified that the inner data of `self` is of type `ArrayBuffer`. + return Ok(BufferObject::Buffer(unsafe { + self.downcast_unchecked::() + })); + } + if obj.is::() { + drop(obj); + // SAFETY: We have verified that the inner data of `self` is of type `SharedArrayBuffer`. + return Ok(BufferObject::SharedBuffer(unsafe { + self.downcast_unchecked::() + })); + } + drop(obj); + + Err(self) + } } impl JsObject { diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index 04831e9d89..84e786f696 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -10,7 +10,6 @@ use thin_vec::ThinVec; use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape}; use crate::{ builtins::{ - array_buffer::{ArrayBuffer, BufferRef, BufferRefMut, SharedArrayBuffer}, function::{ arguments::{MappedArguments, UnmappedArguments}, ConstructorKind, @@ -352,33 +351,6 @@ impl Object { self.data.downcast_mut::() } - /// Gets the buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. - #[inline] - #[must_use] - pub(crate) fn as_buffer(&self) -> Option> { - if let Some(buffer) = self.downcast_ref::() { - return Some(BufferRef::Buffer(buffer)); - } - self.downcast_ref::() - .map(BufferRef::SharedBuffer) - } - - /// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. - #[inline] - pub(crate) fn as_buffer_mut(&mut self) -> Option> { - // Workaround for Problem case 3 of the current borrow checker. - // https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions - if self.is::() { - match self.downcast_mut::() { - Some(buffer) => return Some(BufferRefMut::Buffer(buffer)), - None => unreachable!(), - } - } - - self.downcast_mut::() - .map(BufferRefMut::SharedBuffer) - } - /// Checks if this object is an `Arguments` object. pub(crate) fn is_arguments(&self) -> bool { self.is::() || self.is::()