diff --git a/core/engine/src/builtins/array/array_iterator.rs b/core/engine/src/builtins/array/array_iterator.rs index 7525ba3b58..d323a7ad60 100644 --- a/core/engine/src/builtins/array/array_iterator.rs +++ b/core/engine/src/builtins/array/array_iterator.rs @@ -117,15 +117,17 @@ impl ArrayIterator { } let len = if let Some(f) = array_iterator.array.downcast_ref::() { - if f.is_detached() { + let buf = f.viewed_array_buffer().as_buffer(); + let Some(buf) = buf + .bytes(std::sync::atomic::Ordering::SeqCst) + .filter(|buf| !f.is_out_of_bounds(buf.len())) + else { return Err(JsNativeError::typ() - .with_message( - "Cannot get value from typed array that has a detached array buffer", - ) + .with_message("Cannot get value from out of bounds typed array") .into()); - } + }; - f.array_length() + f.array_length(buf.len()) } else { array_iterator.array.length_of_array_like(context)? }; diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index 5aa44e24ab..6f0d10d78e 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -36,7 +36,7 @@ use crate::{ value::{IntegerOrInfinity, JsValue}, Context, JsArgs, JsResult, JsString, }; -use std::cmp::{max, min, Ordering}; +use std::cmp::{min, Ordering}; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; @@ -3216,18 +3216,19 @@ impl Array { ) -> JsResult { // 1. Let relativeStart be ? ToIntegerOrInfinity(start). let relative_start = arg.to_integer_or_infinity(context)?; - match relative_start { + let start = match relative_start { // 2. If relativeStart is -∞, let k be 0. - IntegerOrInfinity::NegativeInfinity => Ok(0), + IntegerOrInfinity::NegativeInfinity => 0, // 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => Ok(max(len as i64 + i, 0) as u64), - // Both `as` casts are safe as both variables are non-negative + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), // 4. Else, let k be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => Ok(min(i, len as i64) as u64), + IntegerOrInfinity::Integer(i) => min(i as u64, len), // Special case - positive infinity. `len` is always smaller than +inf, thus from (4) - IntegerOrInfinity::PositiveInfinity => Ok(len), - } + IntegerOrInfinity::PositiveInfinity => len, + }; + + Ok(start) } /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions. @@ -3242,18 +3243,20 @@ impl Array { } else { // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end). let relative_end = value.to_integer_or_infinity(context)?; - match relative_end { + let end = match relative_end { // 2. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => Ok(0), + IntegerOrInfinity::NegativeInfinity => 0, // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => Ok(max(len as i64 + i, 0) as u64), + IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), // 4. Else, let final be min(relativeEnd, len). // Both `as` casts are safe as both variables are non-negative - IntegerOrInfinity::Integer(i) => Ok(min(i, len as i64) as u64), + IntegerOrInfinity::Integer(i) => min(i as u64, len), // Special case - positive infinity. `len` is always smaller than +inf, thus from (4) - IntegerOrInfinity::PositiveInfinity => Ok(len), - } + IntegerOrInfinity::PositiveInfinity => len, + }; + + Ok(end) } } diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index a8834fc045..b90e02d99e 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -19,6 +19,7 @@ mod tests; use std::ops::{Deref, DerefMut}; pub use shared::SharedArrayBuffer; +use std::sync::atomic::Ordering; use crate::{ builtins::BuiltInObject, @@ -30,7 +31,6 @@ use crate::{ realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, - value::IntegerOrInfinity, Context, JsArgs, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, GcRef, GcRefMut, Trace}; @@ -39,7 +39,7 @@ use boa_profiler::Profiler; use self::utils::{SliceRef, SliceRefMut}; use super::{ - typed_array::TypedArray, BuiltInBuilder, BuiltInConstructor, DataView, IntrinsicObject, + typed_array::TypedArray, Array, BuiltInBuilder, BuiltInConstructor, DataView, IntrinsicObject, }; #[derive(Debug, Clone, Copy)] @@ -53,15 +53,31 @@ where B: Deref, S: Deref, { - pub(crate) fn data(&self) -> Option> { + /// Gets the inner data of the buffer. + pub(crate) fn bytes(&self, ordering: Ordering) -> Option> { match self { - Self::Buffer(buf) => buf.deref().data().map(SliceRef::Slice), - Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().data())), + Self::Buffer(buf) => buf.deref().bytes().map(SliceRef::Slice), + Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes(ordering))), } } - pub(crate) fn is_detached(&self) -> bool { - self.data().is_none() + /// Gets the inner data of the buffer without accessing the current atomic length. + /// + /// Returns `None` if the buffer is detached or if the provided `len` is bigger than + /// the allocated buffer. + #[track_caller] + pub(crate) fn bytes_with_len(&self, len: usize) -> Option> { + match self { + Self::Buffer(buf) => buf.deref().bytes_with_len(len).map(SliceRef::Slice), + Self::SharedBuffer(buf) => Some(SliceRef::AtomicSlice(buf.deref().bytes_with_len(len))), + } + } + + pub(crate) fn is_fixed_len(&self) -> bool { + match self { + Self::Buffer(buf) => buf.is_fixed_len(), + Self::SharedBuffer(buf) => buf.is_fixed_len(), + } } } @@ -76,10 +92,28 @@ where B: DerefMut, S: DerefMut, { - pub(crate) fn data_mut(&mut self) -> Option> { + pub(crate) fn bytes(&mut self, ordering: Ordering) -> Option> { + match self { + Self::Buffer(buf) => buf.deref_mut().bytes_mut().map(SliceRefMut::Slice), + Self::SharedBuffer(buf) => { + Some(SliceRefMut::AtomicSlice(buf.deref_mut().bytes(ordering))) + } + } + } + + /// Gets the mutable inner data of the buffer without accessing the current atomic length. + /// + /// Returns `None` if the buffer is detached or if the provided `len` is bigger than + /// the allocated buffer. + pub(crate) fn bytes_with_len(&mut self, len: usize) -> Option> { match self { - Self::Buffer(buf) => buf.deref_mut().data_mut().map(SliceRefMut::Slice), - Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice(buf.deref_mut().data())), + Self::Buffer(buf) => buf + .deref_mut() + .bytes_with_len_mut(len) + .map(SliceRefMut::Slice), + Self::SharedBuffer(buf) => Some(SliceRefMut::AtomicSlice( + buf.deref_mut().bytes_with_len(len), + )), } } } @@ -153,7 +187,7 @@ impl BufferObject { let lhs = lhs.borrow(); let rhs = rhs.borrow(); - std::ptr::eq(lhs.data.data().as_ptr(), rhs.data.data().as_ptr()) + std::ptr::eq(lhs.data.as_ptr(), rhs.data.as_ptr()) } _ => false, } @@ -166,6 +200,9 @@ pub struct ArrayBuffer { /// The `[[ArrayBufferData]]` internal slot. data: Option>, + /// The `[[ArrayBufferMaxByteLength]]` internal slot. + max_byte_len: Option, + /// The `[[ArrayBufferDetachKey]]` internal slot. detach_key: JsValue, } @@ -174,6 +211,7 @@ impl ArrayBuffer { pub(crate) fn from_data(data: Vec, detach_key: JsValue) -> Self { Self { data: Some(data), + max_byte_len: None, detach_key, } } @@ -182,14 +220,38 @@ impl ArrayBuffer { self.data.as_ref().map_or(0, Vec::len) } - pub(crate) fn data(&self) -> Option<&[u8]> { + pub(crate) fn bytes(&self) -> Option<&[u8]> { self.data.as_deref() } - pub(crate) fn data_mut(&mut self) -> Option<&mut [u8]> { + pub(crate) fn bytes_mut(&mut self) -> Option<&mut [u8]> { self.data.as_deref_mut() } + pub(crate) fn vec_mut(&mut self) -> Option<&mut Vec> { + self.data.as_mut() + } + + /// Gets the inner bytes of the buffer without accessing the current atomic length. + #[track_caller] + pub(crate) fn bytes_with_len(&self, len: usize) -> Option<&[u8]> { + if let Some(s) = self.data.as_deref() { + Some(&s[..len]) + } else { + None + } + } + + /// Gets the mutable inner bytes of the buffer without accessing the current atomic length. + #[track_caller] + pub(crate) fn bytes_with_len_mut(&mut self, len: usize) -> Option<&mut [u8]> { + if let Some(s) = self.data.as_deref_mut() { + Some(&mut s[..len]) + } else { + None + } + } + /// Detaches the inner data of this `ArrayBuffer`, returning the original buffer if still /// present. /// @@ -206,7 +268,7 @@ impl ArrayBuffer { Ok(self.data.take()) } - /// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )` + /// `IsDetachedBuffer ( arrayBuffer )` /// /// More information: /// - [ECMAScript reference][spec] @@ -217,6 +279,10 @@ impl ArrayBuffer { // 2. Return false. self.data.is_none() } + + pub(crate) fn is_fixed_len(&self) -> bool { + self.max_byte_len.is_none() + } } impl IntrinsicObject for ArrayBuffer { @@ -233,20 +299,41 @@ impl IntrinsicObject for ArrayBuffer { .name(js_string!("get byteLength")) .build(); + let get_resizable = BuiltInBuilder::callable(realm, Self::get_resizable) + .name(js_string!("get resizable")) + .build(); + + let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) + .name(js_string!("get maxByteLength")) + .build(); + BuiltInBuilder::from_standard_constructor::(realm) + .static_accessor( + JsSymbol::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) + .static_method(Self::is_view, js_string!("isView"), 1) .accessor( js_string!("byteLength"), Some(get_byte_length), None, flag_attributes, ) - .static_accessor( - JsSymbol::species(), - Some(get_species), + .accessor( + js_string!("resizable"), + Some(get_resizable), None, - Attribute::CONFIGURABLE, + flag_attributes, ) - .static_method(Self::is_view, js_string!("isView"), 1) + .accessor( + js_string!("maxByteLength"), + Some(get_max_byte_length), + None, + flag_attributes, + ) + .method(Self::resize, js_string!("resize"), 1) .method(Self::slice, js_string!("slice"), 2) .property( JsSymbol::to_string_tag(), @@ -271,7 +358,7 @@ impl BuiltInConstructor for ArrayBuffer { const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::array_buffer; - /// `25.1.3.1 ArrayBuffer ( length )` + /// `ArrayBuffer ( length )` /// /// More information: /// - [ECMAScript reference][spec] @@ -290,29 +377,20 @@ impl BuiltInConstructor for ArrayBuffer { } // 2. Let byteLength be ? ToIndex(length). - let byte_length = args.get_or_undefined(0).to_index(context)?; + let byte_len = args.get_or_undefined(0).to_index(context)?; - // 3. Return ? AllocateArrayBuffer(NewTarget, byteLength). - Ok(Self::allocate(new_target, byte_length, context)? + // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). + let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; + + // 4. Return ? AllocateArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). + Ok(Self::allocate(new_target, byte_len, max_byte_len, context)? .upcast() .into()) } } impl ArrayBuffer { - /// `25.1.4.3 get ArrayBuffer [ @@species ]` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species - #[allow(clippy::unnecessary_wraps)] - fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - // 1. Return the this value. - Ok(this.clone()) - } - - /// `25.1.4.1 ArrayBuffer.isView ( arg )` + /// `ArrayBuffer.isView ( arg )` /// /// More information: /// - [ECMAScript reference][spec] @@ -331,7 +409,19 @@ impl ArrayBuffer { .into()) } - /// `25.1.5.1 get ArrayBuffer.prototype.byteLength` + /// `get ArrayBuffer [ @@species ]` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species + #[allow(clippy::unnecessary_wraps)] + fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { + // 1. Return the this value. + Ok(this.clone()) + } + + /// `get ArrayBuffer.prototype.byteLength` /// /// More information: /// - [ECMAScript reference][spec] @@ -350,7 +440,7 @@ impl ArrayBuffer { .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() - .with_message("ArrayBuffer.byteLength called with non `ArrayBuffer` object") + .with_message("get ArrayBuffer.prototype.byteLength called with invalid `this`") })?; // 4. If IsDetachedBuffer(O) is true, return +0𝔽. @@ -359,7 +449,127 @@ impl ArrayBuffer { Ok(buf.len().into()) } - /// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )` + /// [`get ArrayBuffer.prototype.maxByteLength`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.maxbytelength + pub(crate) fn get_max_byte_length( + this: &JsValue, + _args: &[JsValue], + _context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + let buf = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message( + "get ArrayBuffer.prototype.maxByteLength called with invalid `this`", + ) + })?; + + // 4. If IsDetachedBuffer(O) is true, return +0𝔽. + let Some(data) = buf.bytes() else { + return Ok(JsValue::from(0)); + }; + + // 5. If IsFixedLengthArrayBuffer(O) is true, then + // a. Let length be O.[[ArrayBufferByteLength]]. + // 6. Else, + // a. Let length be O.[[ArrayBufferMaxByteLength]]. + // 7. Return 𝔽(length). + Ok(buf.max_byte_len.unwrap_or(data.len() as u64).into()) + } + + /// [`get ArrayBuffer.prototype.resizable`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable + pub(crate) fn get_resizable( + this: &JsValue, + _args: &[JsValue], + _context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + let buf = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("get ArrayBuffer.prototype.resizable called with invalid `this`") + })?; + + // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false. + Ok(JsValue::from(!buf.is_fixed_len())) + } + + /// [`ArrayBuffer.prototype.resize ( newLength )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize + pub(crate) fn resize( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]). + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + let buf = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("ArrayBuffer.prototype.resize called with invalid `this`") + })?; + + let Some(max_byte_len) = buf.borrow().data.max_byte_len else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer") + .into()); + }; + + // 4. Let newByteLength be ? ToIndex(newLength). + let new_byte_length = args.get_or_undefined(0).to_index(context)?; + + let mut buf = buf.borrow_mut(); + // 5. If IsDetachedBuffer(O) is true, throw a TypeError exception. + let Some(buf) = buf.data.vec_mut() else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.resize: cannot resize a detached buffer") + .into()); + }; + + // 6. If newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. + if new_byte_length > max_byte_len { + return Err(JsNativeError::range() + .with_message( + "ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length", + ) + .into()); + } + + // TODO: 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength). + // 8. If hostHandled is handled, return undefined. + // Used in engines to handle WASM buffers in a special way, but we don't + // have a WASM interpreter in place yet. + + // 9. Let oldBlock be O.[[ArrayBufferData]]. + // 10. Let newBlock be ? CreateByteDataBlock(newByteLength). + // 11. Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]). + // 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength). + // 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. + // Implementations may implement this method as in-place growth or shrinkage. + // 14. Set O.[[ArrayBufferData]] to newBlock. + // 15. Set O.[[ArrayBufferByteLength]] to newByteLength. + buf.resize(new_byte_length as usize, 0); + + // 16. Return undefined. + Ok(JsValue::undefined()) + } + + /// `ArrayBuffer.prototype.slice ( start, end )` /// /// More information: /// - [ECMAScript reference][spec] @@ -369,89 +579,92 @@ impl ArrayBuffer { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") - })?; + let buf = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("ArrayBuffer.slice called with invalid `this` value") + })?; - let buf = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") - })?; + let len = { + let buf = buf.borrow(); + // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if buf.data.is_detached() { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.slice called with detached buffer") + .into()); + } + // 5. Let len be O.[[ArrayBufferByteLength]]. + buf.data.len() as u64 + }; - // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. - if buf.is_detached() { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.slice called with detached buffer") - .into()); - } + // 6. Let relativeStart be ? ToIntegerOrInfinity(start). + // 7. If relativeStart = -∞, let first be 0. + // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). + // 9. Else, let first be min(relativeStart, len). + let first = Array::get_relative_start(context, args.get_or_undefined(0), len)?; - let SliceRange { - start: first, - length: new_len, - } = get_slice_range( - buf.len() as u64, - args.get_or_undefined(0), - args.get_or_undefined(1), - context, - )?; + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + // 11. If relativeEnd = -∞, let final be 0. + // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + // 13. Else, let final be min(relativeEnd, len). + let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len)?; + + // 14. Let newLen be max(final - first, 0). + let new_len = final_.saturating_sub(first); // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). - let ctor = obj.species_constructor(StandardConstructors::array_buffer, context)?; + let ctor = buf + .clone() + .upcast() + .species_constructor(StandardConstructors::array_buffer, context)?; // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; - { - // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. - let new = new.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer constructor returned invalid object") - })?; + // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). + // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. + let Ok(new) = new.downcast::() else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer constructor returned invalid object") + .into()); + }; - // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - if new.is_detached() { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer constructor returned detached ArrayBuffer") - .into()); - } - } // 20. If SameValue(new, O) is true, throw a TypeError exception. - if this - .as_object() - .map(|obj| JsObject::equals(obj, &new)) - .unwrap_or_default() - { + if JsObject::equals(&buf, &new) { return Err(JsNativeError::typ() .with_message("new ArrayBuffer is the same as this ArrayBuffer") .into()); } { - let mut new = new - .downcast_mut::() - .expect("Already checked that `new_obj` was an `ArrayBuffer`"); + // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. + // 25. Let toBuf be new.[[ArrayBufferData]]. + let mut new = new.borrow_mut(); + let Some(to_buf) = new.data.bytes_mut() else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer constructor returned detached ArrayBuffer") + .into()); + }; // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. - if (new.len() as u64) < new_len { + if (to_buf.len() as u64) < new_len { return Err(JsNativeError::typ() .with_message("new ArrayBuffer length too small") .into()); } // 22. NOTE: Side-effects of the above steps may have detached O. + // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. // 24. Let fromBuf be O.[[ArrayBufferData]]. - let Some(from_buf) = buf.data() else { - // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. + let buf = buf.borrow(); + let Some(from_buf) = buf.data.bytes() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer detached while ArrayBuffer.slice was running") .into()); }; - // 25. Let toBuf be new.[[ArrayBufferData]]. - let to_buf = new - .data - .as_mut() - .expect("ArrayBuffer cannot be detached here"); - // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). let first = first as usize; let new_len = new_len as usize; @@ -459,10 +672,10 @@ impl ArrayBuffer { } // 27. Return new. - Ok(new.into()) + Ok(new.upcast().into()) } - /// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )` + /// `AllocateArrayBuffer ( constructor, byteLength )` /// /// More information: /// - [ECMAScript reference][spec] @@ -470,86 +683,75 @@ impl ArrayBuffer { /// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer pub(crate) fn allocate( constructor: &JsValue, - byte_length: u64, + byte_len: u64, + max_byte_len: Option, context: &mut Context, ) -> JsResult> { - // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). + // 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ». + // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false. + // 3. If allocatingResizableBuffer is true, then + // a. If byteLength > maxByteLength, throw a RangeError exception. + // b. Append [[ArrayBufferMaxByteLength]] to slots. + if let Some(max_byte_len) = max_byte_len { + if byte_len > max_byte_len { + return Err(JsNativeError::range() + .with_message("`length` cannot be bigger than `maxByteLength`") + .into()); + } + } + + // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots). let prototype = get_prototype_from_constructor( constructor, StandardConstructors::array_buffer, context, )?; - // 2. Let block be ? CreateByteDataBlock(byteLength). - let block = create_byte_data_block(byte_length, context)?; + // 5. Let block be ? CreateByteDataBlock(byteLength). + // Preemptively allocate for `max_byte_len` if possible. + // a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception. + // b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may + // throw if, for example, virtual memory cannot be reserved up front. + let block = create_byte_data_block(byte_len, max_byte_len, context)?; - // 3. Set obj.[[ArrayBufferData]] to block. - // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. let obj = JsObject::new( context.root_shape(), prototype, Self { + // 6. Set obj.[[ArrayBufferData]] to block. + // 7. Set obj.[[ArrayBufferByteLength]] to byteLength. data: Some(block), + // 8. If allocatingResizableBuffer is true, then + // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. + max_byte_len, detach_key: JsValue::Undefined, }, ); - // 5. Return obj. + // 9. Return obj. Ok(obj) } } -/// Utility struct to return the result of the [`get_slice_range`] function. -#[derive(Debug, Clone, Copy)] -struct SliceRange { - start: u64, - length: u64, -} - -/// Gets the slice copy range from the original length, the relative start and the end. -fn get_slice_range( - len: u64, - relative_start: &JsValue, - end: &JsValue, - context: &mut Context, -) -> JsResult { - // 5. Let len be O.[[ArrayBufferByteLength]]. - - // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - let relative_start = relative_start.to_integer_or_infinity(context)?; - - let first = match relative_start { - // 7. If relativeStart is -∞, let first be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), - // 9. Else, let first be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), - IntegerOrInfinity::PositiveInfinity => len, +/// Abstract operation [`GetArrayBufferMaxByteLengthOption ( options )`][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption +fn get_max_byte_len(options: &JsValue, context: &mut Context) -> JsResult> { + // 1. If options is not an Object, return empty. + let Some(options) = options.as_object() else { + return Ok(None); }; - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let r#final = if end.is_undefined() { - len - } else { - match end.to_integer_or_infinity(context)? { - // 11. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), - // 13. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), - IntegerOrInfinity::PositiveInfinity => len, - } - }; + // 2. Let maxByteLength be ? Get(options, "maxByteLength"). + let max_byte_len = options.get(js_string!("maxByteLength"), context)?; - // 14. Let newLen be max(final - first, 0). - let new_len = r#final.saturating_sub(first); + // 3. If maxByteLength is undefined, return empty. + if max_byte_len.is_undefined() { + return Ok(None); + } - Ok(SliceRange { - start: first, - length: new_len, - }) + // 4. Return ? ToIndex(maxByteLength). + max_byte_len.to_index(context).map(Some) } /// `CreateByteDataBlock ( size )` abstract operation. @@ -558,25 +760,37 @@ fn get_slice_range( /// integer). For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock -pub(crate) fn create_byte_data_block(size: u64, context: &mut Context) -> JsResult> { - if size > context.host_hooks().max_buffer_size(context) { +pub(crate) fn create_byte_data_block( + size: u64, + max_buffer_size: Option, + context: &mut Context, +) -> JsResult> { + let alloc_size = max_buffer_size.unwrap_or(size); + + assert!(size <= alloc_size); + + if alloc_size > context.host_hooks().max_buffer_size(context) { return Err(JsNativeError::range() .with_message( "cannot allocate a buffer that exceeds the maximum buffer size".to_string(), ) .into()); } + // 1. Let db be a new Data Block value consisting of size bytes. If it is impossible to // create such a Data Block, throw a RangeError exception. - let size = size.try_into().map_err(|e| { + let alloc_size = alloc_size.try_into().map_err(|e| { JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; let mut data_block = Vec::new(); - data_block.try_reserve(size).map_err(|e| { + data_block.try_reserve_exact(alloc_size).map_err(|e| { JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}")) })?; + // since size <= alloc_size, then `size` must also fit inside a `usize`. + let size = size as usize; + // 2. Set all of the bytes of db to 0. data_block.resize(size, 0); diff --git a/core/engine/src/builtins/array_buffer/shared.rs b/core/engine/src/builtins/array_buffer/shared.rs index f4fcea1070..790a15eda0 100644 --- a/core/engine/src/builtins/array_buffer/shared.rs +++ b/core/engine/src/builtins/array_buffer/shared.rs @@ -1,15 +1,18 @@ #![allow(unstable_name_collisions)] -use std::{alloc, sync::Arc}; +use std::{ + alloc, + sync::{atomic::Ordering, Arc}, +}; use boa_profiler::Profiler; -use portable_atomic::AtomicU8; +use portable_atomic::{AtomicU8, AtomicUsize}; use boa_gc::{Finalize, Trace}; use sptr::Strict; use crate::{ - builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, + builtins::{Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, object::internal_methods::get_prototype_from_constructor, @@ -19,7 +22,7 @@ use crate::{ Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use super::{get_slice_range, utils::copy_shared_to_shared, SliceRange}; +use super::{get_max_byte_len, utils::copy_shared_to_shared}; /// The internal representation of a `SharedArrayBuffer` object. /// @@ -27,10 +30,23 @@ use super::{get_slice_range, utils::copy_shared_to_shared, SliceRange}; /// running different JS code at the same time. #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct SharedArrayBuffer { - /// The `[[ArrayBufferData]]` internal slot. // Shared buffers cannot be detached. #[unsafe_ignore_trace] - data: Arc>, + data: Arc, +} + +#[derive(Debug, Default)] +struct Inner { + // Technically we should have an `[[ArrayBufferData]]` internal slot, + // `[[ArrayBufferByteLengthData]]` and `[[ArrayBufferMaxByteLength]]` slots for growable arrays + // or `[[ArrayBufferByteLength]]` for fixed arrays, but we can save some work + // by just using this representation instead. + // + // The maximum buffer length is represented by `buffer.len()`, and `current_len` has the current + // buffer length, or `None` if this is a fixed buffer; in this case, `buffer.len()` will be + // the true length of the buffer. + buffer: Box<[AtomicU8]>, + current_len: Option, } impl SharedArrayBuffer { @@ -41,14 +57,33 @@ impl SharedArrayBuffer { data: Arc::default(), } } + /// Gets the length of this `SharedArrayBuffer`. - pub(crate) fn len(&self) -> usize { - self.data.len() + pub(crate) fn len(&self, ordering: Ordering) -> usize { + self.data + .current_len + .as_ref() + .map_or_else(|| self.data.buffer.len(), |len| len.load(ordering)) } /// Gets the inner bytes of this `SharedArrayBuffer`. - pub(crate) fn data(&self) -> &[AtomicU8] { - &self.data + pub(crate) fn bytes(&self, ordering: Ordering) -> &[AtomicU8] { + &self.data.buffer[..self.len(ordering)] + } + + /// Gets the inner data of the buffer without accessing the current atomic length. + #[track_caller] + pub(crate) fn bytes_with_len(&self, len: usize) -> &[AtomicU8] { + &self.data.buffer[..len] + } + + /// Gets a pointer to the internal shared buffer. + pub(crate) fn as_ptr(&self) -> *const AtomicU8 { + (*self.data.buffer).as_ptr() + } + + pub(crate) fn is_fixed_len(&self) -> bool { + self.data.current_len.is_none() } } @@ -66,20 +101,41 @@ impl IntrinsicObject for SharedArrayBuffer { .name(js_string!("get byteLength")) .build(); + let get_growable = BuiltInBuilder::callable(realm, Self::get_growable) + .name(js_string!("get growable")) + .build(); + + let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length) + .name(js_string!("get maxByteLength")) + .build(); + BuiltInBuilder::from_standard_constructor::(realm) + .static_accessor( + JsSymbol::species(), + Some(get_species), + None, + Attribute::CONFIGURABLE, + ) .accessor( js_string!("byteLength"), Some(get_byte_length), None, flag_attributes, ) - .static_accessor( - JsSymbol::species(), - Some(get_species), + .accessor( + js_string!("growable"), + Some(get_growable), None, - Attribute::CONFIGURABLE, + flag_attributes, + ) + .accessor( + js_string!("maxByteLength"), + Some(get_max_byte_length), + None, + flag_attributes, ) .method(Self::slice, js_string!("slice"), 2) + .method(Self::grow, js_string!("grow"), 1) .property( JsSymbol::to_string_tag(), Self::NAME, @@ -122,10 +178,13 @@ impl BuiltInConstructor for SharedArrayBuffer { } // 2. Let byteLength be ? ToIndex(length). - let byte_length = args.get_or_undefined(0).to_index(context)?; + let byte_len = args.get_or_undefined(0).to_index(context)?; + + // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). + let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; - // 3. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). - Ok(Self::allocate(new_target, byte_length, context)? + // 4. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). + Ok(Self::allocate(new_target, byte_len, max_byte_len, context)? .upcast() .into()) } @@ -166,12 +225,158 @@ impl SharedArrayBuffer { .with_message("SharedArrayBuffer.byteLength called with invalid value") })?; - // TODO: 4. Let length be ArrayBufferByteLength(O, seq-cst). + // 4. Let length be ArrayBufferByteLength(O, seq-cst). + let len = buf.bytes(Ordering::SeqCst).len() as u64; + // 5. Return 𝔽(length). - let len = buf.data().len() as u64; Ok(len.into()) } + /// [`get SharedArrayBuffer.prototype.growable`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.growable + pub(crate) fn get_growable( + this: &JsValue, + _args: &[JsValue], + _context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. + let buf = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("get SharedArrayBuffer.growable called with invalid `this`") + })?; + + // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false. + Ok(JsValue::from(!buf.is_fixed_len())) + } + + /// [`get SharedArrayBuffer.prototype.maxByteLength`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.maxbytelength + pub(crate) fn get_max_byte_length( + this: &JsValue, + _args: &[JsValue], + _context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. + let buf = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("get SharedArrayBuffer.maxByteLength called with invalid value") + })?; + + // 4. If IsFixedLengthArrayBuffer(O) is true, then + // a. Let length be O.[[ArrayBufferByteLength]]. + // 5. Else, + // a. Let length be O.[[ArrayBufferMaxByteLength]]. + // 6. Return 𝔽(length). + Ok(buf.data.buffer.len().into()) + } + + /// [`SharedArrayBuffer.prototype.grow ( newLength )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/sec-sharedarraybuffer.prototype.grow + pub(crate) fn grow( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let O be the this value. + // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. + let Some(buf) = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + else { + return Err(JsNativeError::typ() + .with_message("SharedArrayBuffer.grow called with non-object value") + .into()); + }; + + // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]). + if buf.borrow().data.is_fixed_len() { + return Err(JsNativeError::typ() + .with_message("SharedArrayBuffer.grow: cannot grow a fixed-length buffer") + .into()); + } + + // 4. Let newByteLength be ? ToIndex(newLength). + let new_byte_len = args.get_or_undefined(0).to_index(context)?; + + // TODO: 5. Let hostHandled be ? HostGrowSharedArrayBuffer(O, newByteLength). + // 6. If hostHandled is handled, return undefined. + // Used in engines to handle WASM buffers in a special way, but we don't + // have a WASM interpreter in place yet. + + // 7. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. + // 8. Let byteLengthBlock be O.[[ArrayBufferByteLengthData]]. + // 9. Let currentByteLengthRawBytes be GetRawBytesFromSharedBlock(byteLengthBlock, 0, biguint64, true, seq-cst). + // 10. Let newByteLengthRawBytes be NumericToRawBytes(biguint64, ℤ(newByteLength), isLittleEndian). + + let buf = buf.borrow(); + let buf = &buf.data; + + // d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. + // Extracting this condition outside the CAS since throwing early doesn't affect the correct + // behaviour of the loop. + if new_byte_len > buf.data.buffer.len() as u64 { + return Err(JsNativeError::range() + .with_message( + "SharedArrayBuffer.grow: new length cannot be bigger than `maxByteLength`", + ) + .into()); + } + let new_byte_len = new_byte_len as usize; + + // If we used let-else above to avoid the expect, we would carry a borrow through the `to_index` + // call, which could mutably borrow. Another alternative would be to clone the whole + // `SharedArrayBuffer`, but it's better to avoid contention with the counter in the `Arc` pointer. + let atomic_len = buf + .data + .current_len + .as_ref() + .expect("already checked that the buffer is not fixed-length"); + + // 11. Repeat, + // a. NOTE: This is a compare-and-exchange loop to ensure that parallel, racing grows of the same buffer are + // totally ordered, are not lost, and do not silently do nothing. The loop exits if it was able to attempt + // to grow uncontended. + // b. Let currentByteLength be ℝ(RawBytesToNumeric(biguint64, currentByteLengthRawBytes, isLittleEndian)). + // c. If newByteLength = currentByteLength, return undefined. + // d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a + // RangeError exception. + // e. Let byteLengthDelta be newByteLength - currentByteLength. + // f. If it is impossible to create a new Shared Data Block value consisting of byteLengthDelta bytes, throw + // a RangeError exception. + // g. NOTE: No new Shared Data Block is constructed and used here. The observable behaviour of growable + // SharedArrayBuffers is specified by allocating a max-sized Shared Data Block at construction time, and + // this step captures the requirement that implementations that run out of memory must throw a RangeError. + // h. Let readByteLengthRawBytes be AtomicCompareExchangeInSharedBlock(byteLengthBlock, 0, 8, + // currentByteLengthRawBytes, newByteLengthRawBytes). + // i. If ByteListEqual(readByteLengthRawBytes, currentByteLengthRawBytes) is true, return undefined. + // j. Set currentByteLengthRawBytes to readByteLengthRawBytes. + + // We require SEQ-CST operations because readers of the buffer also use SEQ-CST operations. + atomic_len + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev_byte_len| { + (prev_byte_len <= new_byte_len).then_some(new_byte_len) + }) + .map_err(|_| { + JsNativeError::range() + .with_message("SharedArrayBuffer.grow: failed to grow buffer to new length") + })?; + + Ok(JsValue::undefined()) + } + /// `SharedArrayBuffer.prototype.slice ( start, end )` /// /// More information: @@ -181,32 +386,45 @@ impl SharedArrayBuffer { fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") - })?; - // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. - let buf = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") - })?; - - let SliceRange { - start: first, - length: new_len, - } = get_slice_range( - buf.len() as u64, - args.get_or_undefined(0), - args.get_or_undefined(1), - context, - )?; + let buf = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("SharedArrayBuffer.slice called with invalid `this` value") + })?; + + // 4. Let len be ArrayBufferByteLength(O, seq-cst). + let len = buf.borrow().data.len(Ordering::SeqCst); + + // 5. Let relativeStart be ? ToIntegerOrInfinity(start). + // 6. If relativeStart = -∞, let first be 0. + // 7. Else if relativeStart < 0, let first be max(len + relativeStart, 0). + // 8. Else, let first be min(relativeStart, len). + let first = Array::get_relative_start(context, args.get_or_undefined(0), len as u64)?; + + // 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + // 10. If relativeEnd = -∞, let final be 0. + // 11. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + // 12. Else, let final be min(relativeEnd, len). + let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len as u64)?; + + // 13. Let newLen be max(final - first, 0). + let new_len = final_.saturating_sub(first); // 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%). - let ctor = obj.species_constructor(StandardConstructors::shared_array_buffer, context)?; + let ctor = buf + .clone() + .upcast() + .species_constructor(StandardConstructors::shared_array_buffer, context)?; // 15. Let new be ? Construct(ctor, « 𝔽(newLen) »). let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; { + let buf = buf.borrow(); + let buf = &buf.data; // 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). // 17. If IsSharedArrayBuffer(new) is false, throw a TypeError exception. let new = new.downcast_ref::().ok_or_else(|| { @@ -215,33 +433,37 @@ impl SharedArrayBuffer { })?; // 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception. - if std::ptr::eq(buf.data().as_ptr(), new.data().as_ptr()) { + if std::ptr::eq(buf.as_ptr(), new.as_ptr()) { return Err(JsNativeError::typ() - .with_message("cannot reuse the same `SharedArrayBuffer` for a slice operation") + .with_message("cannot reuse the same SharedArrayBuffer for a slice operation") .into()); } - // TODO: 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception. - if (new.len() as u64) < new_len { + // 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception. + if (new.len(Ordering::SeqCst) as u64) < new_len { return Err(JsNativeError::typ() - .with_message("invalid size of constructed shared array") + .with_message("invalid size of constructed SharedArrayBuffer") .into()); } + let first = first as usize; + let new_len = new_len as usize; + // 20. Let fromBuf be O.[[ArrayBufferData]]. - let from_buf = buf.data(); + let from_buf = &buf.bytes_with_len(len)[first..]; // 21. Let toBuf be new.[[ArrayBufferData]]. - let to_buf = new.data(); + let to_buf = new; - // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - let first = first as usize; - let new_len = new_len as usize; + // Sanity check to ensure there is enough space inside `from_buf` for + // `new_len` elements. + debug_assert!(from_buf.len() >= new_len); + // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). // SAFETY: `get_slice_range` will always return indices that are in-bounds. // This also means that the newly created buffer will have at least `new_len` elements // to write to. - unsafe { copy_shared_to_shared(&from_buf[first..], to_buf, new_len) } + unsafe { copy_shared_to_shared(from_buf.as_ptr(), to_buf.as_ptr(), new_len) } } // 23. Return new. @@ -256,10 +478,10 @@ impl SharedArrayBuffer { /// [spec]: https://tc39.es/ecma262/#sec-allocatesharedarraybuffer pub(crate) fn allocate( constructor: &JsValue, - byte_length: u64, + byte_len: u64, + max_byte_len: Option, context: &mut Context, ) -> JsResult> { - // TODO: // 1. Let slots be « [[ArrayBufferData]] ». // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer // be true; otherwise let allocatingGrowableBuffer be false. @@ -268,6 +490,13 @@ impl SharedArrayBuffer { // b. Append [[ArrayBufferByteLengthData]] and [[ArrayBufferMaxByteLength]] to slots. // 4. Else, // a. Append [[ArrayBufferByteLength]] to slots. + if let Some(max_byte_len) = max_byte_len { + if byte_len > max_byte_len { + return Err(JsNativeError::range() + .with_message("`length` cannot be bigger than `maxByteLength`") + .into()); + } + } // 5. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", slots). let prototype = get_prototype_from_constructor( @@ -276,24 +505,36 @@ impl SharedArrayBuffer { context, )?; - // TODO: 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength; - // otherwise let allocLength be byteLength. + // 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength; + // otherwise let allocLength be byteLength. + let alloc_len = max_byte_len.unwrap_or(byte_len); // 7. Let block be ? CreateSharedByteDataBlock(allocLength). // 8. Set obj.[[ArrayBufferData]] to block. - let data = create_shared_byte_data_block(byte_length, context)?; + let block = create_shared_byte_data_block(alloc_len, context)?; - // TODO: // 9. If allocatingGrowableBuffer is true, then - // a. Assert: byteLength ≤ maxByteLength. - // b. Let byteLengthBlock be ? CreateSharedByteDataBlock(8). - // c. Perform SetValueInBuffer(byteLengthBlock, 0, biguint64, ℤ(byteLength), true, seq-cst). - // d. Set obj.[[ArrayBufferByteLengthData]] to byteLengthBlock. - // e. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. + // `byte_len` must fit inside an `usize` thanks to the checks inside + // `create_shared_byte_data_block`. + // a. Assert: byteLength ≤ maxByteLength. + // b. Let byteLengthBlock be ? CreateSharedByteDataBlock(8). + // c. Perform SetValueInBuffer(byteLengthBlock, 0, biguint64, ℤ(byteLength), true, seq-cst). + // d. Set obj.[[ArrayBufferByteLengthData]] to byteLengthBlock. + // e. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. + let current_len = max_byte_len.map(|_| AtomicUsize::new(byte_len as usize)); // 10. Else, // a. Set obj.[[ArrayBufferByteLength]] to byteLength. - let obj = JsObject::new(context.root_shape(), prototype, Self { data }); + let obj = JsObject::new( + context.root_shape(), + prototype, + Self { + data: Arc::new(Inner { + buffer: block, + current_len, + }), + }, + ); // 11. Return obj. Ok(obj) @@ -310,7 +551,7 @@ impl SharedArrayBuffer { pub(crate) fn create_shared_byte_data_block( size: u64, context: &mut Context, -) -> JsResult>> { +) -> JsResult> { if size > context.host_hooks().max_buffer_size(context) { return Err(JsNativeError::range() .with_message( @@ -327,7 +568,7 @@ pub(crate) fn create_shared_byte_data_block( if size == 0 { // Must ensure we don't allocate a zero-sized buffer. - return Ok(Arc::new(Box::new([]))); + return Ok(Box::default()); } // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. @@ -371,5 +612,5 @@ pub(crate) fn create_shared_byte_data_block( assert_eq!(buffer.as_ptr().addr() % std::mem::align_of::(), 0); // 3. Return db. - Ok(Arc::new(buffer)) + Ok(buffer) } diff --git a/core/engine/src/builtins/array_buffer/tests.rs b/core/engine/src/builtins/array_buffer/tests.rs index 5581593b25..604bf6140f 100644 --- a/core/engine/src/builtins/array_buffer/tests.rs +++ b/core/engine/src/builtins/array_buffer/tests.rs @@ -4,10 +4,10 @@ use crate::Context; fn create_byte_data_block() { let context = &mut Context::default(); // Sunny day - assert!(super::create_byte_data_block(100, context).is_ok()); + assert!(super::create_byte_data_block(100, None, context).is_ok()); // Rainy day - assert!(super::create_byte_data_block(u64::MAX, context).is_err()); + assert!(super::create_byte_data_block(u64::MAX, None, context).is_err()); } #[test] diff --git a/core/engine/src/builtins/array_buffer/utils.rs b/core/engine/src/builtins/array_buffer/utils.rs index 31d1263152..784469a7c0 100644 --- a/core/engine/src/builtins/array_buffer/utils.rs +++ b/core/engine/src/builtins/array_buffer/utils.rs @@ -1,6 +1,6 @@ #![allow(unstable_name_collisions)] -use std::{ptr, slice::SliceIndex, sync::atomic}; +use std::{ptr, slice::SliceIndex, sync::atomic::Ordering}; use portable_atomic::AtomicU8; @@ -11,6 +11,18 @@ use crate::{ use super::ArrayBuffer; +#[derive(Clone, Copy)] +pub(crate) enum BytesConstPtr { + Bytes(*const u8), + AtomicBytes(*const AtomicU8), +} + +#[derive(Clone, Copy)] +pub(crate) enum BytesMutPtr { + Bytes(*mut u8), + AtomicBytes(*const AtomicU8), +} + #[derive(Debug, Clone, Copy)] pub(crate) enum SliceRef<'a> { Slice(&'a [u8]), @@ -49,6 +61,14 @@ impl SliceRef<'_> { } } + /// Gets a pointer to the underlying slice. + pub(crate) fn as_ptr(&self) -> BytesConstPtr { + match self { + SliceRef::Slice(s) => BytesConstPtr::Bytes(s.as_ptr()), + SliceRef::AtomicSlice(s) => BytesConstPtr::AtomicBytes(s.as_ptr()), + } + } + /// [`GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`][spec] /// /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. @@ -62,9 +82,9 @@ impl SliceRef<'_> { pub(crate) unsafe fn get_value( &self, kind: TypedArrayKind, - order: atomic::Ordering, + order: Ordering, ) -> TypedArrayElement { - unsafe fn read_elem(buffer: SliceRef<'_>, order: atomic::Ordering) -> T { + unsafe fn read_elem(buffer: SliceRef<'_>, order: Ordering) -> T { // // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. @@ -116,10 +136,7 @@ impl SliceRef<'_> { } } - /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )` - /// - /// More information: - /// - [ECMAScript reference][spec] + /// [`CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer pub(crate) fn clone(&self, context: &mut Context) -> JsResult> { @@ -135,6 +152,7 @@ impl SliceRef<'_> { .constructor() .into(), self.len() as u64, + None, context, )?; @@ -145,14 +163,20 @@ impl SliceRef<'_> { let mut target_buffer = target_buffer.borrow_mut(); let target_block = target_buffer .data - .data_mut() + .bytes_mut() .expect("ArrayBuffer cannot be detached here"); // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). // SAFETY: Both buffers are of the same length, `buffer.len()`, which makes this operation // safe. - unsafe { memcpy(*self, SliceRefMut::Slice(target_block), self.len()) } + unsafe { + memcpy( + self.as_ptr(), + BytesMutPtr::Bytes(target_block.as_mut_ptr()), + self.len(), + ); + } } // 6. Return targetBuffer. @@ -180,7 +204,6 @@ 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(), @@ -213,6 +236,14 @@ impl SliceRefMut<'_> { } } + /// Gets a pointer to the underlying slice. + pub(crate) fn as_ptr(&mut self) -> BytesMutPtr { + match self { + Self::Slice(s) => BytesMutPtr::Bytes(s.as_mut_ptr()), + Self::AtomicSlice(s) => BytesMutPtr::AtomicBytes(s.as_ptr()), + } + } + /// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )` /// /// The start offset is determined by the input buffer instead of a `byteIndex` parameter. @@ -230,12 +261,8 @@ impl SliceRefMut<'_> { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer - pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: atomic::Ordering) { - unsafe fn write_elem( - buffer: SliceRefMut<'_>, - value: T, - order: atomic::Ordering, - ) { + pub(crate) unsafe fn set_value(&mut self, value: TypedArrayElement, order: Ordering) { + unsafe fn write_elem(buffer: SliceRefMut<'_>, value: T, order: Ordering) { // // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. @@ -309,15 +336,16 @@ impl<'a> From<&'a [AtomicU8]> for SliceRefMut<'a> { /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, /// respectively. -pub(super) unsafe fn copy_shared_to_shared(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { +pub(super) unsafe fn copy_shared_to_shared( + src: *const AtomicU8, + dest: *const AtomicU8, + count: usize, +) { // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. for i in 0..count { // SAFETY: The invariants of this operation are ensured by the caller of the function. unsafe { - dest.get_unchecked(i).store( - src.get_unchecked(i).load(atomic::Ordering::Relaxed), - atomic::Ordering::Relaxed, - ); + (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } } @@ -328,60 +356,48 @@ pub(super) unsafe fn copy_shared_to_shared(src: &[AtomicU8], dest: &[AtomicU8], /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, /// respectively. -unsafe fn copy_shared_to_shared_backwards(src: &[AtomicU8], dest: &[AtomicU8], count: usize) { +unsafe fn copy_shared_to_shared_backwards( + src: *const AtomicU8, + dest: *const AtomicU8, + count: usize, +) { for i in (0..count).rev() { // SAFETY: The invariants of this operation are ensured by the caller of the function. unsafe { - dest.get_unchecked(i).store( - src.get_unchecked(i).load(atomic::Ordering::Relaxed), - atomic::Ordering::Relaxed, - ); + (*dest.add(i)).store((*src.add(i)).load(Ordering::Relaxed), Ordering::Relaxed); } } } -/// Copies `count` bytes from the buffer `src` into the buffer `dest`, using the atomic ordering `order` -/// if any of the buffers are atomic. +/// Copies `count` bytes from the buffer `src` into the buffer `dest`, using the atomic ordering +/// `Ordering::Relaxed` if any of the buffers are atomic. /// /// # Safety /// /// - Both `src` and `dest` must have at least `count` bytes to read and write, respectively. /// - The region of memory referenced by `src` must not overlap with the region of memory -/// referenced by `dest`. This is guaranteed if either of them are slices -/// (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) { - #[cfg(debug_assertions)] - { - assert!(src.len() >= count); - assert!(dest.len() >= count); - let src_range = src.addr()..src.addr() + src.len(); - let dest_range = dest.addr()..dest.addr() + dest.len(); - assert!(!src_range.contains(&dest_range.start)); - assert!(!src_range.contains(&dest_range.end)); - } - +/// referenced by `dest`. +pub(crate) unsafe fn memcpy(src: BytesConstPtr, dest: BytesMutPtr, count: usize) { // TODO: this could be optimized with batches of writes using `u32/u64` stores instead. match (src, dest) { // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Slice(src), SliceRefMut::Slice(dest)) => unsafe { - ptr::copy_nonoverlapping(src.as_ptr(), dest.as_mut_ptr(), count); + (BytesConstPtr::Bytes(src), BytesMutPtr::Bytes(dest)) => unsafe { + ptr::copy_nonoverlapping(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::Slice(src), SliceRefMut::AtomicSlice(dest)) => unsafe { + (BytesConstPtr::Bytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe { for i in 0..count { - dest.get_unchecked(i) - .store(*src.get_unchecked(i), atomic::Ordering::Relaxed); + (*dest.add(i)).store(*src.add(i), Ordering::Relaxed); } }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::AtomicSlice(src), SliceRefMut::Slice(dest)) => unsafe { + (BytesConstPtr::AtomicBytes(src), BytesMutPtr::Bytes(dest)) => unsafe { for i in 0..count { - *dest.get_unchecked_mut(i) = src.get_unchecked(i).load(atomic::Ordering::Relaxed); + *dest.add(i) = (*src.add(i)).load(Ordering::Relaxed); } }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - (SliceRef::AtomicSlice(src), SliceRefMut::AtomicSlice(dest)) => unsafe { + (BytesConstPtr::AtomicBytes(src), BytesMutPtr::AtomicBytes(dest)) => unsafe { copy_shared_to_shared(src, dest, count); }, } @@ -391,28 +407,20 @@ pub(crate) unsafe fn memcpy(src: SliceRef<'_>, dest: SliceRefMut<'_>, count: usi /// /// # Safety /// -/// - `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) { - #[cfg(debug_assertions)] - { - assert!(from + count <= buffer.len()); - assert!(to + count <= buffer.len()); - } - - match buffer { +/// - `ptr` must be valid from the offset `ptr + from` for `count` reads of bytes. +/// - `ptr` must be valid from the offset `ptr + to` for `count` writes of bytes. +pub(crate) unsafe fn memmove(ptr: BytesMutPtr, from: usize, to: usize, count: usize) { + match ptr { // SAFETY: The invariants of this operation are ensured by the caller of the function. - SliceRefMut::Slice(buf) => unsafe { - let ptr = buf.as_mut_ptr(); - let src_ptr = ptr.add(from); - let dest_ptr = ptr.add(to); - ptr::copy(src_ptr, dest_ptr, count); + BytesMutPtr::Bytes(ptr) => unsafe { + let src = ptr.add(from); + let dest = ptr.add(to); + ptr::copy(src, dest, count); }, // SAFETY: The invariants of this operation are ensured by the caller of the function. - SliceRefMut::AtomicSlice(buf) => unsafe { - let src = buf.get_unchecked(from..); - let dest = buf.get_unchecked(to..); - + BytesMutPtr::AtomicBytes(ptr) => unsafe { + let src = ptr.add(from); + let dest = ptr.add(to); // Let's draw a simple array. // // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@ -453,7 +461,7 @@ pub(crate) unsafe fn memmove(buffer: SliceRefMut<'_>, from: usize, to: usize, co // | 0 | 1 | 0 | 1 | 2 | 3 | 6 | 7 | 8 | // ^ ^ // from to - if from < to && to < from + count { + if src < dest { copy_shared_to_shared_backwards(src, dest, count); } else { copy_shared_to_shared(src, dest, count); diff --git a/core/engine/src/builtins/atomics/futex.rs b/core/engine/src/builtins/atomics/futex.rs index f5347ada95..b265a6aa68 100644 --- a/core/engine/src/builtins/atomics/futex.rs +++ b/core/engine/src/builtins/atomics/futex.rs @@ -273,6 +273,7 @@ pub(super) enum AtomicsWaitResult { // our implementation guarantees that `SharedArrayBuffer` is always aligned to `u64` at minimum. pub(super) unsafe fn wait( buffer: &SharedArrayBuffer, + buf_len: usize, offset: usize, check: E, timeout: Option, @@ -287,7 +288,7 @@ pub(super) unsafe fn wait( let time_info = timeout.map(|timeout| (Instant::now(), timeout)); - let buffer = &buffer.data()[offset..]; + let buffer = &buffer.bytes_with_len(buf_len)[offset..]; // 13. Let elementType be TypedArrayElementType(typedArray). // 14. Let w be GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst). @@ -380,7 +381,7 @@ pub(super) unsafe fn wait( /// Notifies at most `count` agents waiting on the memory address pointed to by `buffer[offset..]`. pub(super) fn notify(buffer: &SharedArrayBuffer, offset: usize, count: u64) -> JsResult { - let addr = buffer.data()[offset..].as_ptr().addr(); + let addr = buffer.as_ptr().addr() + offset; // 7. Let WL be GetWaiterList(block, indexedPosition). // 8. Perform EnterCriticalSection(WL). diff --git a/core/engine/src/builtins/atomics/mod.rs b/core/engine/src/builtins/atomics/mod.rs index 9556d67281..bca4509562 100644 --- a/core/engine/src/builtins/atomics/mod.rs +++ b/core/engine/src/builtins/atomics/mod.rs @@ -20,11 +20,11 @@ use crate::{ sys::time::Duration, value::IntegerOrInfinity, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, }; -use boa_gc::GcRef; + use boa_profiler::Profiler; use super::{ - array_buffer::BufferRef, + array_buffer::{BufferObject, BufferRef}, typed_array::{Atomic, ContentType, Element, TypedArray, TypedArrayElement, TypedArrayKind}, BuiltInBuilder, IntrinsicObject, }; @@ -75,19 +75,35 @@ macro_rules! atomic_op { let index = args.get_or_undefined(1); let value = args.get_or_undefined(2); - let ii = validate_integer_typed_array(array, false)?; - let pos = validate_atomic_access(&ii, index, context)?; - let value = ii.kind().get_element(value, context)?; + // AtomicReadModifyWrite ( typedArray, index, value, op ) + // + + // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray). + let (ta, buf_len) = validate_integer_typed_array(array, false)?; + + // 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index). + let access = validate_atomic_access(&ta, buf_len, index, context)?; - // revalidate - let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); - let Some(mut data) = buffer.data_mut() else { + // 3. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value). + // 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). + // 7. Let elementType be TypedArrayElementType(typedArray). + let value = access.kind.get_element(value, context)?; + + // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + // 6. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call + // to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could + // cause the buffer to become detached. + let ta = ta.borrow(); + let ta = &ta.data; + let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); + let Some(mut data) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() - .with_message("cannot execute atomic operation in detached buffer") - .into()); + .with_message("cannot execute atomic operation in detached buffer") + .into()); }; - let data = data.subslice_mut(pos..); + let data = data.subslice_mut(access.byte_offset..); + // 8. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. let value: TypedArrayElement = unsafe { @@ -161,24 +177,26 @@ impl Atomics { let index = args.get_or_undefined(1); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). - let ii = validate_integer_typed_array(array, false)?; - let pos = validate_atomic_access(&ii, index, context)?; + let (ta, buf_len) = validate_integer_typed_array(array, false)?; + let access = validate_atomic_access(&ta, buf_len, index, context)?; // 2. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let buffer = ii.viewed_array_buffer().as_buffer(); - let Some(data) = buffer.data() else { + let ta = ta.borrow(); + let ta = &ta.data; + let buffer = ta.viewed_array_buffer().as_buffer(); + let Some(data) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; - let data = data.subslice(pos..); + let data = data.subslice(access.byte_offset..); // 3. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 4. Let elementType be TypedArrayElementType(typedArray). // 5. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, seq-cst). // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. - let value = unsafe { data.get_value(ii.kind(), Ordering::SeqCst) }; + let value = unsafe { data.get_value(access.kind, Ordering::SeqCst) }; Ok(value.into()) } @@ -192,12 +210,12 @@ impl Atomics { let value = args.get_or_undefined(2); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). - let ii = validate_integer_typed_array(array, false)?; - let pos = validate_atomic_access(&ii, index, context)?; + let (ta, buf_len) = validate_integer_typed_array(array, false)?; + let access = validate_atomic_access(&ta, buf_len, index, context)?; // bit of a hack to preserve the converted value // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value). - let converted: JsValue = if ii.kind().content_type() == ContentType::BigInt { + let converted: JsValue = if access.kind.content_type() == ContentType::BigInt { value.to_bigint(context)?.into() } else { // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). @@ -208,16 +226,18 @@ impl Atomics { } .into() }; - let value = ii.kind().get_element(&converted, context)?; + let value = access.kind.get_element(&converted, context)?; // 4. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); - let Some(mut buffer) = buffer.data_mut() else { + let ta = ta.borrow(); + let ta = &ta.data; + let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); + let Some(mut buffer) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; - let mut data = buffer.subslice_mut(pos..); + let mut data = buffer.subslice_mut(access.byte_offset..); // 5. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 6. Let elementType be TypedArrayElementType(typedArray). @@ -244,9 +264,8 @@ impl Atomics { // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index). // 2. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 3. Let block be buffer.[[ArrayBufferData]]. - let ii = validate_integer_typed_array(array, false)?; - let pos = validate_atomic_access(&ii, index, context)?; - let typed_array_kind = ii.kind(); + let (ta, buf_len) = validate_integer_typed_array(array, false)?; + let access = validate_atomic_access(&ta, buf_len, index, context)?; // 4. If typedArray.[[ContentType]] is bigint, then // a. Let expected be ? ToBigInt(expectedValue). @@ -254,19 +273,19 @@ impl Atomics { // 5. Else, // a. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)). // b. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)). - let exp = typed_array_kind.get_element(expected, context)?.to_bytes(); - let rep = typed_array_kind - .get_element(replacement, context)? - .to_bytes(); + let exp = access.kind.get_element(expected, context)?.to_bits(); + let rep = access.kind.get_element(replacement, context)?.to_bits(); // 6. Perform ? RevalidateAtomicAccess(typedArray, indexedPosition). - let mut buffer = ii.viewed_array_buffer().as_buffer_mut(); - let Some(mut data) = buffer.data_mut() else { + let ta = ta.borrow(); + let ta = &ta.data; + let mut buffer = ta.viewed_array_buffer().as_buffer_mut(); + let Some(mut buffer) = buffer.bytes_with_len(buf_len) else { return Err(JsNativeError::typ() .with_message("cannot execute atomic operation in detached buffer") .into()); }; - let data = data.subslice_mut(pos..); + let data = buffer.subslice_mut(access.byte_offset..); // 7. Let elementType be TypedArrayElementType(typedArray). // 8. Let elementSize be TypedArrayElementSize(typedArray). @@ -280,10 +299,11 @@ impl Atomics { // b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then // i. Store the individual bytes of replacementBytes into block, starting at block[indexedPosition]. // 14. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian). + // SAFETY: The integer indexed object guarantees that the buffer is aligned. // The call to `validate_atomic_access` guarantees that the index is in-bounds. let value: TypedArrayElement = unsafe { - match typed_array_kind { + match access.kind { TypedArrayKind::Int8 => i8::read_mut(data) .compare_exchange(exp as i8, rep as i8, Ordering::SeqCst) .into(), @@ -321,23 +341,6 @@ impl Atomics { // =========== Atomics.ops start =========== - // Most of the operations here follow the same list of steps: - // - // AtomicReadModifyWrite ( typedArray, index, value, op ) - // - // - // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray). - // 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index). - // 3. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value). - // 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). - // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - // 6. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached. - // 7. Let elementType be TypedArrayElementType(typedArray). - // 8. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op). - // - // However, our impementation differs significantly from this, which is why these steps are - // just here for documentation purposes. - atomic_op! { /// [`Atomics.add ( typedArray, index, value )`][spec] /// @@ -383,28 +386,32 @@ impl Atomics { /// [`Atomics.wait ( typedArray, index, value, timeout )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-atomics.wait + // TODO: rewrite this to support Atomics.waitAsync fn wait(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let array = args.get_or_undefined(0); let index = args.get_or_undefined(1); let value = args.get_or_undefined(2); let timeout = args.get_or_undefined(3); - // 1. Let buffer be ? ValidateIntegerTypedArray(typedArray, true). - let ii = validate_integer_typed_array(array, true)?; - let buffer = ii.viewed_array_buffer().as_buffer(); + // 1. Let taRecord be ? ValidateIntegerTypedArray(typedArray, true). + let (ta, buf_len) = validate_integer_typed_array(array, true)?; + // 2. Let buffer be taRecord.[[Object]].[[ViewedArrayBuffer]]. // 2. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception. - let BufferRef::SharedBuffer(buffer) = buffer else { - return Err(JsNativeError::typ() - .with_message("cannot use `ArrayBuffer` for an atomic wait") - .into()); + let buffer = match ta.borrow().data.viewed_array_buffer() { + BufferObject::SharedBuffer(buf) => buf.clone(), + BufferObject::Buffer(_) => { + return Err(JsNativeError::typ() + .with_message("cannot use `ArrayBuffer` for an atomic wait") + .into()) + } }; // 3. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index). - let offset = validate_atomic_access(&ii, index, context)?; + let access = validate_atomic_access(&ta, buf_len, index, context)?; // spec expects the evaluation of this first, then the timeout. - let value = if ii.kind() == TypedArrayKind::BigInt64 { + let value = if access.kind == TypedArrayKind::BigInt64 { // 4. If typedArray.[[TypedArrayName]] is "BigInt64Array", let v be ? ToBigInt64(value). value.to_big_int64(context)? } else { @@ -435,11 +442,23 @@ 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)? + if access.kind == TypedArrayKind::BigInt64 { + futex::wait( + &buffer.borrow().data, + buf_len, + access.byte_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.borrow().data, + buf_len, + access.byte_offset, + value as i32, + timeout, + )? } }; @@ -460,8 +479,8 @@ impl Atomics { let count = args.get_or_undefined(2); // 1. Let indexedPosition be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index, true). - let ii = validate_integer_typed_array(array, true)?; - let offset = validate_atomic_access(&ii, index, context)?; + let (ta, buf_len) = validate_integer_typed_array(array, true)?; + let access = validate_atomic_access(&ta, buf_len, index, context)?; // 2. If count is undefined, then let count = if count.is_undefined() { @@ -481,11 +500,12 @@ impl Atomics { // 4. Let buffer be typedArray.[[ViewedArrayBuffer]]. // 5. Let block be buffer.[[ArrayBufferData]]. // 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽. - let BufferRef::SharedBuffer(shared) = ii.viewed_array_buffer().as_buffer() else { + let ta = ta.borrow(); + let BufferRef::SharedBuffer(shared) = ta.data.viewed_array_buffer().as_buffer() else { return Ok(0.into()); }; - let count = futex::notify(&shared, offset, count)?; + let count = futex::notify(&shared, access.byte_offset, count)?; // 12. Let n be the number of elements in S. // 13. Return 𝔽(n). @@ -493,63 +513,74 @@ impl Atomics { } } -/// [`ValidateIntegerTypedArray ( typedArray [ , waitable ] )`][spec] +/// [`ValidateIntegerTypedArray ( typedArray, waitable )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-validateintegertypedarray fn validate_integer_typed_array( array: &JsValue, waitable: bool, -) -> JsResult> { - // 1. If waitable is not present, set waitable to false. - // 2. Perform ? ValidateTypedArray(typedArray). - let ii = array - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| JsNativeError::typ().with_message("value is not a typed array object"))?; - if ii.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let buffer be typedArray.[[ViewedArrayBuffer]]. - - if waitable { - // 4. If waitable is true, then - // a. If typedArray.[[TypedArrayName]] is neither "Int32Array" nor "BigInt64Array", throw a TypeError exception. - if ![TypedArrayKind::Int32, TypedArrayKind::BigInt64].contains(&ii.kind()) { - return Err(JsNativeError::typ() - .with_message("can only atomically wait using Int32 or BigInt64 arrays") - .into()); - } - } else { - // 5. Else, - // a. Let type be TypedArrayElementType(typedArray). - // b. If IsUnclampedIntegerElementType(type) is false and IsBigIntElementType(type) is - // false, throw a TypeError exception. - if !ii.kind().supports_atomic_ops() { - return Err(JsNativeError::typ() - .with_message( - "platform doesn't support atomic operations on the provided `TypedArray`", - ) - .into()); +) -> JsResult<(JsObject, usize)> { + // 1. Let taRecord be ? ValidateTypedArray(typedArray, unordered). + // 2. NOTE: Bounds checking is not a synchronizing operation when typedArray's backing buffer is a growable SharedArrayBuffer. + let ta_record = TypedArray::validate(array, Ordering::Relaxed)?; + + { + let array = ta_record.0.borrow(); + + // 3. If waitable is true, then + if waitable { + // a. If typedArray.[[TypedArrayName]] is neither "Int32Array" nor "BigInt64Array", throw a TypeError exception. + if ![TypedArrayKind::Int32, TypedArrayKind::BigInt64].contains(&array.data.kind()) { + return Err(JsNativeError::typ() + .with_message("can only atomically wait using Int32 or BigInt64 arrays") + .into()); + } + } else { + // 4. Else, + // a. Let type be TypedArrayElementType(typedArray). + // b. If IsUnclampedIntegerElementType(type) is false and IsBigIntElementType(type) is false, throw a TypeError exception. + if !array.data.kind().supports_atomic_ops() { + return Err(JsNativeError::typ() + .with_message( + "platform doesn't support atomic operations on the provided `TypedArray`", + ) + .into()); + } } } - // 6. Return buffer. - Ok(ii) + // 5. Return taRecord. + Ok(ta_record) +} + +struct AtomicAccess { + byte_offset: usize, + kind: TypedArrayKind, } -/// [`ValidateAtomicAccess ( iieoRecord, requestIndex )`][spec] +/// [`ValidateAtomicAccess ( taRecord, requestIndex )`][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-validateatomicaccess fn validate_atomic_access( - array: &TypedArray, + array: &JsObject, + buf_len: usize, request_index: &JsValue, context: &mut Context, -) -> JsResult { - // 1. Let length be typedArray.[[ArrayLength]]. - let length = array.array_length(); +) -> JsResult { + // 5. Let typedArray be taRecord.[[Object]]. + let (length, kind, offset) = { + let array = array.borrow(); + let array = &array.data; + + // 1. Let length be typedArray.[[ArrayLength]]. + // 6. Let elementSize be TypedArrayElementSize(typedArray). + // 7. Let offset be typedArray.[[ByteOffset]]. + ( + array.array_length(buf_len), + array.kind(), + array.byte_offset(), + ) + }; // 2. Let accessIndex be ? ToIndex(requestIndex). let access_index = request_index.to_index(context)?; @@ -564,12 +595,10 @@ fn validate_atomic_access( .into()); } - // 5. Let elementSize be TypedArrayElementSize(typedArray). - let element_size = array.kind().element_size(); - - // 6. Let offset be typedArray.[[ByteOffset]]. - let offset = array.byte_offset(); - - // 7. Return (accessIndex × elementSize) + offset. - Ok(((access_index * element_size) + offset) as usize) + // 8. Return (accessIndex × elementSize) + offset. + let offset = ((access_index * kind.element_size()) + offset) as usize; + Ok(AtomicAccess { + byte_offset: offset, + kind, + }) } diff --git a/core/engine/src/builtins/dataview/mod.rs b/core/engine/src/builtins/dataview/mod.rs index 84a9861134..7274cc1fd9 100644 --- a/core/engine/src/builtins/dataview/mod.rs +++ b/core/engine/src/builtins/dataview/mod.rs @@ -7,7 +7,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-dataview-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView -use std::mem; +use std::{mem, sync::atomic::Ordering}; use crate::{ builtins::BuiltInObject, @@ -27,7 +27,7 @@ use bytemuck::{bytes_of, bytes_of_mut}; use super::{ array_buffer::{ - utils::{memcpy, SliceRef, SliceRefMut}, + utils::{memcpy, BytesConstPtr, BytesMutPtr}, BufferObject, }, typed_array::{self, TypedArrayElement}, @@ -38,10 +38,61 @@ use super::{ #[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct DataView { pub(crate) viewed_array_buffer: BufferObject, - pub(crate) byte_length: u64, + pub(crate) byte_length: Option, pub(crate) byte_offset: u64, } +impl DataView { + /// Abstract operation [`GetViewByteLength ( viewRecord )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-getviewbytelength + fn byte_length(&self, buf_byte_len: usize) -> u64 { + // 1. Assert: IsViewOutOfBounds(viewRecord) is false. + debug_assert!(!self.is_out_of_bounds(buf_byte_len)); + + // 2. Let view be viewRecord.[[Object]]. + // 3. If view.[[ByteLength]] is not auto, return view.[[ByteLength]]. + if let Some(byte_length) = self.byte_length { + return byte_length; + } + + // 4. Assert: IsFixedLengthArrayBuffer(view.[[ViewedArrayBuffer]]) is false. + + // 5. Let byteOffset be view.[[ByteOffset]]. + // 6. Let byteLength be viewRecord.[[CachedBufferByteLength]]. + // 7. Assert: byteLength is not detached. + // 8. Return byteLength - byteOffset. + buf_byte_len as u64 - self.byte_offset + } + + /// Abstract operation [`IsViewOutOfBounds ( viewRecord )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-isviewoutofbounds + fn is_out_of_bounds(&self, buf_byte_len: usize) -> bool { + let buf_byte_len = buf_byte_len as u64; + // 1. Let view be viewRecord.[[Object]]. + // 2. Let bufferByteLength be viewRecord.[[CachedBufferByteLength]]. + // 3. Assert: IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached. + // 4. If bufferByteLength is detached, return true. + // handled by the caller + + // 5. Let byteOffsetStart be view.[[ByteOffset]]. + + // 6. If view.[[ByteLength]] is auto, then + // a. Let byteOffsetEnd be bufferByteLength. + // 7. Else, + // a. Let byteOffsetEnd be byteOffsetStart + view.[[ByteLength]]. + let byte_offset_end = self + .byte_length + .map_or(buf_byte_len, |byte_length| byte_length + self.byte_offset); + + // 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true. + // 9. NOTE: 0-length DataViews are not considered out-of-bounds. + // 10. Return false. + self.byte_offset > buf_byte_len || byte_offset_end > buf_byte_len + } +} + impl IntrinsicObject for DataView { fn init(realm: &Realm) { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; @@ -120,7 +171,7 @@ impl BuiltInConstructor for DataView { const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::data_view; - /// `25.3.2.1 DataView ( buffer [ , byteOffset [ , byteLength ] ] )` + /// `DataView ( buffer [ , byteOffset [ , byteLength ] ] )` /// /// The `DataView` view provides a low-level interface for reading and writing multiple number /// types in a binary `ArrayBuffer`, without having to care about the platform's endianness. @@ -136,93 +187,124 @@ impl BuiltInConstructor for DataView { args: &[JsValue], context: &mut Context, ) -> JsResult { - let byte_length = args.get_or_undefined(2); - // 1. If NewTarget is undefined, throw a TypeError exception. if new_target.is_undefined() { return Err(JsNativeError::typ() - .with_message("new target is undefined") + .with_message("cannot call `DataView` constructor without `new`") .into()); } + let byte_len = args.get_or_undefined(2); // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). - let buffer_obj = args + let buffer = 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) = { - let buffer = buffer_obj.as_buffer(); + // 3. Let offset be ? ToIndex(byteOffset). + let offset = args.get_or_undefined(1).to_index(context)?; - // 3. Let offset be ? ToIndex(byteOffset). - let offset = args.get_or_undefined(1).to_index(context)?; + let (buf_byte_len, is_fixed_len) = { + let buffer = buffer.as_buffer(); // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - let Some(buffer) = buffer.data() else { + let Some(slice) = buffer.bytes(Ordering::SeqCst) else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); }; - // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. - let buffer_byte_length = buffer.len() as u64; + // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst). + let buf_len = slice.len() as u64; + // 6. If offset > bufferByteLength, throw a RangeError exception. - if offset > buffer_byte_length { + if offset > buf_len { return Err(JsNativeError::range() .with_message("Start offset is outside the bounds of the buffer") .into()); } - // 7. If byteLength is undefined, then - let view_byte_length = if byte_length.is_undefined() { - // a. Let viewByteLength be bufferByteLength - offset. - buffer_byte_length - offset - } else { - // 8.a. Let viewByteLength be ? ToIndex(byteLength). - let view_byte_length = byte_length.to_index(context)?; - // 8.b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. - if offset + view_byte_length > buffer_byte_length { - return Err(JsNativeError::range() - .with_message("Invalid data view length") - .into()); - } - - view_byte_length - }; - (offset, view_byte_length) + + // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer). + + (buf_len, buffer.is_fixed_len()) }; - // 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). + // 8. If byteLength is undefined, then + let view_byte_len = if byte_len.is_undefined() { + // a. If bufferIsFixedLength is true, then + // i. Let viewByteLength be bufferByteLength - offset. + // b. Else, + // i. Let viewByteLength be auto. + is_fixed_len.then_some(buf_byte_len - offset) + } else { + // 9. Else, + // a. Let viewByteLength be ? ToIndex(byteLength). + let byte_len = byte_len.to_index(context)?; + + // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. + if offset + byte_len > buf_byte_len { + return Err(JsNativeError::range() + .with_message("Invalid data view length") + .into()); + } + Some(byte_len) + }; + + // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", + // « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?; - // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer_obj.as_buffer().is_detached() { + // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst). + let Some(buf_byte_len) = buffer + .as_buffer() + .bytes(Ordering::SeqCst) + .map(|s| s.len() as u64) + else { return Err(JsNativeError::typ() .with_message("ArrayBuffer can't be detached") .into()); + }; + + // 13. If offset > bufferByteLength, throw a RangeError exception. + if offset > buf_byte_len { + return Err(JsNativeError::range() + .with_message("DataView offset outside of buffer array bounds") + .into()); + } + + // 14. If byteLength is not undefined, then + if let Some(view_byte_len) = view_byte_len.filter(|_| !byte_len.is_undefined()) { + // a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. + if offset + view_byte_len > buf_byte_len { + return Err(JsNativeError::range() + .with_message("DataView offset outside of buffer array bounds") + .into()); + } } let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, Self { - // 11. Set O.[[ViewedArrayBuffer]] to buffer. - viewed_array_buffer: buffer_obj, - // 12. Set O.[[ByteLength]] to viewByteLength. - byte_length: view_byte_length, - // 13. Set O.[[ByteOffset]] to offset. + // 15. Set O.[[ViewedArrayBuffer]] to buffer. + viewed_array_buffer: buffer, + // 16. Set O.[[ByteLength]] to viewByteLength. + byte_length: view_byte_len, + // 17. Set O.[[ByteOffset]] to offset. byte_offset: offset, }, ); - // 14. Return O. + // 18. Return O. Ok(obj.into()) } } impl DataView { - /// `25.3.4.1 get DataView.prototype.buffer` + /// `get DataView.prototype.buffer` /// /// The buffer accessor property represents the `ArrayBuffer` or `SharedArrayBuffer` referenced /// by the `DataView` at construction time. @@ -251,7 +333,7 @@ impl DataView { Ok(buffer.into()) } - /// `25.3.4.1 get DataView.prototype.byteLength` + /// `get DataView.prototype.byteLength` /// /// The `byteLength` accessor property represents the length (in bytes) of the dataview. /// @@ -268,26 +350,32 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. let view = this .as_object() .and_then(JsObject::downcast_ref::) .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]]. + + // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst). + // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. let buffer = view.viewed_array_buffer.as_buffer(); - // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer.is_detached() { + let Some(slice) = buffer + .bytes(Ordering::SeqCst) + .filter(|s| !view.is_out_of_bounds(s.len())) + else { return Err(JsNativeError::typ() - .with_message("ArrayBuffer is detached") + .with_message("view out of bounds for its inner buffer") .into()); - } - // 6. Let size be O.[[ByteLength]]. - let size = view.byte_length; + }; + + // 6. Let size be GetViewByteLength(viewRecord). + let size = view.byte_length(slice.len()); + // 7. Return 𝔽(size). Ok(size.into()) } - /// `25.3.4.1 get DataView.prototype.byteOffset` + /// `get DataView.prototype.byteOffset` /// /// The `byteOffset` accessor property represents the offset (in bytes) of this view from the /// start of its `ArrayBuffer` or `SharedArrayBuffer`. @@ -309,22 +397,28 @@ impl DataView { .as_object() .and_then(JsObject::downcast_ref::) .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 = view.viewed_array_buffer.as_buffer(); - // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if buffer.is_detached() { + // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst). + // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. + if buffer + .bytes(Ordering::SeqCst) + .filter(|b| !view.is_out_of_bounds(b.len())) + .is_none() + { return Err(JsNativeError::typ() - .with_message("Buffer is detached") + .with_message("data view is outside the bounds of its inner buffer") .into()); } + // 6. Let offset be O.[[ByteOffset]]. let offset = view.byte_offset; // 7. Return 𝔽(offset). Ok(offset.into()) } - /// `25.3.1.1 GetViewValue ( view, requestIndex, isLittleEndian, type )` + /// `GetViewValue ( view, requestIndex, isLittleEndian, type )` /// /// The abstract operation `GetViewValue` takes arguments view, requestIndex, `isLittleEndian`, /// and type. It is used by functions on `DataView` instances to retrieve values from the @@ -346,48 +440,56 @@ impl DataView { .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; + // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; - // 4. Set isLittleEndian to ! ToBoolean(isLittleEndian). + // 4. Set isLittleEndian to ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); - // 5. Let buffer be view.[[ViewedArrayBuffer]]. + // 6. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). + // 7. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer. + // 8. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. let buffer = view.viewed_array_buffer.as_buffer(); - - // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - let Some(data) = buffer.data() else { + let Some(data) = buffer + .bytes(Ordering::Relaxed) + .filter(|buf| !view.is_out_of_bounds(buf.len())) + else { return Err(JsNativeError::typ() - .with_message("ArrayBuffer is detached") + .with_message("view out of bounds for its inner buffer") .into()); }; - // 7. Let viewOffset be view.[[ByteOffset]]. + // 5. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; - // 8. Let viewSize be view.[[ByteLength]]. - let view_size = view.byte_length; + // 9. Let viewSize be GetViewByteLength(viewRecord). + let view_size = view.byte_length(data.len()); - // 9. Let elementSize be the Element Size value specified in Table 72 for Element Type type. + // 10. Let elementSize be the Element Size value specified in Table 71 for Element Type type. let element_size = mem::size_of::() as u64; - // 10. If getIndex + elementSize > viewSize, throw a RangeError exception. + // 11. If getIndex + elementSize > viewSize, throw a RangeError exception. if get_index + element_size > view_size { return Err(JsNativeError::range() .with_message("Offset is outside the bounds of the DataView") .into()); } - // 11. Let bufferIndex be getIndex + viewOffset. + // 12. Let bufferIndex be getIndex + viewOffset. let buffer_index = (get_index + view_offset) as usize; - // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian). + let src = data.subslice(buffer_index..); + + debug_assert!(src.len() >= mem::size_of::()); + + // 13. Return GetValueFromBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, false, unordered, isLittleEndian). // SAFETY: All previous checks ensure the element fits in the buffer. let value: TypedArrayElement = unsafe { let mut value = T::zeroed(); memcpy( - data.subslice(buffer_index..), - SliceRefMut::Slice(bytes_of_mut(&mut value)), + src.as_ptr(), + BytesMutPtr::Bytes(bytes_of_mut(&mut value).as_mut_ptr()), mem::size_of::(), ); @@ -402,7 +504,7 @@ impl DataView { Ok(value.into()) } - /// `25.3.4.5 DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] )` /// /// The `getBigInt64()` method gets a signed 64-bit integer (long long) at the specified byte /// offset from the start of the `DataView`. @@ -425,7 +527,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.6 DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` /// /// The `getBigUint64()` method gets an unsigned 64-bit integer (unsigned long long) at the /// specified byte offset from the start of the `DataView`. @@ -448,7 +550,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.7 DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] )` /// /// The `getFloat32()` method gets a signed 32-bit float (float) at the specified byte offset /// from the start of the `DataView`. @@ -471,7 +573,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.8 DataView.prototype.getFloat64 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getFloat64 ( byteOffset [ , littleEndian ] )` /// /// The `getFloat64()` method gets a signed 64-bit float (double) at the specified byte offset /// from the start of the `DataView`. @@ -494,7 +596,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.9 DataView.prototype.getInt8 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getInt8 ( byteOffset [ , littleEndian ] )` /// /// The `getInt8()` method gets a signed 8-bit integer (byte) at the specified byte offset /// from the start of the `DataView`. @@ -517,7 +619,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.10 DataView.prototype.getInt16 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getInt16 ( byteOffset [ , littleEndian ] )` /// /// The `getInt16()` method gets a signed 16-bit integer (short) at the specified byte offset /// from the start of the `DataView`. @@ -540,7 +642,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.11 DataView.prototype.getInt32 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getInt32 ( byteOffset [ , littleEndian ] )` /// /// The `getInt32()` method gets a signed 32-bit integer (long) at the specified byte offset /// from the start of the `DataView`. @@ -563,7 +665,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.12 DataView.prototype.getUint8 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getUint8 ( byteOffset [ , littleEndian ] )` /// /// The `getUint8()` method gets an unsigned 8-bit integer (unsigned byte) at the specified /// byte offset from the start of the `DataView`. @@ -586,7 +688,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.13 DataView.prototype.getUint16 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getUint16 ( byteOffset [ , littleEndian ] )` /// /// The `getUint16()` method gets an unsigned 16-bit integer (unsigned short) at the specified /// byte offset from the start of the `DataView`. @@ -609,7 +711,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.4.14 DataView.prototype.getUint32 ( byteOffset [ , littleEndian ] )` + /// `DataView.prototype.getUint32 ( byteOffset [ , littleEndian ] )` /// /// The `getUint32()` method gets an unsigned 32-bit integer (unsigned long) at the specified /// byte offset from the start of the `DataView`. @@ -632,7 +734,7 @@ impl DataView { Self::get_view_value::(this, byte_offset, is_little_endian, context) } - /// `25.3.1.1 SetViewValue ( view, requestIndex, isLittleEndian, type )` + /// `SetViewValue ( view, requestIndex, isLittleEndian, type )` /// /// The abstract operation `SetViewValue` takes arguments view, requestIndex, `isLittleEndian`, /// type, and value. It is used by functions on `DataView` instances to store values into the @@ -655,44 +757,55 @@ impl DataView { .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; + // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; - // 4. If ! IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). + // 4. If IsBigIntElementType(type) is true, let numberValue be ? ToBigInt(value). // 5. Otherwise, let numberValue be ? ToNumber(value). let value = T::from_js_value(value, context)?; - // 6. Set isLittleEndian to ! ToBoolean(isLittleEndian). + // 6. Set isLittleEndian to ToBoolean(isLittleEndian). let is_little_endian = is_little_endian.to_boolean(); - // 7. Let buffer be view.[[ViewedArrayBuffer]]. + + // 8. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). + // 9. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer. + // 10. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception. 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 { + let Some(mut data) = buffer + .bytes(Ordering::Relaxed) + .filter(|buf| !view.is_out_of_bounds(buf.len())) + else { return Err(JsNativeError::typ() - .with_message("ArrayBuffer is detached") + .with_message("view out of bounds for its inner buffer") .into()); }; - // 9. Let viewOffset be view.[[ByteOffset]]. + // 11. Let viewSize be GetViewByteLength(viewRecord). + let view_size = view.byte_length(data.len()); + + // 7. Let viewOffset be view.[[ByteOffset]]. let view_offset = view.byte_offset; - // 10. Let viewSize be view.[[ByteLength]]. - let view_size = view.byte_length; + // 12. Let elementSize be the Element Size value specified in Table 71 for Element Type type. + let elem_size = mem::size_of::(); - // 11. Let elementSize be the Element Size value specified in Table 72 for Element Type type. - // 12. If getIndex + elementSize > viewSize, throw a RangeError exception. - if get_index + mem::size_of::() as u64 > view_size { + // 13. If getIndex + elementSize > viewSize, throw a RangeError exception. + if get_index + elem_size as u64 > view_size { return Err(JsNativeError::range() .with_message("Offset is outside the bounds of DataView") .into()); } - // 13. Let bufferIndex be getIndex + viewOffset. + // 14. Let bufferIndex be getIndex + viewOffset. let buffer_index = (get_index + view_offset) as usize; - // 14. Return SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian). + let mut target = data.subslice_mut(buffer_index..); + + debug_assert!(target.len() >= mem::size_of::()); + // 15. Perform SetValueInBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, numberValue, false, unordered, isLittleEndian). // SAFETY: All previous checks ensure the element fits in the buffer. unsafe { let value = if is_little_endian { @@ -702,16 +815,17 @@ impl DataView { }; memcpy( - SliceRef::Slice(bytes_of(&value)), - data.subslice_mut(buffer_index..), + BytesConstPtr::Bytes(bytes_of(&value).as_ptr()), + target.as_ptr(), mem::size_of::(), ); } + // 16. Return undefined. Ok(JsValue::undefined()) } - /// `25.3.4.15 DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setBigInt64()` method stores a signed 64-bit integer (long long) value at the /// specified byte offset from the start of the `DataView`. @@ -735,7 +849,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.16 DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setBigUint64()` method stores an unsigned 64-bit integer (unsigned long long) value at /// the specified byte offset from the start of the `DataView`. @@ -759,7 +873,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.17 DataView.prototype.setFloat32 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setFloat32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setFloat32()` method stores a signed 32-bit float (float) value at the specified byte /// offset from the start of the `DataView`. @@ -783,7 +897,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.18 DataView.prototype.setFloat64 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setFloat64 ( byteOffset, value [ , littleEndian ] )` /// /// The `setFloat64()` method stores a signed 64-bit float (double) value at the specified byte /// offset from the start of the `DataView`. @@ -807,7 +921,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.19 DataView.prototype.setInt8 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setInt8 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt8()` method stores a signed 8-bit integer (byte) value at the specified byte /// offset from the start of the `DataView`. @@ -831,7 +945,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.20 DataView.prototype.setInt16 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setInt16 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt16()` method stores a signed 16-bit integer (short) value at the specified byte /// offset from the start of the `DataView`. @@ -855,7 +969,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.21 DataView.prototype.setInt32 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setInt32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setInt32()` method stores a signed 32-bit integer (long) value at the specified byte /// offset from the start of the `DataView`. @@ -879,7 +993,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.22 DataView.prototype.setUint8 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setUint8 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint8()` method stores an unsigned 8-bit integer (byte) value at the specified byte /// offset from the start of the `DataView`. @@ -903,7 +1017,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.23 DataView.prototype.setUint16 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setUint16 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint16()` method stores an unsigned 16-bit integer (unsigned short) value at the /// specified byte offset from the start of the `DataView`. @@ -927,7 +1041,7 @@ impl DataView { Self::set_view_value::(this, byte_offset, is_little_endian, value, context) } - /// `25.3.4.24 DataView.prototype.setUint32 ( byteOffset, value [ , littleEndian ] )` + /// `DataView.prototype.setUint32 ( byteOffset, value [ , littleEndian ] )` /// /// The `setUint32()` method stores an unsigned 32-bit integer (unsigned long) value at the /// specified byte offset from the start of the `DataView`. diff --git a/core/engine/src/builtins/typed_array/builtin.rs b/core/engine/src/builtins/typed_array/builtin.rs index 9679b92b9d..0ea69306cc 100644 --- a/core/engine/src/builtins/typed_array/builtin.rs +++ b/core/engine/src/builtins/typed_array/builtin.rs @@ -1,4 +1,7 @@ -use std::{cmp, sync::atomic}; +use std::{ + cmp::{self, min}, + sync::atomic::{self, Ordering}, +}; use boa_macros::utf16; use num_traits::Zero; @@ -167,7 +170,7 @@ impl BuiltInConstructor for BuiltinTypedArray { const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = StandardConstructors::typed_array; - /// `23.2.1.1 %TypedArray% ( )` + /// `%TypedArray% ( )` /// /// More information: /// - [ECMAScript reference][spec] @@ -182,7 +185,7 @@ impl BuiltInConstructor for BuiltinTypedArray { } impl BuiltinTypedArray { - /// `23.2.2.1 %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` + /// `%TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -229,7 +232,7 @@ impl BuiltinTypedArray { // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[values.len().into()], context)?; + let target_obj = Self::create(constructor, &[values.len().into()], context)?.upcast(); // d. Let k be 0. // e. Repeat, while k < len, @@ -265,7 +268,7 @@ impl BuiltinTypedArray { let len = array_like.length_of_array_like(context)?; // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[len.into()], context)?; + let target_obj = Self::create(constructor, &[len.into()], context)?.upcast(); // 11. Let k be 0. // 12. Repeat, while k < len, @@ -303,10 +306,10 @@ impl BuiltinTypedArray { let constructor = kind.standard_constructor()(context.intrinsics().constructors()).constructor(); - Self::create(&constructor, &[length.into()], context) + Self::create(&constructor, &[length.into()], context).map(JsObject::upcast) } - /// `23.2.2.2 %TypedArray%.of ( ...items )` + /// `%TypedArray%.of ( ...items )` /// /// More information: /// - [ECMAScript reference][spec] @@ -327,7 +330,7 @@ impl BuiltinTypedArray { }; // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let new_obj = Self::create(constructor, &[args.len().into()], context)?; + let new_obj = Self::create(constructor, &[args.len().into()], context)?.upcast(); // 5. Let k be 0. // 6. Repeat, while k < len, @@ -342,7 +345,7 @@ impl BuiltinTypedArray { Ok(new_obj.into()) } - /// `23.2.2.4 get %TypedArray% [ @@species ]` + /// `get %TypedArray% [ @@species ]` /// /// More information: /// - [ECMAScript reference][spec] @@ -354,7 +357,7 @@ impl BuiltinTypedArray { Ok(this.clone()) } - /// `23.2.3.1 %TypedArray%.prototype.at ( index )` + /// `%TypedArray%.prototype.at ( index )` /// /// More information: /// - [ECMAScript reference][spec] @@ -362,24 +365,11 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (o, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = o.borrow().data.array_length(buf_len) as i64; // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; @@ -403,10 +393,10 @@ impl BuiltinTypedArray { } // 8. Return ! Get(O, ! ToString(𝔽(k))). - Ok(obj.get(k, context).expect("Get cannot fail here")) + Ok(o.upcast().get(k, context).expect("Get cannot fail here")) } - /// `23.2.3.2 get %TypedArray%.prototype.buffer` + /// `get %TypedArray%.prototype.buffer` /// /// More information: /// - [ECMAScript reference][spec] @@ -416,19 +406,19 @@ impl BuiltinTypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let typed_array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let ta = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a typed array object") + })?; // 4. Let buffer be O.[[ViewedArrayBuffer]]. // 5. Return buffer. - Ok(JsObject::from(typed_array.viewed_array_buffer().clone()).into()) + Ok(ta.viewed_array_buffer().clone().into()) } - /// `23.2.3.3 get %TypedArray%.prototype.byteLength` + /// `get %TypedArray%.prototype.byteLength` /// /// More information: /// - [ECMAScript reference][spec] @@ -438,25 +428,27 @@ impl BuiltinTypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let typed_array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let ta = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a typed array object") + })?; - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - // 6. Let size be O.[[ByteLength]]. - // 7. Return 𝔽(size). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.byte_length().into()) - } + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + let buf_len = ta + .viewed_array_buffer() + .as_buffer() + .bytes(Ordering::SeqCst) + .map(|s| s.len()) + .unwrap_or_default(); + + // 5. Let size be TypedArrayByteLength(taRecord). + // 6. Return 𝔽(size). + Ok(ta.byte_length(buf_len).into()) } - /// `23.2.3.4 get %TypedArray%.prototype.byteOffset` + /// `get %TypedArray%.prototype.byteOffset` /// /// More information: /// - [ECMAScript reference][spec] @@ -466,25 +458,31 @@ impl BuiltinTypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let typed_array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let ta = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; + + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽. + if ta + .viewed_array_buffer() + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|s| !ta.is_out_of_bounds(s.len())) + .is_none() + { + return Ok(0.into()); + } - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. // 6. Let offset be O.[[ByteOffset]]. // 7. Return 𝔽(offset). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.byte_offset().into()) - } + Ok(ta.byte_offset().into()) } - /// `23.2.3.6 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )` + /// `%TypedArray%.prototype.copyWithin ( target, start [ , end ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -492,128 +490,118 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin fn copy_within(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let len = { - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - // 2. Perform ? ValidateTypedArray(O). - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - o.array_length() - }; + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. Let relativeTarget be ? ToIntegerOrInfinity(target). - let relative_target = args.get_or_undefined(0).to_integer_or_infinity(context)?; - - let to = match relative_target { - // 5. If relativeTarget is -∞, let to be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). - IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), - // 7. Else, let to be min(relativeTarget, len). - // We can directly convert to `u64` since we covered the case where `i < 0`. - IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), - IntegerOrInfinity::PositiveInfinity => len, - }; + // 5. If relativeTarget is -∞, let to be 0. + // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). + // 7. Else, let to be min(relativeTarget, len). + let to = Array::get_relative_start(context, args.get_or_undefined(0), len)?; // 8. Let relativeStart be ? ToIntegerOrInfinity(start). - let relative_start = args.get_or_undefined(1).to_integer_or_infinity(context)?; - - let from = match relative_start { - // 9. If relativeStart is -∞, let from be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), - // 11. Else, let from be min(relativeStart, len). - // We can directly convert to `u64` since we covered the case where `i < 0`. - IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), - IntegerOrInfinity::PositiveInfinity => len, - }; + // 9. If relativeStart is -∞, let from be 0. + // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + // 11. Else, let from be min(relativeStart, len). + let from = Array::get_relative_start(context, args.get_or_undefined(1), len)?; // 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(2); - let r#final = if end.is_undefined() { - len - } else { - match end.to_integer_or_infinity(context)? { - // 13. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0), - // 15. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i as u64, len), - IntegerOrInfinity::PositiveInfinity => len, - } - }; + // 13. If relativeEnd is -∞, let final be 0. + // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + // 15. Else, let final be min(relativeEnd, len). + let final_ = Array::get_relative_end(context, args.get_or_undefined(2), len)?; // 16. Let count be min(final - from, len - to). - let count = match (r#final.checked_sub(from), len.checked_sub(to)) { - (Some(lhs), Some(rhs)) => std::cmp::min(lhs, rhs), + let count = match (final_.checked_sub(from), len.checked_sub(to)) { + (Some(lhs), Some(rhs)) => min(lhs, rhs), _ => 0, }; // 17. If count > 0, then if count > 0 { - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let ta = ta.borrow(); + let ta = &ta.data; // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. // b. Let buffer be O.[[ViewedArrayBuffer]]. - // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - let buffer_obj = o.viewed_array_buffer(); + // c. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // d. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + let buffer_obj = ta.viewed_array_buffer(); let mut buffer = buffer_obj.as_buffer_mut(); - let Some(buffer) = buffer.data_mut() else { + let Some(mut buf) = buffer + .bytes(Ordering::SeqCst) + .filter(|s| !ta.is_out_of_bounds(s.len())) + else { return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") + .with_message("typed array is outside the bounds of its inner buffer") .into()); }; - // d. Let typedArrayName be the String value of O.[[TypedArrayName]]. - let kind = o.kind(); + // e. Set len to TypedArrayLength(taRecord). + let len = ta.array_length(buf.len()); - // e. Let elementSize be the Element Size value specified in Table 73 for typedArrayName. - let element_size = kind.element_size(); + // f. Let elementSize be TypedArrayElementSize(O). + let element_size = ta.kind().element_size(); - // f. Let byteOffset be O.[[ByteOffset]]. - let byte_offset = o.byte_offset(); + // g. Let byteOffset be O.[[ByteOffset]]. + let byte_offset = ta.byte_offset(); - // g. Let toByteIndex be to × elementSize + byteOffset. + // h. Let bufferByteLimit be (len × elementSize) + byteOffset. + let buffer_byte_limit = ((len * element_size) + byte_offset) as usize; + + // i. Let toByteIndex be (targetIndex × elementSize) + byteOffset. let to_byte_index = (to * element_size + byte_offset) as usize; - // h. Let fromByteIndex be from × elementSize + byteOffset. + // j. Let fromByteIndex be (startIndex × elementSize) + byteOffset. let from_byte_index = (from * element_size + byte_offset) as usize; - // i. Let countBytes be count × elementSize. - let count_bytes = (count * element_size) as usize; - - // j. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then - // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. - // iii. Set toByteIndex to toByteIndex + countBytes - 1. - // i. Let direction be -1. - // k. Else, - // i. Let direction be 1. - // l. Repeat, while countBytes > 0, - // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). - // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). - // iii. Set fromByteIndex to fromByteIndex + direction. - // iv. Set toByteIndex to toByteIndex + direction. - // v. Set countBytes to countBytes - 1. + // k. Let countBytes be count × elementSize. + let mut count_bytes = (count * element_size) as usize; + + // Readjust considering the buffer_byte_limit. A resize could + // have readjusted the buffer size, which could put `count_bytes` + // outside the allowed range. + if to_byte_index >= buffer_byte_limit || from_byte_index >= buffer_byte_limit { + return Ok(this.clone()); + } + + count_bytes = min( + count_bytes, + min( + buffer_byte_limit - to_byte_index, + buffer_byte_limit - from_byte_index, + ), + ); + + // l. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then + // i. Let direction be -1. + // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. + // iii. Set toByteIndex to toByteIndex + countBytes - 1. + // m. Else, + // i. Let direction be 1. + // n. Repeat, while countBytes > 0, + // i. If fromByteIndex < bufferByteLimit and toByteIndex < bufferByteLimit, then + // 1. Let value be GetValueFromBuffer(buffer, fromByteIndex, uint8, true, unordered). + // 2. Perform SetValueInBuffer(buffer, toByteIndex, uint8, value, true, unordered). + // 3. Set fromByteIndex to fromByteIndex + direction. + // 4. Set toByteIndex to toByteIndex + direction. + // 5. Set countBytes to countBytes - 1. + // ii. Else, + // 1. Set countBytes to 0. + + #[cfg(debug_assertions)] + { + assert!(buf.subslice_mut(from_byte_index..).len() >= count_bytes); + assert!(buf.subslice_mut(to_byte_index..).len() >= count_bytes); + } // SAFETY: All previous checks are made to ensure this memmove is always in-bounds, // making this operation safe. unsafe { - memmove(buffer, from_byte_index, to_byte_index, count_bytes); + memmove(buf.as_ptr(), from_byte_index, to_byte_index, count_bytes); } } @@ -621,7 +609,7 @@ impl BuiltinTypedArray { Ok(this.clone()) } - /// `23.2.3.7 %TypedArray%.prototype.entries ( )` + /// `%TypedArray%.prototype.entries ( )` /// /// More information: /// - [ECMAScript reference][spec] @@ -629,28 +617,18 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries fn entries(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.downcast_ref::() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Perform ? ValidateTypedArray(O, seq-cst). + let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?; // 3. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - o.clone(), + ta.upcast(), PropertyNameKind::KeyAndValue, context, )) } - /// `23.2.3.8 %TypedArray%.prototype.every ( callbackfn [ , thisArg ] )` + /// `%TypedArray%.prototype.every ( callbackfn [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -662,24 +640,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -695,10 +660,11 @@ impl BuiltinTypedArray { // 5. Let k be 0. // 6. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context)?; + let k_value = ta.get(k, context)?; // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback_fn @@ -719,7 +685,7 @@ impl BuiltinTypedArray { Ok(true.into()) } - /// `23.2.3.9 %TypedArray%.prototype.fill ( value [ , start [ , end ] ] )` + /// `%TypedArray%.prototype.fill ( value [ , start [ , end ] ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -731,85 +697,75 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); - // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). - let value: JsValue = if o.kind().content_type() == ContentType::BigInt { + let value: JsValue = if ta.borrow().data.kind().content_type() == ContentType::BigInt { + // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). args.get_or_undefined(0).to_bigint(context)?.into() - // 5. Otherwise, set value to ? ToNumber(value). } else { + // 5. Otherwise, set value to ? ToNumber(value). args.get_or_undefined(0).to_number(context)?.into() }; // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - let mut k = match args.get_or_undefined(1).to_integer_or_infinity(context)? { - // 7. If relativeStart is -∞, let k be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeStart < 0, let k be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 9. Else, let k be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; + // 7. If relativeStart = -∞, let startIndex be 0. + // 8. Else if relativeStart < 0, let startIndex be max(len + relativeStart, 0). + // 9. Else, let startIndex be min(relativeStart, len). + let start_index = Array::get_relative_start(context, args.get_or_undefined(1), len)?; // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(2); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; + // 11. If relativeEnd = -∞, let endIndex be 0. + // 12. Else if relativeEnd < 0, let endIndex be max(len + relativeEnd, 0). + // 13. Else, let endIndex be min(relativeEnd, len). + let end_index = Array::get_relative_end(context, args.get_or_undefined(2), len)?; - let r#final = match relative_end { - // 11. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 13. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, + // 14. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // 15. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + let len = { + let ta = ta.borrow(); + + let Some(buf_len) = ta + .data + .viewed_array_buffer() + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|b| !ta.data.is_out_of_bounds(b.len())) + .map(|b| b.len()) + else { + return Err(JsNativeError::typ() + .with_message("typed array is outside the bounds of its inner buffer") + .into()); + }; + + // 16. Set len to TypedArrayLength(taRecord). + ta.data.array_length(buf_len) }; - // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 17. Set endIndex to min(endIndex, len). + let end_index = min(end_index, len); - drop(o); + // 18. Let k be startIndex. + // 19. Repeat, while k < endIndex, - // 15. Repeat, while k < final, - while k < r#final { + let ta = ta.upcast(); + for k in start_index..end_index { // a. Let Pk be ! ToString(𝔽(k)). // b. Perform ! Set(O, Pk, value, true). - obj.set(k, value.clone(), true, context) + ta.set(k, value.clone(), true, context) .expect("Set cannot fail here"); // c. Set k to k + 1. - k += 1; } - // 16. Return O. + // 20. Return O. Ok(this.clone()) } - /// `23.2.3.10 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )` + /// `%TypedArray%.prototype.filter ( callbackfn [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -821,26 +777,12 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - let typed_array_name = o.kind(); - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); + let typed_array_kind = ta.borrow().data.kind(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -861,10 +803,11 @@ impl BuiltinTypedArray { let mut captured = 0; // 8. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# let selected = callback_fn @@ -886,7 +829,7 @@ impl BuiltinTypedArray { } // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). - let a = Self::species_create(obj, typed_array_name, &[captured.into()], context)?; + let a = Self::species_create(&ta, typed_array_kind, &[captured.into()], context)?.upcast(); // 10. Let n be 0. // 11. For each element e of kept, do @@ -901,7 +844,7 @@ impl BuiltinTypedArray { Ok(a.into()) } - /// `23.2.3.11 %TypedArray%.prototype.find ( predicate [ , thisArg ] )` + /// `%TypedArray%.prototype.find ( predicate [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -913,31 +856,18 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). let (_, value) = find_via_predicate( - obj, + &ta.upcast(), len, Direction::Ascending, predicate, @@ -950,7 +880,7 @@ impl BuiltinTypedArray { Ok(value) } - /// `23.2.3.12 %TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )` + /// `%TypedArray%.prototype.findIndex ( predicate [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -962,31 +892,18 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). let (index, _) = find_via_predicate( - obj, + &ta.upcast(), len, Direction::Ascending, predicate, @@ -999,7 +916,7 @@ impl BuiltinTypedArray { Ok(index) } - /// `23.2.3.13 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] )` + /// `%TypedArray%.prototype.findLast ( predicate [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1011,29 +928,18 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). let (_, value) = find_via_predicate( - obj, + &ta.upcast(), len, Direction::Descending, predicate, @@ -1046,7 +952,7 @@ impl BuiltinTypedArray { Ok(value) } - /// `23.2.3.14 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] )` + /// `%TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1058,29 +964,18 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). let (index, _) = find_via_predicate( - obj, + &ta.upcast(), len, Direction::Descending, predicate, @@ -1093,7 +988,7 @@ impl BuiltinTypedArray { Ok(index) } - /// `23.2.3.15 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )` + /// `%TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1105,24 +1000,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -1137,10 +1019,11 @@ impl BuiltinTypedArray { // 5. Let k be 0. // 6. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). callback_fn.call( @@ -1154,7 +1037,7 @@ impl BuiltinTypedArray { Ok(JsValue::undefined()) } - /// `23.2.3.14 %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )` + /// `%TypedArray%.prototype.includes ( searchElement [ , fromIndex ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1166,24 +1049,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If len is 0, return false. if len == 0 { @@ -1203,24 +1073,21 @@ impl BuiltinTypedArray { }; // 9. If n ≥ 0, then - let mut k = if n >= 0 { + let k = if n >= 0 { // a. Let k be n. - n - // 10. Else, + n as u64 } else { + // 10. Else, // a. Let k be len + n. // b. If k < 0, set k to 0. - if len + n < 0 { - 0 - } else { - len + n - } + len.saturating_add_signed(n) }; // 11. Repeat, while k < len, - while k < len { + let ta = ta.upcast(); + for k in k..len { // a. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); + let element_k = ta.get(k, context).expect("Get cannot fail here"); // b. If SameValueZero(searchElement, elementK) is true, return true. if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { @@ -1228,14 +1095,13 @@ impl BuiltinTypedArray { } // c. Set k to k + 1. - k += 1; } // 12. Return false. Ok(false.into()) } - /// `23.2.3.15 %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )` + /// `%TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1247,24 +1113,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If len is 0, return -1𝔽. if len == 0 { @@ -1284,31 +1137,28 @@ impl BuiltinTypedArray { }; // 9. If n ≥ 0, then - let mut k = if n >= 0 { + let k = if n >= 0 { // a. Let k be n. - n + n as u64 // 10. Else, } else { // a. Let k be len + n. // b. If k < 0, set k to 0. - if len + n < 0 { - 0 - } else { - len + n - } + len.saturating_add_signed(n) }; // 11. Repeat, while k < len, - while k < len { + let ta = ta.upcast(); + for k in k..len { // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). - let k_present = obj + let k_present = ta .has_property(k, context) .expect("HasProperty cannot fail here"); // b. If kPresent is true, then if k_present { // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); + let element_k = ta.get(k, context).expect("Get cannot fail here"); // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). @@ -1318,14 +1168,13 @@ impl BuiltinTypedArray { } // c. Set k to k + 1. - k += 1; } // 12. Return -1𝔽. Ok((-1).into()) } - /// `23.2.3.16 %TypedArray%.prototype.join ( separator )` + /// `%TypedArray%.prototype.join ( separator )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1337,24 +1186,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If separator is undefined, let sep be the single-element String ",". let separator = args.get_or_undefined(0); @@ -1366,31 +1202,32 @@ impl BuiltinTypedArray { }; // 6. Let R be the empty String. - let mut r = js_string!(); + let mut r = Vec::new(); // 7. Let k be 0. // 8. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. If k > 0, set R to the string-concatenation of R and sep. if k > 0 { - r = js_string!(&r, &sep); + r.extend_from_slice(&sep); } // b. Let element be ! Get(O, ! ToString(𝔽(k))). - let element = obj.get(k, context).expect("Get cannot fail here"); + let element = ta.get(k, context).expect("Get cannot fail here"); // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). // d. Set R to the string-concatenation of R and next. if !element.is_undefined() { - r = js_string!(&r, &element.to_string(context)?); + r.extend_from_slice(&element.to_string(context)?); } } // 9. Return R. - Ok(r.into()) + Ok(js_string!(r).into()) } - /// `23.2.3.17 %TypedArray%.prototype.keys ( )` + /// `%TypedArray%.prototype.keys ( )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1398,28 +1235,18 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.downcast_ref::() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?; // 3. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - o.clone(), + ta.upcast(), PropertyNameKind::Key, context, )) } - /// `23.2.3.18 %TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )` + /// `%TypedArray%.prototype.lastIndexOf ( searchElement [ , fromIndex ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1431,24 +1258,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If len is 0, return -1𝔽. if len == 0 { @@ -1456,35 +1270,36 @@ impl BuiltinTypedArray { } // 5. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. - let n = if let Some(n) = args.get(1) { - n.to_integer_or_infinity(context)? - } else { - IntegerOrInfinity::Integer(len - 1) - }; - - let mut k = match n { - // 6. If n is -∞, return -1𝔽. - IntegerOrInfinity::NegativeInfinity => return Ok((-1).into()), - // 7. If n ≥ 0, then - // a. Let k be min(n, len - 1). - IntegerOrInfinity::Integer(i) if i >= 0 => std::cmp::min(i, len - 1), - IntegerOrInfinity::PositiveInfinity => len - 1, - // 8. Else, - // a. Let k be len + n. - IntegerOrInfinity::Integer(i) => len + i, + let k = match args.get(1) { + None => len, + Some(n) => { + let n = n.to_integer_or_infinity(context)?; + match n { + // 6. If n is -∞, return -1𝔽. + IntegerOrInfinity::NegativeInfinity => return Ok((-1).into()), + // 7. If n ≥ 0, then + // a. Let k be min(n, len - 1). + IntegerOrInfinity::Integer(i) if i >= 0 => min(i as u64 + 1, len), + IntegerOrInfinity::PositiveInfinity => len, + // 8. Else, + // a. Let k be len + n. + IntegerOrInfinity::Integer(i) => len.saturating_add_signed(i + 1), + } + } }; // 9. Repeat, while k ≥ 0, - while k >= 0 { + let ta = ta.upcast(); + for k in (0..k).rev() { // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). - let k_present = obj + let k_present = ta .has_property(k, context) .expect("HasProperty cannot fail here"); // b. If kPresent is true, then if k_present { // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). - let element_k = obj.get(k, context).expect("Get cannot fail here"); + let element_k = ta.get(k, context).expect("Get cannot fail here"); // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). @@ -1494,14 +1309,13 @@ impl BuiltinTypedArray { } // c. Set k to k - 1. - k -= 1; } // 10. Return -1𝔽. Ok((-1).into()) } - /// `23.2.3.19 get %TypedArray%.prototype.length` + /// `get %TypedArray%.prototype.length` /// /// More information: /// - [ECMAScript reference][spec] @@ -1511,26 +1325,29 @@ impl BuiltinTypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let ta = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a typed array object") + })?; - let typed_array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽. + let buf = ta.viewed_array_buffer().as_buffer(); + let Some(buf) = buf + .bytes(Ordering::SeqCst) + .filter(|s| !ta.is_out_of_bounds(s.len())) + else { + return Ok(0.into()); + }; - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - // 6. Let length be O.[[ArrayLength]]. + // 6. Let length be TypedArrayLength(taRecord). // 7. Return 𝔽(length). - if typed_array.is_detached() { - Ok(0.into()) - } else { - Ok(typed_array.array_length().into()) - } + Ok(ta.array_length(buf.len()).into()) } - /// `23.2.3.20 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] )` + /// `%TypedArray%.prototype.map ( callbackfn [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1542,26 +1359,13 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - let typed_array_name = o.kind(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); - drop(o); + let typed_array_kind = ta.borrow().data.kind(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1575,15 +1379,17 @@ impl BuiltinTypedArray { } }; + let ta = ta.upcast(); + // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). - let a = Self::species_create(obj, typed_array_name, &[len.into()], context)?; + let a = Self::species_create(&ta, typed_array_kind, &[len.into()], context)?.upcast(); // 6. Let k be 0. // 7. Repeat, while k < len, for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = callback_fn.call( @@ -1600,7 +1406,7 @@ impl BuiltinTypedArray { Ok(a.into()) } - /// `23.2.3.21 %TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )` + /// `%TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1612,24 +1418,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -1649,6 +1442,8 @@ impl BuiltinTypedArray { .into()); } + let ta = ta.upcast(); + // 6. Let k be 0. let mut k = 0; @@ -1663,14 +1458,14 @@ impl BuiltinTypedArray { // b. Set accumulator to ! Get(O, Pk). // c. Set k to k + 1. k += 1; - obj.get(0, context).expect("Get cannot fail here") + ta.get(0, context).expect("Get cannot fail here") }; // 10. Repeat, while k < len, - while k < len { + for k in k..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). accumulator = callback_fn.call( @@ -1680,14 +1475,13 @@ impl BuiltinTypedArray { )?; // d. Set k to k + 1. - k += 1; } // 11. Return accumulator. Ok(accumulator) } - /// `23.2.3.22 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )` + /// `%TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1699,24 +1493,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1735,31 +1516,29 @@ impl BuiltinTypedArray { .into()); } - // 6. Let k be len - 1. - let mut k = len - 1; + let ta = ta.upcast(); + // 6. Let k be len - 1. // 7. Let accumulator be undefined. // 8. If initialValue is present, then - let mut accumulator = if let Some(initial_value) = args.get(1) { + let (mut accumulator, k) = if let Some(initial_value) = args.get(1) { // a. Set accumulator to initialValue. - initial_value.clone() + (initial_value.clone(), len) // 9. Else, } else { // a. Let Pk be ! ToString(𝔽(k)). // b. Set accumulator to ! Get(O, Pk). - let accumulator = obj.get(k, context).expect("Get cannot fail here"); + let accumulator = ta.get(len - 1, context).expect("Get cannot fail here"); // c. Set k to k - 1. - k -= 1; - - accumulator + (accumulator, len - 1) }; // 10. Repeat, while k ≥ 0, - while k >= 0 { + for k in (0..k).rev() { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). accumulator = callback_fn.call( @@ -1769,14 +1548,13 @@ impl BuiltinTypedArray { )?; // d. Set k to k - 1. - k -= 1; } // 11. Return accumulator. Ok(accumulator) } - /// `23.2.3.23 %TypedArray%.prototype.reverse ( )` + /// `%TypedArray%.prototype.reverse ( )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1789,24 +1567,13 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); - drop(o); + let ta = ta.upcast(); // 4. Let middle be floor(len / 2). let middle = len / 2; @@ -1821,15 +1588,15 @@ impl BuiltinTypedArray { // b. Let upperP be ! ToString(𝔽(upper)). // c. Let lowerP be ! ToString(𝔽(lower)). // d. Let lowerValue be ! Get(O, lowerP). - let lower_value = obj.get(lower, context).expect("Get cannot fail here"); + let lower_value = ta.get(lower, context).expect("Get cannot fail here"); // e. Let upperValue be ! Get(O, upperP). - let upper_value = obj.get(upper, context).expect("Get cannot fail here"); + let upper_value = ta.get(upper, context).expect("Get cannot fail here"); // f. Perform ! Set(O, lowerP, upperValue, true). - obj.set(lower, upper_value, true, context) + ta.set(lower, upper_value, true, context) .expect("Set cannot fail here"); // g. Perform ! Set(O, upperP, lowerValue, true). - obj.set(upper, lower_value, true, context) + ta.set(upper, lower_value, true, context) .expect("Set cannot fail here"); // h. Set lower to lower + 1. @@ -1849,35 +1616,25 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Let iieoRecord be ? ValidateTypedArray(O, seq-cst). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - let array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let length be IntegerIndexedObjectLength(iieoRecord). - let length = array.array_length(); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); + let kind = ta.borrow().data.kind(); // 4. Let A be ? TypedArrayCreateSameType(O, « 𝔽(length) »). - let new_array = Self::from_kind_and_length(array.kind(), length, context)?; - - drop(array); + let new_array = Self::from_kind_and_length(kind, len, context)?; // 5. Let k be 0. // 6. Repeat, while k < length, - for k in 0..length { + let ta = ta.upcast(); + for k in 0..len { // a. Let from be ! ToString(𝔽(length - k - 1)). // b. Let Pk be ! ToString(𝔽(k)). // c. Let fromValue be ! Get(O, from). - let value = obj - .get(length - k - 1, context) + let value = ta + .get(len - k - 1, context) .expect("cannot fail per the spec"); // d. Perform ! Set(A, Pk, fromValue, true). new_array @@ -1887,11 +1644,10 @@ impl BuiltinTypedArray { } // 7. Return A. - Ok(new_array.into()) } - /// `23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )` + /// `%TypedArray%.prototype.set ( source [ , offset ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -1905,14 +1661,13 @@ impl BuiltinTypedArray { // 1. Let target be the this value. // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. - let target = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("TypedArray.set must be called on typed array object") - })?; - if !target.is::() { - return Err(JsNativeError::typ() - .with_message("TypedArray.set must be called on typed array object") - .into()); - } + let target = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("TypedArray.set must be called on typed array object") + })?; // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). let target_offset = args.get_or_undefined(1).to_integer_or_infinity(context)?; @@ -1933,18 +1688,19 @@ impl BuiltinTypedArray { IntegerOrInfinity::Integer(i) => U64OrPositiveInfinity::U64(i as u64), }; + // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then let source = args.get_or_undefined(0); - match source { - // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsValue::Object(source) if source.is::() => { - // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). - Self::set_typed_array_from_typed_array(target, &target_offset, source, context)?; - } - // 7. Else, - _ => { - // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). - Self::set_typed_array_from_array_like(target, &target_offset, source, context)?; - } + if let Some(source) = source + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + { + // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). + Self::set_typed_array_from_typed_array(&target, &target_offset, &source, context)?; + } + // 7. Else, + else { + // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). + Self::set_typed_array_from_array_like(&target, &target_offset, source, context)?; } // 8. Return undefined. @@ -1958,82 +1714,78 @@ impl BuiltinTypedArray { /// /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray fn set_typed_array_from_typed_array( - target: &JsObject, + target: &JsObject, target_offset: &U64OrPositiveInfinity, - source: &JsObject, + source: &JsObject, context: &mut Context, ) -> JsResult<()> { - let target_borrow = target.borrow(); - let target_array = target_borrow - .downcast_ref::() - .expect("Target must be a typed array"); - - let source_borrow = source.borrow(); - let source_array = source_borrow - .downcast_ref::() - .expect("Source must be a typed array"); - - // TODO: Implement growable buffers. // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. - // 2. Let targetRecord be MakeIntegerIndexedObjectWithBufferWitnessRecord(target, seq-cst). - // 3. If IsIntegerIndexedObjectOutOfBounds(targetRecord) is true, throw a TypeError exception. - // 4. Let targetLength be IntegerIndexedObjectLength(targetRecord). - // 5. Let srcBuffer be source.[[ViewedArrayBuffer]]. - // 6. Let srcRecord be MakeIntegerIndexedObjectWithBufferWitnessRecord(source, seq-cst). - // 7. If IsIntegerIndexedObjectOutOfBounds(srcRecord) is true, throw a TypeError exception. - - if target_array.is_detached() { + // 2. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst). + // 3. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception. + let target_array = target.borrow(); + let target_buf_obj = target_array.data.viewed_array_buffer().clone(); + let Some(target_buf_len) = target_buf_obj + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|s| !target_array.data.is_out_of_bounds(s.len())) + .map(|s| s.len()) + else { return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") + .with_message("typed array is outside the bounds of its inner buffer") .into()); - } - let target_buffer_obj = target_array.viewed_array_buffer().clone(); + }; - // 3. Let targetLength be target.[[ArrayLength]]. - let target_length = target_array.array_length(); + // 4. Let targetLength be TypedArrayLength(targetRecord). + let target_length = target_array.data.array_length(target_buf_len); - // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. - // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. - if source_array.is_detached() { + // 5. Let srcBuffer be source.[[ViewedArrayBuffer]]. + // 6. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(source, seq-cst). + // 7. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception. + let src_array = source.borrow(); + let mut src_buf_obj = src_array.data.viewed_array_buffer().clone(); + let Some(mut src_buf_len) = src_buf_obj + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|s| !src_array.data.is_out_of_bounds(s.len())) + .map(|s| s.len()) + else { return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") + .with_message("typed array is outside the bounds of its inner buffer") .into()); - } - let mut src_buffer_obj = source_array.viewed_array_buffer().clone(); + }; - // 6. Let targetName be the String value of target.[[TypedArrayName]]. - // 7. Let targetType be the Element Type value in Table 73 for targetName. - let target_type = target_array.kind(); + // 8. Let srcLength be TypedArrayLength(srcRecord). + let src_length = src_array.data.array_length(src_buf_len); - // 8. Let targetElementSize be the Element Size value specified in Table 73 for targetName. - let target_element_size = target_type.element_size(); + // 9. Let targetType be TypedArrayElementType(target). + let target_type = target_array.data.kind(); - // 9. Let targetByteOffset be target.[[ByteOffset]]. - let target_byte_offset = target_array.byte_offset(); + // 10. Let targetElementSize be TypedArrayElementSize(target). + let target_element_size = target_type.element_size(); - drop(target_borrow); + // 11. Let targetByteOffset be target.[[ByteOffset]]. + let target_byte_offset = target_array.data.byte_offset(); - // 10. Let srcName be the String value of source.[[TypedArrayName]]. - // 11. Let srcType be the Element Type value in Table 73 for srcName. - let src_type = source_array.kind(); + // 12. Let srcType be TypedArrayElementType(source). + let src_type = src_array.data.kind(); - // 12. Let srcElementSize be the Element Size value specified in Table 73 for srcName. + // 13. Let srcElementSize be TypedArrayElementSize(source). let src_element_size = src_type.element_size(); - // 13. Let srcLength be source.[[ArrayLength]]. - let src_length = source_array.array_length(); - // 14. Let srcByteOffset be source.[[ByteOffset]]. - let src_byte_offset = source_array.byte_offset(); + let src_byte_offset = src_array.data.byte_offset(); - // 15. If targetOffset is +∞, throw a RangeError exception. - let target_offset = match target_offset { - U64OrPositiveInfinity::U64(target_offset) => target_offset, - U64OrPositiveInfinity::PositiveInfinity => { - return Err(JsNativeError::range() - .with_message("Target offset cannot be Infinity") - .into()); - } + // a. Let srcByteLength be source.[[ByteLength]]. + let src_byte_length = src_array.data.byte_length(src_buf_len); + + drop(target_array); + drop(src_array); + + // 15. If targetOffset = +∞, throw a RangeError exception. + let U64OrPositiveInfinity::U64(target_offset) = target_offset else { + return Err(JsNativeError::range() + .with_message("Target offset cannot be Infinity") + .into()); }; // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. @@ -2043,7 +1795,7 @@ impl BuiltinTypedArray { .into()); } - // 17. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. + // 17. If target.[[ContentType]] is not source.[[ContentType]], throw a TypeError exception. if target_type.content_type() != src_type.content_type() { return Err(JsNativeError::typ() .with_message( @@ -2056,51 +1808,54 @@ impl BuiltinTypedArray { // and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let // sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false. // 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then - let src_byte_index = if BufferObject::equals(&src_buffer_obj, &target_buffer_obj) { + let src_byte_index = if BufferObject::equals(&src_buf_obj, &target_buf_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 src_byte_length = src_byte_length as usize; let s = { - let slice = src_buffer_obj.as_buffer(); - let slice = slice.data().expect("Already checked for detached buffer"); + let slice = src_buf_obj.as_buffer(); + let slice = slice + .bytes_with_len(src_buf_len) + .expect("Already checked for detached buffer"); // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength, %ArrayBuffer%). // c. NOTE: %ArrayBuffer% is used to clone srcBuffer because is it known to not have any observable side-effects. - slice - .subslice(src_byte_offset..src_byte_offset + src_byte_length) - .clone(context)? + let subslice = slice.subslice(src_byte_offset..src_byte_offset + src_byte_length); + src_buf_len = subslice.len(); + + subslice.clone(context)? }; - src_buffer_obj = BufferObject::Buffer(s); + + src_buf_obj = BufferObject::Buffer(s); // d. Let srcByteIndex be 0. 0 } - // 21. Else, let srcByteIndex be srcByteOffset. + // 20. Else, else { + // a. Let srcByteIndex be srcByteOffset. src_byte_offset }; - drop(source_borrow); - // 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.as_buffer(); + let src_buffer = src_buf_obj.as_buffer(); let src_buffer = src_buffer - .data() + .bytes_with_len(src_buf_len) .expect("Already checked for detached buffer"); - let mut target_buffer = target_buffer_obj.as_buffer_mut(); + let mut target_buffer = target_buf_obj.as_buffer_mut(); let mut target_buffer = target_buffer - .data_mut() + .bytes_with_len(target_buf_len) .expect("Already checked for detached buffer"); // 24. If srcType is the same as targetType, then if src_type == target_type { let src_byte_index = src_byte_index as usize; let target_byte_index = target_byte_index as usize; - let count = (target_element_size * src_length) as usize; + let byte_count = (target_element_size * src_length) as usize; // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. // b. Repeat, while targetByteIndex < limit, @@ -2108,14 +1863,18 @@ impl BuiltinTypedArray { // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). // iii. Set srcByteIndex to srcByteIndex + 1. // iv. Set targetByteIndex to targetByteIndex + 1. + let src = src_buffer.subslice(src_byte_index..); + let mut target = target_buffer.subslice_mut(target_byte_index..); + + #[cfg(debug_assertions)] + { + assert!(src.len() >= byte_count); + assert!(target.len() >= byte_count); + } // SAFETY: We already asserted that the indices are in bounds. unsafe { - memcpy( - src_buffer.subslice(src_byte_index..), - target_buffer.subslice_mut(target_byte_index..), - count, - ); + memcpy(src.as_ptr(), target.as_ptr(), byte_count); } } // 25. Else, @@ -2161,34 +1920,39 @@ impl BuiltinTypedArray { Ok(()) } - /// `23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )` + /// `SetTypedArrayFromArrayLike ( target, targetOffset, source )` /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-settypedarrayfromarraylike fn set_typed_array_from_array_like( - target: &JsObject, + target: &JsObject, target_offset: &U64OrPositiveInfinity, source: &JsValue, context: &mut Context, ) -> JsResult<()> { + // 3. Let targetLength be TypedArrayLength(targetRecord). let target_length = { - let target_borrow = target.borrow(); - let target_array = target_borrow - .downcast_ref::() - .expect("Target must be a typed array"); - - // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. - // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. - if target_array.is_detached() { + let target = target.borrow(); + let target = &target.data; + + // 1. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst). + // 2. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception. + let Some(buf_len) = target + .viewed_array_buffer() + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|s| !target.is_out_of_bounds(s.len())) + .map(|s| s.len()) + else { return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") + .with_message("typed array is outside the bounds of its inner buffer") .into()); - } + }; // 3. Let targetLength be target.[[ArrayLength]]. - target_array.array_length() + target.array_length(buf_len) }; // 4. Let src be ? ToObject(source). @@ -2216,6 +1980,7 @@ impl BuiltinTypedArray { // 8. Let k be 0. // 9. Repeat, while k < srcLength, + let target = target.clone().upcast(); for k in 0..src_length { // a. Let Pk be ! ToString(𝔽(k)). // b. Let value be ? Get(src, Pk). @@ -2225,7 +1990,7 @@ impl BuiltinTypedArray { let target_index = target_offset + k; // d. Perform ? IntegerIndexedElementSet(target, targetIndex, value). - typed_array_set_element(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. } @@ -2234,7 +1999,7 @@ impl BuiltinTypedArray { Ok(()) } - /// `23.2.3.25 %TypedArray%.prototype.slice ( start, end )` + /// `%TypedArray%.prototype.slice ( start, end )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2246,156 +2011,152 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (src, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length() as i64; + let src_borrow = src.borrow(); - // 4. Let relativeStart be ? ToIntegerOrInfinity(start). - let mut k = match args.get_or_undefined(0).to_integer_or_infinity(context)? { - // 5. If relativeStart is -∞, let k be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 6. Else if relativeStart < 0, let k be max(len + relativeStart, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 7. Else, let k be min(relativeStart, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; + // 3. Let len be TypedArrayLength(taRecord). + let src_len = src_borrow.data.array_length(buf_len); - // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - let end = args.get_or_undefined(1); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(len) - } else { - end.to_integer_or_infinity(context)? - }; + // e. Let srcType be TypedArrayElementType(O). + let src_type = src_borrow.data.kind(); - let r#final = match relative_end { - // 9. If relativeEnd is -∞, let final be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(len + i, 0), - // 11. Else, let final be min(relativeEnd, len). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), - IntegerOrInfinity::PositiveInfinity => len, - }; + drop(src_borrow); - // 12. Let count be max(final - k, 0). - let count = std::cmp::max(r#final - k, 0) as u64; + // 4. Let relativeStart be ? ToIntegerOrInfinity(start). + // 5. If relativeStart = -∞, let startIndex be 0. + // 6. Else if relativeStart < 0, let startIndex be max(srcArrayLength + relativeStart, 0). + // 7. Else, let startIndex be min(relativeStart, srcArrayLength). + let start_index = Array::get_relative_start(context, args.get_or_undefined(0), src_len)?; - // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). - let a = Self::species_create(obj, o.kind(), &[count.into()], context)?; + // 8. If end is undefined, let relativeEnd be srcArrayLength; else let relativeEnd be ? ToIntegerOrInfinity(end). + // 9. If relativeEnd = -∞, let endIndex be 0. + // 10. Else if relativeEnd < 0, let endIndex be max(srcArrayLength + relativeEnd, 0). + // 11. Else, let endIndex be min(relativeEnd, srcArrayLength). + let end_index = Array::get_relative_end(context, args.get_or_undefined(1), src_len)?; - // 14. If count > 0, then - if count > 0 { - let a_array = a - .downcast_ref::() - .expect("This must be a typed array"); + // 12. Let countBytes be max(endIndex - startIndex, 0). + let count = end_index.saturating_sub(start_index); - // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(countBytes) »). + let target = + Self::species_create(&src.clone().upcast(), src_type, &[count.into()], context)?; - // b. Let srcName be the String value of O.[[TypedArrayName]]. - // c. Let srcType be the Element Type value in Table 73 for srcName. - let src_type = o.kind(); + // 14. If countBytes > 0, then + if count == 0 { + // 15. Return A. + return Ok(target.upcast().into()); + } - // d. Let targetName be the String value of A.[[TypedArrayName]]. - // e. Let targetType be the Element Type value in Table 73 for targetName. - let target_type = a_array.kind(); + let src_borrow = src.borrow(); + let target_borrow = target.borrow(); - // f. If srcType is different from targetType, then - #[allow(clippy::if_not_else)] - if src_type != target_type { - drop(a_array); + // a. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + let src_buf_borrow = src_borrow.data.viewed_array_buffer().as_buffer(); + let Some(src_buf) = src_buf_borrow + .bytes(Ordering::SeqCst) + .filter(|s| !src_borrow.data.is_out_of_bounds(s.len())) + else { + return Err(JsNativeError::typ() + .with_message("typed array is outside the bounds of its inner buffer") + .into()); + }; - // i. Let n be 0. - let mut n = 0; + // c. Set endIndex to min(endIndex, TypedArrayLength(taRecord)). + let end_index = min(end_index, src_borrow.data.array_length(src_buf.len())); - // ii. Repeat, while k < final, - while k < r#final { - // 1. Let Pk be ! ToString(𝔽(k)). - // 2. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + // d. Set countBytes to max(endIndex - startIndex, 0). + let count = end_index.saturating_sub(start_index) as usize; - // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). - a.set(n, k_value, true, context) - .expect("Set cannot fail here"); + // f. Let targetType be TypedArrayElementType(A). + let target_type = target_borrow.data.kind(); - // 4. Set k to k + 1. - k += 1; + // g. If srcType is targetType, then + if src_type == target_type { + { + let byte_count = count * src_type.element_size() as usize; - // 5. Set n to n + 1. - n += 1; - } - // g. Else, - } else { - // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. - let src_buffer_obj = o.viewed_array_buffer(); - let src_buffer = src_buffer_obj.as_buffer(); - let src_buffer = src_buffer.data().expect("cannot be detached here"); + // i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // ii. Let srcBuffer be O.[[ViewedArrayBuffer]]. + // iii. Let targetBuffer be A.[[ViewedArrayBuffer]]. + let target_borrow = target_borrow; + let mut target_buf = target_borrow.data.viewed_array_buffer().as_buffer_mut(); + let mut target_buf = target_buf + .bytes_with_len(byte_count) + .expect("newly created array cannot be detached"); - // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. - let target_buffer_obj = a_array.viewed_array_buffer(); - let mut target_buffer = target_buffer_obj.as_buffer_mut(); - let mut target_buffer = target_buffer.data_mut().expect("cannot be detached here"); + // iv. Let elementSize be TypedArrayElementSize(O). + let element_size = src_type.element_size(); - // iii. Let elementSize be the Element Size value specified in Table 73 for Element Type srcType. - let element_size = o.kind().element_size(); + // v. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = src_borrow.data.byte_offset(); - // iv. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // vi. Let srcByteIndex be (startIndex × elementSize) + srcByteOffset. + let src_byte_index = (start_index * element_size + src_byte_offset) as usize; - // v. Let srcByteOffset be O.[[ByteOffset]]. - let src_byte_offset = o.byte_offset(); + // vii. Let targetByteIndex be A.[[ByteOffset]]. + let target_byte_index = target_borrow.data.byte_offset() as usize; - // vi. Let targetByteIndex be A.[[ByteOffset]]. - let target_byte_index = (a_array.byte_offset()) as usize; + // viii. Let endByteIndex be targetByteIndex + (countBytes × elementSize). + // Not needed by the impl. - // vii. Let srcByteIndex be (k × elementSize) + srcByteOffset. - let src_byte_index = (k as u64 * element_size + src_byte_offset) as usize; + // ix. Repeat, while targetByteIndex < endByteIndex, + // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, uint8, true, unordered). + // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, uint8, value, true, unordered). + // 3. Set srcByteIndex to srcByteIndex + 1. + // 4. Set targetByteIndex to targetByteIndex + 1. - let byte_count = (count * element_size) as usize; + let src = src_buf.subslice(src_byte_index..); + let mut target = target_buf.subslice_mut(target_byte_index..); - // viii. Let limit be targetByteIndex + count × elementSize. - // ix. Repeat, while targetByteIndex < limit, - // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). - // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). - // 3. Set srcByteIndex to srcByteIndex + 1. - // 4. Set targetByteIndex to targetByteIndex + 1. + #[cfg(debug_assertions)] + { + assert!(src.len() >= byte_count); + assert!(target.len() >= byte_count); + } // SAFETY: All previous checks put the indices at least within the bounds of `src_buffer`. // Also, `target_buffer` is precisely allocated to fit all sliced elements from // `src_buffer`, making this operation safe. unsafe { - memcpy( - src_buffer.subslice(src_byte_index..), - target_buffer.subslice_mut(target_byte_index..), - byte_count, - ); + memcpy(src.as_ptr(), target.as_ptr(), byte_count); } } - } - // 15. Return A. - Ok(a.into()) + // 15. Return A. + Ok(target.upcast().into()) + } else { + // h. Else, + drop(src_buf_borrow); + drop((src_borrow, target_borrow)); + + // i. Let n be 0. + // ii. Let k be startIndex. + // iii. Repeat, while k < endIndex, + let src = src.upcast(); + let target = target.upcast(); + for (n, k) in (start_index..end_index).enumerate() { + // 1. Let Pk be ! ToString(𝔽(k)). + // 2. Let kValue be ! Get(O, Pk). + let k_value = src.get(k, context).expect("Get cannot fail here"); + + // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). + target + .set(n, k_value, true, context) + .expect("Set cannot fail here"); + + // 4. Set k to k + 1. + // 5. Set n to n + 1. + } + + // 15. Return A. + Ok(target.into()) + } } - /// `23.2.3.26 %TypedArray%.prototype.some ( callbackfn [ , thisArg ] )` + /// `%TypedArray%.prototype.some ( callbackfn [ , thisArg ] )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2407,24 +2168,11 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be O.[[ArrayLength]]. - let len = o.array_length(); - - drop(o); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -2440,10 +2188,11 @@ impl BuiltinTypedArray { // 5. Let k be 0. // 6. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). // b. Let kValue be ! Get(O, Pk). - let k_value = obj.get(k, context).expect("Get cannot fail here"); + let k_value = ta.get(k, context).expect("Get cannot fail here"); // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // d. If testResult is true, return true. @@ -2463,7 +2212,7 @@ impl BuiltinTypedArray { Ok(false.into()) } - /// `23.2.3.27 %TypedArray%.prototype.sort ( comparefn )` + /// `%TypedArray%.prototype.sort ( comparefn )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2486,27 +2235,11 @@ impl BuiltinTypedArray { }; // 2. Let obj be the this value. - // 3. Let iieoRecord be ? ValidateTypedArray(obj, seq-cst). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; + // 3. Let taRecord be ? ValidateTypedArray(obj, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 4. Let len be IntegerIndexedObjectLength(iieoRecord). - let len = - { - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ().with_message( - "TypedArray.sort called on typed array object with detached array buffer", - ).into()); - } - - o.array_length() - }; + // 4. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30. // 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: @@ -2516,21 +2249,22 @@ impl BuiltinTypedArray { compare_typed_array_elements(x, y, compare_fn, context) }; + let ta = ta.upcast(); // 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes). - let sorted = Array::sort_indexed_properties(obj, len, sort_compare, false, context)?; + let sorted = Array::sort_indexed_properties(&ta, len, sort_compare, false, context)?; // 8. Let j be 0. // 9. Repeat, while j < len, for (j, item) in sorted.into_iter().enumerate() { // a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true). - obj.set(j, item, true, context) + ta.set(j, item, true, context) .expect("cannot fail per spec"); // b. Set j to j + 1. } // 10. Return obj. - Ok(obj.clone().into()) + Ok(ta.into()) } /// [`%TypedArray%.prototype.toSorted ( comparefn )`][spec] @@ -2553,28 +2287,14 @@ impl BuiltinTypedArray { }; // 2. Let O be the this value. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; - - // 3. Let iieoRecord be ? ValidateTypedArray(O, seq-cst). - let array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 3. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 4. Let len be IntegerIndexedObjectLength(iieoRecord). - let len = array.array_length(); + // 4. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); // 5. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »). - let new_array = Self::from_kind_and_length(array.kind(), len, context)?; - - drop(array); + let new_array = Self::from_kind_and_length(ta.borrow().data.kind(), len, context)?; // 6. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.34. // 7. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: @@ -2584,11 +2304,13 @@ impl BuiltinTypedArray { compare_typed_array_elements(x, y, compare_fn, context) }; + let ta = ta.upcast(); + // 8. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes). - let sorted = Array::sort_indexed_properties(obj, len, sort_compare, false, context)?; + let sorted = Array::sort_indexed_properties(&ta, len, sort_compare, false, context)?; // 9. Let j be 0. - // 10. Repeat, while j < len + // 10. Repeat, while j < len; for (j, item) in sorted.into_iter().enumerate() { // a. Perform ! Set(A, ! ToString(𝔽(j)), sortedList[j], true). new_array @@ -2601,7 +2323,7 @@ impl BuiltinTypedArray { Ok(new_array.into()) } - /// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )` + /// `%TypedArray%.prototype.subarray ( begin, end )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2615,75 +2337,90 @@ impl BuiltinTypedArray { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let src = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; - let o = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let src_borrow = src.borrow(); // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer = o.viewed_array_buffer(); + let buffer = src_borrow.data.viewed_array_buffer().clone(); + + // 5. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // 6. If IsTypedArrayOutOfBounds(srcRecord) is true, then + // a. Let srcLength be 0. + // 7. Else, + // a. Let srcLength be TypedArrayLength(srcRecord). + let src_len = if let Some(buf) = buffer + .as_buffer() + .bytes(Ordering::SeqCst) + .filter(|s| !src_borrow.data.is_out_of_bounds(s.len())) + { + src_borrow.data.array_length(buf.len()) + } else { + 0 + }; - // 5. Let srcLength be O.[[ArrayLength]]. - let src_length = o.array_length() as i64; + let kind = src_borrow.data.kind(); - // 6. Let relativeBegin be ? ToIntegerOrInfinity(begin). - let begin_index = match args.get_or_undefined(0).to_integer_or_infinity(context)? { - // 7. If relativeBegin is -∞, let beginIndex be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 8. Else if relativeBegin < 0, let beginIndex be max(srcLength + relativeBegin, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), - // 9. Else, let beginIndex be min(relativeBegin, srcLength). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), - IntegerOrInfinity::PositiveInfinity => src_length, - }; + // 12. Let elementSize be TypedArrayElementSize(O). + let element_size = kind.element_size(); + + // 13. Let srcByteOffset be O.[[ByteOffset]]. + let src_byte_offset = src_borrow.data.byte_offset(); + + let is_auto_length = src_borrow.data.is_auto_length(); + + drop(src_borrow); + + // 8. Let relativeStart be ? ToIntegerOrInfinity(start). + // 9. If relativeStart = -∞, let startIndex be 0. + // 10. Else if relativeStart < 0, let startIndex be max(srcLength + relativeStart, 0). + // 11. Else, let startIndex be min(relativeStart, srcLength). + let start_index = Array::get_relative_start(context, args.get_or_undefined(0), src_len)?; + + // 14. Let beginByteOffset be srcByteOffset + (startIndex × elementSize). + let begin_byte_offset = src_byte_offset + (start_index * element_size); - // 10. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). let end = args.get_or_undefined(1); - let relative_end = if end.is_undefined() { - IntegerOrInfinity::Integer(src_length) - } else { - end.to_integer_or_infinity(context)? - }; - let end_index = match relative_end { - // 11. If relativeEnd is -∞, let endIndex be 0. - IntegerOrInfinity::NegativeInfinity => 0, - // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). - IntegerOrInfinity::Integer(i) if i < 0 => std::cmp::max(src_length + i, 0), - // 13. Else, let endIndex be min(relativeEnd, srcLength). - IntegerOrInfinity::Integer(i) => std::cmp::min(i, src_length), - IntegerOrInfinity::PositiveInfinity => src_length, - }; + // 15. If O.[[ArrayLength]] is auto and end is undefined, then + if is_auto_length && end.is_undefined() { + // a. Let argumentsList be « buffer, 𝔽(beginByteOffset) ». - // 14. Let newLength be max(endIndex - beginIndex, 0). - let new_length = std::cmp::max(end_index - begin_index, 0); - - // 15. Let constructorName be the String value of O.[[TypedArrayName]]. - // 16. Let elementSize be the Element Size value specified in Table 73 for constructorName. - let element_size = o.kind().element_size(); - - // 17. Let srcByteOffset be O.[[ByteOffset]]. - let src_byte_offset = o.byte_offset(); - - // 18. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. - let begin_byte_offset = src_byte_offset + begin_index as u64 * element_size; - - // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». - // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). - Ok(Self::species_create( - obj, - o.kind(), - &[ - JsObject::from(buffer.clone()).into(), - begin_byte_offset.into(), - new_length.into(), - ], - context, - )? - .into()) + // 17. Return ? TypedArraySpeciesCreate(O, argumentsList). + Ok(Self::species_create( + &src.upcast(), + kind, + &[buffer.into(), begin_byte_offset.into()], + context, + )? + .upcast() + .into()) + } else { + // 16. Else, + // a. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). + // b. If relativeEnd = -∞, let endIndex be 0. + // c. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). + // d. Else, let endIndex be min(relativeEnd, srcLength). + let end_index = Array::get_relative_end(context, end, src_len)?; + + // e. Let newLength be max(endIndex - startIndex, 0). + let new_len = end_index.saturating_sub(start_index); + + // f. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». + Ok(Self::species_create( + &src.upcast(), + kind, + &[buffer.into(), begin_byte_offset.into(), new_len.into()], + context, + )? + .upcast() + .into()) + } } /// `%TypedArray%.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )` @@ -2700,28 +2437,31 @@ impl BuiltinTypedArray { args: &[JsValue], context: &mut Context, ) -> JsResult { - // 1. Let array be ? ToObject(this value). - // Note: ValidateTypedArray is applied to the this value prior to evaluating the algorithm. + // This is a distinct method that implements the same algorithm as Array.prototype.toLocaleString as defined in + // 23.1.3.32 except that TypedArrayLength is called in place of performing a [[Get]] of "length". + let array = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let len = { + let (len, is_fixed_len) = { let o = array.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - if o.is_detached() { + let buf = o.viewed_array_buffer().as_buffer(); + let Some((buf_len, is_fixed_len)) = buf + .bytes(Ordering::SeqCst) + .filter(|s| !o.is_out_of_bounds(s.len())) + .map(|s| (s.len(), buf.is_fixed_len())) + else { return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") + .with_message("typed array is outside the bounds of its inner buffer") .into()); - } + }; - // 2. Let len be array.[[ArrayLength]] - o.array_length() + (o.array_length(buf_len), is_fixed_len) }; - // 3. Let separator be the implementation-defined list-separator String value - // appropriate for the host environment's current locale (such as ", "). let separator = { #[cfg(feature = "intl")] { @@ -2735,24 +2475,18 @@ impl BuiltinTypedArray { } }; - // 4. Let R be the empty String. let mut r = Vec::new(); - // 5. Let k be 0. - // 6. Repeat, while k < len, for k in 0..len { - // a. If k > 0, then if k > 0 { - // i. Set R to the string-concatenation of R and separator. r.extend_from_slice(separator); } - // b. Let nextElement be ? Get(array, ! ToString(k)). let next_element = array.get(k, context)?; - // c. If nextElement is not undefined or null, then - if !next_element.is_null_or_undefined() { - // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). + // Mirrors the behaviour of `join`, but the compiler + // could unswitch the loop using `is_fixed_len`. + if is_fixed_len || !next_element.is_undefined() { let s = next_element .invoke( utf16!("toLocaleString"), @@ -2764,18 +2498,14 @@ impl BuiltinTypedArray { )? .to_string(context)?; - // ii. Set R to the string-concatenation of R and S. r.extend_from_slice(&s); - } - - // d. Increase k by 1. + }; } - // 7. Return R. Ok(js_string!(r).into()) } - /// `23.2.3.31 %TypedArray%.prototype.values ( )` + /// `%TypedArray%.prototype.values ( )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2783,22 +2513,12 @@ impl BuiltinTypedArray { /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.values fn values(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.downcast_ref::() - .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? - .is_detached() - { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Perform ? ValidateTypedArray(O, seq-cst). + let (ta, _) = TypedArray::validate(this, Ordering::SeqCst)?; // 3. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - o.clone(), + ta.upcast(), PropertyNameKind::Value, context, )) @@ -2813,26 +2533,12 @@ impl BuiltinTypedArray { context: &mut Context, ) -> JsResult { // 1. Let O be the this value. - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("TypedArray.sort must be called on typed array object") - })?; + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + let (ta, buf_len) = TypedArray::validate(this, Ordering::SeqCst)?; - // 2. Let iieoRecord be ? ValidateTypedArray(O, seq-cst). - let array = obj.downcast_ref::().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if array.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } - - // 3. Let len be IntegerIndexedObjectLength(iieoRecord). - let len = array.array_length(); - let kind = array.kind(); - - drop(array); + // 3. Let len be TypedArrayLength(taRecord). + let len = ta.borrow().data.array_length(buf_len); + let kind = ta.borrow().data.kind(); // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). // triggers any conversion errors before throwing range errors. @@ -2854,28 +2560,22 @@ impl BuiltinTypedArray { .with_message("invalid integer index for TypedArray operation") .into()); }; - let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0` .ok() .or_else(|| len.checked_add_signed(relative_index)) - .filter(|&rel| rel < len) + // TODO: Replace with `is_valid_integer_index_u64` or equivalent. + .filter(|&rel| is_valid_integer_index(&ta.clone().upcast(), rel as f64)) .ok_or_else(|| { JsNativeError::range() .with_message("invalid integer index for TypedArray operation") })?; - // TODO: Replace with `is_valid_integer_index_u64` or equivalent. - if !is_valid_integer_index(obj, actual_index as f64) { - return Err(JsNativeError::range() - .with_message("invalid integer index for TypedArray operation") - .into()); - } - // 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »). let new_array = Self::from_kind_and_length(kind, len, context)?; // 11. Let k be 0. // 12. Repeat, while k < len, + let ta = ta.upcast(); for k in 0..len { // a. Let Pk be ! ToString(𝔽(k)). let value = if k == actual_index { @@ -2883,7 +2583,7 @@ impl BuiltinTypedArray { numeric_value.clone() } else { // c. Else, let fromValue be ! Get(O, Pk). - obj.get(k, context).expect("cannot fail per the spec") + ta.get(k, context).expect("cannot fail per the spec") }; // d. Perform ! Set(A, Pk, fromValue, true). new_array @@ -2897,7 +2597,7 @@ impl BuiltinTypedArray { Ok(new_array.into()) } - /// `23.2.3.33 get %TypedArray%.prototype [ @@toStringTag ]` + /// `get %TypedArray%.prototype [ @@toStringTag ]` /// /// More information: /// - [ECMAScript reference][spec] @@ -2920,7 +2620,7 @@ impl BuiltinTypedArray { .unwrap_or(JsValue::Undefined)) } - /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` + /// `TypedArraySpeciesCreate ( exemplar, argumentList )` /// /// More information: /// - [ECMAScript reference][spec] @@ -2931,7 +2631,7 @@ impl BuiltinTypedArray { kind: TypedArrayKind, args: &[JsValue], context: &mut Context, - ) -> JsResult { + ) -> JsResult> { // 1. Let defaultConstructor be the intrinsic object listed in column one of Table 73 for exemplar.[[TypedArrayName]]. let default_constructor = kind.standard_constructor(); @@ -2943,13 +2643,7 @@ impl BuiltinTypedArray { // 4. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. // 5. If result.[[ContentType]] ≠ exemplar.[[ContentType]], throw a TypeError exception. - if result - .downcast_ref::() - .expect("This can only be a typed array object") - .kind() - .content_type() - != kind.content_type() - { + if result.borrow().data.kind().content_type() != kind.content_type() { return Err(JsNativeError::typ() .with_message("New typed array has different context type than exemplar") .into()); @@ -2959,49 +2653,49 @@ impl BuiltinTypedArray { Ok(result) } - /// `23.2.4.2 TypedArrayCreate ( constructor, argumentList )` + /// [`TypedArrayCreateFromConstructor ( constructor, argumentList )`][spec]. /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#typedarray-create + /// [spec]: https://tc39.es/ecma262/#sec-typedarraycreatefromconstructor fn create( constructor: &JsObject, args: &[JsValue], context: &mut Context, - ) -> JsResult { + ) -> JsResult> { // 1. Let newTypedArray be ? Construct(constructor, argumentList). let new_typed_array = constructor.construct(args, Some(constructor), context)?; - // 2. Perform ? ValidateTypedArray(newTypedArray). - let o = new_typed_array - .downcast_ref::() - .ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; - if o.is_detached() { - return Err(JsNativeError::typ() - .with_message("Buffer of the typed array is detached") - .into()); - } + // 2. Let taRecord be ? ValidateTypedArray(newTypedArray, seq-cst). + let (new_ta, buf_len) = + TypedArray::validate(&JsValue::Object(new_typed_array), Ordering::SeqCst)?; - // 3. If argumentList is a List of a single Number, then + // 3. If the number of elements in argumentList is 1 and argumentList[0] is a Number, then if args.len() == 1 { if let Some(number) = args[0].as_number() { - // a. If newTypedArray.[[ArrayLength]] < ℝ(argumentList[0]), throw a TypeError exception. - if (o.array_length() as f64) < number { + let new_ta = new_ta.borrow(); + // a. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + if new_ta.data.is_out_of_bounds(buf_len) { return Err(JsNativeError::typ() - .with_message("New typed array length is smaller than expected") + .with_message("new typed array outside of the bounds of its inner buffer") + .into()); + } + + // b. Let length be TypedArrayLength(taRecord). + // c. If length < ℝ(argumentList[0]), throw a TypeError exception. + if (new_ta.data.array_length(buf_len) as f64) < number { + return Err(JsNativeError::typ() + .with_message("new typed array length is smaller than expected") .into()); } } } // 4. Return newTypedArray. - Ok(new_typed_array.clone()) + Ok(new_ta) } - /// + /// [`AllocateTypedArrayBuffer ( O, length )`][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarraybuffer fn allocate_buffer( length: u64, context: &mut Context, @@ -3024,21 +2718,21 @@ impl BuiltinTypedArray { .constructor() .into(), byte_length, + None, context, )?; - // 6. Set O.[[ViewedArrayBuffer]] to data. - // 7. Set O.[[ByteLength]] to byteLength. - // 8. Set O.[[ByteOffset]] to 0. - // 9. Set O.[[ArrayLength]] to length. - // 10. Return O. Ok(TypedArray::new( + // 6. Set O.[[ViewedArrayBuffer]] to data. BufferObject::Buffer(data), T::ERASED, + // 8. Set O.[[ByteOffset]] to 0. 0, - byte_length, - length, + // 7. Set O.[[ByteLength]] to byteLength. + Some(byte_length), + // 9. Set O.[[ArrayLength]] to length. + Some(length), )) } @@ -3108,7 +2802,7 @@ impl BuiltinTypedArray { Ok(obj) } - /// `23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )` + /// `InitializeTypedArrayFromTypedArray ( O, srcArray )` /// /// More information: /// - [ECMAScript reference][spec] @@ -3122,48 +2816,48 @@ impl BuiltinTypedArray { let src_array = src_array.borrow(); let src_array = &src_array.data; + // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. let src_data = src_array.viewed_array_buffer(); let src_data = src_data.as_buffer(); - // 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. - // 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. - let Some(src_data) = src_data.data() else { - return Err(JsNativeError::typ() - .with_message("Cannot initialize typed array from detached buffer") - .into()); - }; - - // 3. Let elementType be TypedArrayElementType(O). + // 2. Let elementType be TypedArrayElementType(O). let element_type = T::ERASED; + // 3. Let elementSize be TypedArrayElementSize(O). + let element_size = element_type.element_size(); - // 4. Let elementSize be TypedArrayElementSize(O). - let target_element_size = element_type.element_size(); - - // 5. Let srcType be TypedArrayElementType(srcArray). + // 4. Let srcType be TypedArrayElementType(srcArray). let src_type = src_array.kind(); - - // 6. Let srcElementSize be TypedArrayElementSize(srcArray). + // 5. Let srcElementSize be TypedArrayElementSize(srcArray). let src_element_size = src_type.element_size(); - - // 7. Let srcByteOffset be srcArray.[[ByteOffset]]. + // 6. Let srcByteOffset be srcArray.[[ByteOffset]]. let src_byte_offset = src_array.byte_offset(); - // 8. Let elementLength be srcArray.[[ArrayLength]]. - let element_length = src_array.array_length(); + // 7. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(srcArray, seq-cst). + // 8. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception. + let Some(src_data) = src_data + .bytes(Ordering::SeqCst) + .filter(|buf| !src_array.is_out_of_bounds(buf.len())) + else { + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from invalid buffer") + .into()); + }; - // 9. Let byteLength be elementSize × elementLength. - let byte_length = target_element_size * element_length; + // 9. Let elementLength be TypedArrayLength(srcRecord). + let element_length = src_array.array_length(src_data.len()); + // 10. Let byteLength be elementSize × elementLength. + let byte_length = element_size * element_length; + + // 11. If elementType is srcType, then - // 10. If elementType is srcType, then let new_buffer = if element_type == src_type { let start = src_byte_offset as usize; - let end = src_byte_offset as usize; + let count = byte_length as usize; // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength). - src_data.subslice(start..start + end).clone(context)? - } - // 11. Else, - else { - // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + src_data.subslice(start..start + count).clone(context)? + } else { + // 12. Else, + // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). let data_obj = ArrayBuffer::allocate( &context .realm() @@ -3173,13 +2867,14 @@ impl BuiltinTypedArray { .constructor() .into(), byte_length, + None, context, )?; { let mut data = data_obj.borrow_mut(); let mut data = SliceRefMut::Slice( data.data - .data_mut() + .bytes_mut() .expect("a new buffer cannot be detached"), ); @@ -3191,7 +2886,7 @@ impl BuiltinTypedArray { } let src_element_size = src_element_size as usize; - let target_element_size = target_element_size as usize; + let target_element_size = element_size as usize; // c. Let srcByteIndex be srcByteOffset. let mut src_byte_index = src_byte_offset as usize; @@ -3204,7 +2899,7 @@ impl BuiltinTypedArray { // f. Repeat, while count > 0, while count > 0 { - // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). + // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, unordered). // SAFETY: All integer indexed objects are always in-bounds and properly // aligned to their underlying buffer. let value = unsafe { @@ -3220,8 +2915,7 @@ impl BuiltinTypedArray { .get_element(&value, context) .expect("value must be bigint or float"); - // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). - + // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, unordered). // SAFETY: The newly created buffer has at least `element_size * element_length` // bytes available, which makes `target_byte_index` always in-bounds. unsafe { @@ -3243,10 +2937,10 @@ impl BuiltinTypedArray { data_obj }; - // 12. Set O.[[ViewedArrayBuffer]] to data. - // 13. Set O.[[ByteLength]] to byteLength. - // 14. Set O.[[ByteOffset]] to 0. - // 15. Set O.[[ArrayLength]] to elementLength. + // 13. Set O.[[ViewedArrayBuffer]] to data. + // 14. Set O.[[ByteLength]] to byteLength. + // 15. Set O.[[ByteOffset]] to 0. + // 16. Set O.[[ArrayLength]] to elementLength. let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, @@ -3254,19 +2948,16 @@ impl BuiltinTypedArray { BufferObject::Buffer(new_buffer), element_type, 0, - byte_length, - element_length, + Some(byte_length), + Some(element_length), ), ); - // 16. Return unused. + // 17. Return unused. Ok(obj) } - /// `23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )` - /// - /// More information: - /// - [ECMAScript reference][spec] + /// [`InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset, length )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer pub(super) fn initialize_from_array_buffer( @@ -3285,11 +2976,14 @@ impl BuiltinTypedArray { // 3. If offset modulo elementSize ≠ 0, throw a RangeError exception. if offset % element_size != 0 { return Err(JsNativeError::range() - .with_message("Invalid offset for typed array") + .with_message("byte offset of typed array must be aligned") .into()); } - // 4. If length is not undefined, then + // 4. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer). + let is_fixed_length = buffer.as_buffer().is_fixed_len(); + + // 5. If length is not undefined, then let new_length = if length.is_undefined() { None } else { @@ -3297,76 +2991,83 @@ impl BuiltinTypedArray { Some(length.to_index(context)?) }; - // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - // 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. let buffer_byte_length = { let buffer = buffer.as_buffer(); - let Some(data) = buffer.data() else { + // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + let Some(data) = buffer.bytes(Ordering::SeqCst) else { return Err(JsNativeError::typ() - .with_message("Cannot construct typed array from detached buffer") + .with_message("cannot construct typed array from detached buffer") .into()); }; + // 7. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst). data.len() as u64 }; - // 7. If length is undefined, then - // 8. Else, - let new_byte_length = if let Some(new_length) = new_length { - // a. Let newByteLength be newLength × elementSize. + let (byte_length, array_length) = if let Some(new_length) = new_length { + // 9. Else, + // b. Else, + // i. Let newByteLength be newLength × elementSize. let new_byte_length = new_length * element_size; - // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. + // ii. If offset + newByteLength > bufferByteLength, throw a RangeError exception. if offset + new_byte_length > buffer_byte_length { return Err(JsNativeError::range() - .with_message("Invalid length for typed array") + .with_message( + "cannot create a typed array spanning a byte range outside of its buffer", + ) .into()); } - new_byte_length + // c. Set O.[[ByteLength]] to newByteLength. + // d. Set O.[[ArrayLength]] to newByteLength / elementSize. + (Some(new_byte_length), Some(new_length)) + } else if !is_fixed_length { + // 8. If length is undefined and bufferIsFixedLength is false, then + // a. If offset > bufferByteLength, throw a RangeError exception. + if offset > buffer_byte_length { + return Err(JsNativeError::range() + .with_message("TypedArray offset outside of buffer length") + .into()); + } + + // b. Set O.[[ByteLength]] to auto. + // c. Set O.[[ArrayLength]] to auto. + (None, None) } else { - // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. + // a. If length is undefined, then + // i. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception. if buffer_byte_length % element_size != 0 { return Err(JsNativeError::range() - .with_message("Invalid length for typed array") + .with_message("cannot construct a typed array with an unaligned buffer") .into()); } - // b. Let newByteLength be bufferByteLength - offset. - let new_byte_length = buffer_byte_length as i64 - offset as i64; - - // c. If newByteLength < 0, throw a RangeError exception. - if new_byte_length < 0 { + // ii. Let newByteLength be bufferByteLength - offset. + // iii. If newByteLength < 0, throw a RangeError exception. + let Some(new_byte_length) = buffer_byte_length.checked_sub(offset) else { return Err(JsNativeError::range() - .with_message("Invalid length for typed array") + .with_message("offset of typed array exceeds buffer size") .into()); - } + }; - new_byte_length as u64 + // c. Set O.[[ByteLength]] to newByteLength. + // d. Set O.[[ArrayLength]] to newByteLength / elementSize. + (Some(new_byte_length), Some(new_byte_length / element_size)) }; - // 9. Set O.[[ViewedArrayBuffer]] to buffer. - // 10. Set O.[[ByteLength]] to newByteLength. + // 10. Set O.[[ViewedArrayBuffer]] to buffer. // 11. Set O.[[ByteOffset]] to offset. - // 12. Set O.[[ArrayLength]] to newByteLength / elementSize. - let obj = JsObject::from_proto_and_data_with_shared_shape( + // 12. Return unused. + Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, - TypedArray::new( - buffer, - T::ERASED, - offset, - new_byte_length, - new_byte_length / element_size, - ), - ); - - // 13. Return unused. - Ok(obj) + TypedArray::new(buffer, T::ERASED, offset, byte_length, array_length), + )) } - /// `23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )` + /// `InitializeTypedArrayFromArrayLike ( O, arrayLike )` /// /// More information: /// - [ECMAScript reference][spec] @@ -3498,6 +3199,8 @@ fn compare_typed_array_elements( /// Abstract operation `IsValidIntegerIndex ( O, index )`. /// +/// Returns `true` if the index is valid, or `false` otherwise. +/// /// More information: /// - [ECMAScript reference][spec] /// @@ -3507,26 +3210,15 @@ pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool { "integer indexed exotic method should only be callable from integer indexed objects", ); - // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. - if inner.is_detached() { - return false; - } + let buf = inner.viewed_array_buffer(); + let buf = buf.as_buffer(); - // 2. If IsIntegralNumber(index) is false, return false. - if index.is_nan() || index.is_infinite() || index.fract() != 0.0 { - return false; - } - - // 3. If index is -0𝔽, return false. - if index == 0.0 && index.is_sign_negative() { - return false; - } - - // 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false. - if index < 0.0 || index >= inner.array_length() as f64 { + // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, unordered). + // 5. NOTE: Bounds checking is not a synchronizing operation when O's backing buffer is a growable SharedArrayBuffer. + let Some(buf_len) = buf.bytes(Ordering::Relaxed).map(|s| s.len()) else { return false; - } + }; - // 5. Return true. - true + inner.validate_index(index, buf_len).is_some() } diff --git a/core/engine/src/builtins/typed_array/mod.rs b/core/engine/src/builtins/typed_array/mod.rs index 34b6924610..a2b20fe51e 100644 --- a/core/engine/src/builtins/typed_array/mod.rs +++ b/core/engine/src/builtins/typed_array/mod.rs @@ -511,11 +511,11 @@ pub(crate) enum TypedArrayElement { } impl TypedArrayElement { - /// Converts the element into its extended bytes representation as a `u64`. + /// Converts the element into its extended bytes representation as an `u64`. /// /// This is guaranteed to never fail, since all numeric types supported by JS are less than /// 8 bytes long. - pub(crate) fn to_bytes(self) -> u64 { + pub(crate) fn to_bits(self) -> u64 { #[allow(clippy::cast_lossless)] match self { TypedArrayElement::Int8(num) => num as u64, diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 86881399bb..078698a353 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -1,6 +1,6 @@ //! This module implements the `TypedArray` exotic object. -use std::sync::atomic; +use std::sync::atomic::{self, Ordering}; use crate::{ builtins::{array_buffer::BufferObject, Number}, @@ -13,7 +13,7 @@ use crate::{ JsData, JsObject, }, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, JsString, JsValue, + Context, JsNativeError, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_macros::utf16; @@ -32,8 +32,8 @@ pub struct TypedArray { viewed_array_buffer: BufferObject, kind: TypedArrayKind, byte_offset: u64, - byte_length: u64, - array_length: u64, + byte_length: Option, + array_length: Option, } impl JsData for TypedArray { @@ -58,8 +58,8 @@ impl TypedArray { viewed_array_buffer: BufferObject, kind: TypedArrayKind, byte_offset: u64, - byte_length: u64, - array_length: u64, + byte_length: Option, + array_length: Option, ) -> Self { Self { viewed_array_buffer, @@ -70,16 +70,42 @@ impl TypedArray { } } - /// Abstract operation `IsDetachedBuffer ( arrayBuffer )`. - /// - /// Check if `[[ArrayBufferData]]` is null. - /// - /// More information: - /// - [ECMAScript reference][spec] + /// Returns `true` if the typed array has an automatic array length. + pub(crate) fn is_auto_length(&self) -> bool { + self.array_length.is_none() + } + + /// Abstract operation [`IsTypedArrayOutOfBounds ( taRecord )`][spec]. /// - /// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer - pub(crate) fn is_detached(&self) -> bool { - self.viewed_array_buffer.as_buffer().is_detached() + /// [spec]: https://tc39.es/ecma262/sec-istypedarrayoutofbounds + pub(crate) fn is_out_of_bounds(&self, buf_byte_len: usize) -> bool { + // Checks when allocating the buffer ensure the length fits inside an `u64`. + let buf_byte_len = buf_byte_len as u64; + + // 1. Let O be taRecord.[[Object]]. + // 2. Let bufferByteLength be taRecord.[[CachedBufferByteLength]]. + // 3. Assert: IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached. + // 4. If bufferByteLength is detached, return true. + // Handled by the caller + + // 5. Let byteOffsetStart be O.[[ByteOffset]]. + let byte_start = self.byte_offset; + + // 6. If O.[[ArrayLength]] is auto, then + // a. Let byteOffsetEnd be bufferByteLength. + let byte_end = self.array_length.map_or(buf_byte_len, |arr_len| { + // 7. Else, + // a. Let elementSize be TypedArrayElementSize(O). + let element_size = self.kind.element_size(); + + // b. Let byteOffsetEnd be byteOffsetStart + O.[[ArrayLength]] × elementSize. + byte_start + arr_len * element_size + }); + + // 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true. + // 9. NOTE: 0-length TypedArrays are not considered out-of-bounds. + // 10. Return false. + byte_start > buf_byte_len || byte_end > buf_byte_len } /// Get the `TypedArray` object's byte offset. @@ -99,16 +125,133 @@ impl TypedArray { &self.viewed_array_buffer } + /// [`TypedArrayByteLength ( taRecord )`][spec]. + /// /// Get the `TypedArray` object's byte length. + /// + /// [spec]: https://tc39.es/ecma262/#sec-typedarraybytelength #[must_use] - pub const fn byte_length(&self) -> u64 { - self.byte_length + pub fn byte_length(&self, buf_byte_len: usize) -> u64 { + // 1. If IsTypedArrayOutOfBounds(taRecord) is true, return 0. + if self.is_out_of_bounds(buf_byte_len) { + return 0; + } + + // 2. Let length be TypedArrayLength(taRecord). + let length = self.array_length(buf_byte_len); + // 3. If length = 0, return 0. + if length == 0 { + return 0; + } + + // 4. Let O be taRecord.[[Object]]. + + // 5. If O.[[ByteLength]] is not auto, return O.[[ByteLength]]. + if let Some(byte_length) = self.byte_length { + return byte_length; + } + + // 6. Let elementSize be TypedArrayElementSize(O). + let elem_size = self.kind.element_size(); + + // 7. Return length × elementSize. + // Should not overflow thanks to the checks at creation time. + length * elem_size } + /// [`TypedArrayLength ( taRecord )`][spec]. + /// /// Get the `TypedArray` object's array length. + /// + /// [spec]: https://tc39.es/ecma262/#sec-typedarraylength #[must_use] - pub const fn array_length(&self) -> u64 { - self.array_length + pub fn array_length(&self, buf_byte_len: usize) -> u64 { + // 1. Assert: IsTypedArrayOutOfBounds(taRecord) is false. + debug_assert!(!self.is_out_of_bounds(buf_byte_len)); + let buf_byte_len = buf_byte_len as u64; + + // 2. Let O be taRecord.[[Object]]. + + // 3. If O.[[ArrayLength]] is not auto, return O.[[ArrayLength]]. + if let Some(array_length) = self.array_length { + return array_length; + } + // 4. Assert: IsFixedLengthArrayBuffer(O.[[ViewedArrayBuffer]]) is false. + + // 5. Let byteOffset be O.[[ByteOffset]]. + let byte_offset = self.byte_offset; + // 6. Let elementSize be TypedArrayElementSize(O). + let elem_size = self.kind.element_size(); + + // 7. Let byteLength be taRecord.[[CachedBufferByteLength]]. + // 8. Assert: byteLength is not detached. + // 9. Return floor((byteLength - byteOffset) / elementSize). + (buf_byte_len - byte_offset) / elem_size + } + + /// Abstract operation [`ValidateTypedArray ( O, order )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/sec-validatetypedarray + pub(crate) fn validate(this: &JsValue, order: Ordering) -> JsResult<(JsObject, usize)> { + // 1. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + let obj = this + .as_object() + .and_then(|o| o.clone().downcast::().ok()) + .ok_or_else(|| { + JsNativeError::typ().with_message("`this` is not a typed array object") + })?; + + let len = { + let array = obj.borrow(); + let buffer = array.data.viewed_array_buffer().as_buffer(); + // 2. Assert: O has a [[ViewedArrayBuffer]] internal slot. + // 3. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, order). + // 4. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + let Some(buf) = buffer + .bytes(order) + .filter(|buf| !array.data.is_out_of_bounds(buf.len())) + else { + return Err(JsNativeError::typ() + .with_message("typed array is outside the bounds of its inner buffer") + .into()); + }; + buf.len() + }; + + // 5. Return taRecord. + Ok((obj, len)) + } + + /// Validates `index` to be in bounds for the inner buffer of this `TypedArray`. + /// + /// Note: if this is only used for bounds checking, it is recommended to use + /// the `Ordering::Relaxed` ordering to get the buffer slice. + pub(crate) fn validate_index(&self, index: f64, buf_len: usize) -> Option { + // 2. If IsIntegralNumber(index) is false, return false. + if index.is_nan() || index.is_infinite() || index.fract() != 0.0 { + return None; + } + + // 3. If index is -0𝔽, return false. + if index == 0.0 && index.is_sign_negative() { + return None; + } + + // 6. If IsTypedArrayOutOfBounds(taRecord) is true, return false. + if self.is_out_of_bounds(buf_len) { + return None; + } + + // 7. Let length be TypedArrayLength(taRecord). + let length = self.array_length(buf_len); + + // 8. If ℝ(index) < 0 or ℝ(index) ≥ length, return false. + if index < 0.0 || index >= length as f64 { + return None; + } + + // 9. Return true. + Some(index as u64) } } @@ -393,24 +536,32 @@ pub(crate) fn typed_array_exotic_own_property_keys( .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() { - vec![] - } else { - // 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then - // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do - // i. Add ! ToString(𝔽(i)) as the last element of keys. - (0..inner.array_length()).map(PropertyKey::from).collect() + // 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + // 2. Let keys be a new empty List. + // 3. If IsTypedArrayOutOfBounds(taRecord) is false, then + let mut keys = match inner + .viewed_array_buffer + .as_buffer() + .bytes(Ordering::SeqCst) + { + Some(buf) if !inner.is_out_of_bounds(buf.len()) => { + // a. Let length be TypedArrayLength(taRecord). + let length = inner.array_length(buf.len()); + + // b. For each integer i such that 0 ≤ i < length, in ascending order, do + // i. Append ! ToString(𝔽(i)) to keys. + (0..length).map(PropertyKey::from).collect() + } + _ => Vec::new(), }; - // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - // - // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. + // 4. For each own property key P of O such that P is a String and P is not an integer index, in ascending chronological order of property creation, do + // a. Append P to keys. + // 5. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do + // a. Append P to keys. keys.extend(obj.properties.shape.keys()); - // 5. Return keys. + // 6. Return keys. Ok(keys) } @@ -421,40 +572,38 @@ pub(crate) fn typed_array_exotic_own_property_keys( /// /// [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; - } - let inner = obj .downcast_ref::() .expect("Must be an TypedArray object"); let buffer = inner.viewed_array_buffer(); let buffer = buffer.as_buffer(); - let buffer = buffer - .data() - .expect("already checked that it's not detached"); + + // 1. If IsValidIntegerIndex(O, index) is false, return undefined. + let Some(buffer) = buffer.bytes(Ordering::Relaxed) else { + return None; + }; + let Some(index) = inner.validate_index(index, buffer.len()) else { + return None; + }; // 2. Let offset be O.[[ByteOffset]]. let offset = inner.byte_offset(); - // 3. Let arrayTypeName be the String value of O.[[TypedArrayName]]. - // 6. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.kind(); - - // 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. - let size = elem_type.element_size(); + // 3. Let elementSize be TypedArrayElementSize(O). + let size = inner.kind.element_size(); - // 5. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = ((index as u64 * size) + offset) as usize; + // 4. Let byteIndexInBuffer be (ℝ(index) × elementSize) + offset. + let byte_index = ((index * size) + offset) as usize; - // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). + // 5. Let elementType be TypedArrayElementType(O). + let elem_type = inner.kind(); + // 6. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], byteIndexInBuffer, elementType, true, unordered). // 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 - .subslice(indexed_position..) + .subslice(byte_index..) .get_value(elem_type, atomic::Ordering::Relaxed) }; @@ -473,49 +622,47 @@ pub(crate) fn typed_array_set_element( value: &JsValue, context: &mut InternalMethodContext<'_>, ) -> JsResult<()> { - let obj_borrow = obj.borrow(); - let inner = obj_borrow - .downcast_ref::() - .expect("TypedArray exotic method should only be callable from TypedArray objects"); + let obj = obj + .clone() + .downcast::() + .expect("function can only be called for typed array objects"); + + // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = obj.borrow().data.kind(); // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). // 2. Otherwise, let numValue be ? ToNumber(value). - let value = inner.kind().get_element(value, context)?; + let value = elem_type.get_element(value, context)?; - if !is_valid_integer_index(obj, index) { + // 3. If IsValidIntegerIndex(O, index) is true, then + let array = obj.borrow(); + let mut buffer = array.data.viewed_array_buffer().as_buffer_mut(); + let Some(mut buffer) = buffer.bytes(Ordering::Relaxed) else { return Ok(()); - } - - // 3. If ! IsValidIntegerIndex(O, index) is true, then - // a. Let offset be O.[[ByteOffset]]. - let offset = inner.byte_offset(); + }; + let Some(index) = array.data.validate_index(index, buffer.len()) else { + return Ok(()); + }; - // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. - // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.kind(); + // a. Let offset be O.[[ByteOffset]]. + let offset = array.data.byte_offset(); - // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + // b. Let elementSize be TypedArrayElementSize(O). let size = elem_type.element_size(); - // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = ((index as u64 * size) + offset) as usize; - - let buffer = inner.viewed_array_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). + // c. Let byteIndexInBuffer be (ℝ(index) × elementSize) + offset. + let byte_index = ((index * size) + offset) as usize; + // e. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], byteIndexInBuffer, elementType, numValue, true, unordered). // SAFETY: The TypedArray object guarantees that the buffer is aligned. - // The call to `is_valid_integer_index` guarantees that the index is in-bounds. + // The call to `validate_index` guarantees that the index is in-bounds. unsafe { buffer - .subslice_mut(indexed_position..) + .subslice_mut(byte_index..) .set_value(value, atomic::Ordering::Relaxed); } - // 4. Return NormalCompletion(undefined). + // 4. Return unused. Ok(()) } diff --git a/core/engine/src/context/hooks.rs b/core/engine/src/context/hooks.rs index 3912a6f86f..31f66eb35b 100644 --- a/core/engine/src/context/hooks.rs +++ b/core/engine/src/context/hooks.rs @@ -210,7 +210,7 @@ pub trait HostHooks { /// exhaust the virtual memory address space and to reduce interoperability risk. /// /// - /// [specification]: https://tc39.es/ecma262/multipage/structured-data.html#sec-resizable-arraybuffer-guidelines + /// [specification]: https://tc39.es/ecma262/#sec-resizable-arraybuffer-guidelines fn max_buffer_size(&self, _context: &mut Context) -> u64 { 1_610_612_736 // 1.5 GiB } diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index e378ccde07..339be87925 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -61,6 +61,7 @@ impl JsArrayBuffer { .constructor() .into(), byte_length as u64, + None, context, )?; @@ -236,7 +237,7 @@ impl JsArrayBuffer { #[inline] #[must_use] pub fn data(&self) -> Option> { - GcRef::try_map(self.inner.borrow(), |o| o.data.data()) + GcRef::try_map(self.inner.borrow(), |o| o.data.bytes()) } /// Get a mutable reference to the [`JsArrayBuffer`]'s data. @@ -269,7 +270,7 @@ impl JsArrayBuffer { #[inline] #[must_use] pub fn data_mut(&self) -> Option, [u8]>> { - GcRefMut::try_map(self.inner.borrow_mut(), |o| o.data.data_mut()) + GcRefMut::try_map(self.inner.borrow_mut(), |o| o.data.bytes_mut()) } } diff --git a/core/engine/src/object/builtins/jsdataview.rs b/core/engine/src/object/builtins/jsdataview.rs index 4e6bcdfcd4..c1194dc013 100644 --- a/core/engine/src/object/builtins/jsdataview.rs +++ b/core/engine/src/object/builtins/jsdataview.rs @@ -1,10 +1,7 @@ //! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object use crate::{ builtins::{array_buffer::BufferObject, DataView}, - context::intrinsics::StandardConstructors, - object::{ - internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType, - }, + object::{JsArrayBuffer, JsObject, JsObjectType}, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, }; @@ -55,66 +52,101 @@ impl From> for JsDataView { impl JsDataView { /// Create a new `JsDataView` object from an existing `JsArrayBuffer`. pub fn from_js_array_buffer( - array_buffer: JsArrayBuffer, + buffer: JsArrayBuffer, offset: Option, - byte_length: Option, + byte_len: Option, context: &mut Context, ) -> JsResult { - let (byte_offset, byte_length) = { - let buffer = array_buffer.borrow(); - let provided_offset = offset.unwrap_or(0_u64); + let offset = offset.unwrap_or_default(); - // Check if buffer is detached. - if buffer.data.is_detached() { + let (buf_byte_len, is_fixed_len) = { + let buffer = buffer.borrow(); + let buffer = &buffer.data; + + // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + let Some(slice) = buffer.bytes() else { return Err(JsNativeError::typ() .with_message("ArrayBuffer is detached") .into()); }; - let array_buffer_length = buffer.data.len() as u64; + // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst). + let buf_len = slice.len() as u64; - if provided_offset > array_buffer_length { + // 6. If offset > bufferByteLength, throw a RangeError exception. + if offset > buf_len { return Err(JsNativeError::range() - .with_message("Provided offset is outside the bounds of the buffer") + .with_message("Start offset is outside the bounds of the buffer") .into()); } - let view_byte_length = if let Some(provided_length) = byte_length { - // Check that the provided length and offset does not exceed the bounds of the ArrayBuffer - if provided_offset + provided_length > array_buffer_length { - return Err(JsNativeError::range() - .with_message("Invalid data view length") - .into()); - } - - provided_length - } else { - array_buffer_length - provided_offset - }; + // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer). + (buf_len, buffer.is_fixed_len()) + }; + + // 8. If byteLength is undefined, then + let view_byte_len = if let Some(byte_len) = byte_len { + // 9. Else, + // a. Let viewByteLength be ? ToIndex(byteLength). + // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. + if offset + byte_len > buf_byte_len { + return Err(JsNativeError::range() + .with_message("Invalid data view length") + .into()); + } - (provided_offset, view_byte_length) + Some(byte_len) + } else { + // a. If bufferIsFixedLength is true, then + // i. Let viewByteLength be bufferByteLength - offset. + // b. Else, + // i. Let viewByteLength be auto. + is_fixed_len.then_some(buf_byte_len - offset) }; - let constructor = context - .intrinsics() - .constructors() - .data_view() - .constructor() - .into(); + // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", + // « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). + let prototype = context.intrinsics().constructors().data_view().prototype(); - let prototype = - get_prototype_from_constructor(&constructor, StandardConstructors::data_view, context)?; + // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. + // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst). + let Some(buf_byte_len) = buffer.borrow().data.bytes().map(|s| s.len() as u64) else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer is detached") + .into()); + }; + + // 13. If offset > bufferByteLength, throw a RangeError exception. + if offset > buf_byte_len { + return Err(JsNativeError::range() + .with_message("DataView offset outside of buffer array bounds") + .into()); + } + + // 14. If byteLength is not undefined, then + if let Some(view_byte_len) = view_byte_len.filter(|_| byte_len.is_some()) { + // a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. + if offset + view_byte_len > buf_byte_len { + return Err(JsNativeError::range() + .with_message("DataView offset outside of buffer array bounds") + .into()); + } + } let obj = JsObject::new( context.root_shape(), prototype, DataView { - viewed_array_buffer: BufferObject::Buffer(array_buffer.into()), - byte_length, - byte_offset, + // 15. Set O.[[ViewedArrayBuffer]] to buffer. + viewed_array_buffer: BufferObject::Buffer(buffer.into()), + // 16. Set O.[[ByteLength]] to viewByteLength. + byte_length: view_byte_len, + // 17. Set O.[[ByteOffset]] to offset. + byte_offset: offset, }, ); + // 18. Return O. Ok(Self { inner: obj }) } diff --git a/core/engine/src/object/builtins/jssharedarraybuffer.rs b/core/engine/src/object/builtins/jssharedarraybuffer.rs index 1a892a6f14..e051dc160c 100644 --- a/core/engine/src/object/builtins/jssharedarraybuffer.rs +++ b/core/engine/src/object/builtins/jssharedarraybuffer.rs @@ -7,7 +7,7 @@ use crate::{ Context, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; -use std::ops::Deref; +use std::{ops::Deref, sync::atomic::Ordering}; /// `JsSharedArrayBuffer` provides a wrapper for Boa's implementation of the ECMAScript `ArrayBuffer` object #[derive(Debug, Clone, Trace, Finalize)] @@ -42,6 +42,7 @@ impl JsSharedArrayBuffer { .constructor() .into(), byte_length as u64, + None, context, )?; @@ -83,7 +84,7 @@ impl JsSharedArrayBuffer { #[inline] #[must_use] pub fn byte_length(&self) -> usize { - self.borrow().data.len() + self.borrow().data.len(Ordering::SeqCst) } /// Gets the raw buffer of this `JsSharedArrayBuffer`. diff --git a/test262_config.toml b/test262_config.toml index 6efeff356f..e98f217bff 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -9,7 +9,6 @@ features = [ "FinalizationRegistry", "IsHTMLDDA", - "resizable-arraybuffer", "symbols-as-weakmap-keys", "intl-normative-optional", "Intl.DisplayNames",