diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 6d57ec67e2..afd4c192fc 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -379,7 +379,7 @@ impl ArrayBuffer { self.array_buffer_data.is_none() } - /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor )` + /// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength )` /// /// More information: /// - [ECMAScript reference][spec] @@ -389,39 +389,51 @@ impl ArrayBuffer { &self, src_byte_offset: u64, src_length: u64, - clone_constructor: &JsValue, context: &mut Context<'_>, ) -> JsResult { - // 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength). - let target_buffer = Self::allocate(clone_constructor, src_length, context)?; + // 1. Assert: IsDetachedBuffer(srcBuffer) is false. + debug_assert!(!self.is_detached_buffer()); + + // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). + let target_buffer = Self::allocate( + &context + .realm() + .intrinsics() + .constructors() + .array_buffer() + .constructor() + .into(), + src_length, + context, + )?; - // 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. - let src_block = self.array_buffer_data.as_deref().ok_or_else(|| { - JsNativeError::syntax().with_message("Cannot clone detached array buffer") - })?; - - { - // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. - let mut target_buffer_mut = target_buffer.borrow_mut(); - let target_block = target_buffer_mut - .as_array_buffer_mut() - .expect("This must be an ArrayBuffer"); - - // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). - copy_data_block_bytes( - target_block - .array_buffer_data - .as_mut() - .expect("ArrayBuffer cannot me detached here"), - 0, - src_block, - src_byte_offset as usize, - src_length as usize, - ); - } + let src_block = self + .array_buffer_data + .as_deref() + .expect("ArrayBuffer cannot be detached"); + + // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. + let mut target_buffer_mut = target_buffer.borrow_mut(); + let target_array_buffer = target_buffer_mut + .as_array_buffer_mut() + .expect("This must be an ArrayBuffer"); + let target_block = target_array_buffer + .array_buffer_data + .as_mut() + .expect("ArrayBuffer cannot me detached here"); + + // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). + copy_data_block_bytes( + target_block, + 0, + src_block, + src_byte_offset as usize, + src_length as usize, + ); // 6. Return targetBuffer. + drop(target_buffer_mut); Ok(target_buffer) } diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 9e61735854..f7cea50170 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -184,7 +184,7 @@ macro_rules! typed_array { let first_argument_v = JsValue::from(first_argument.clone()); let using_iterator = - first_argument_v.get_method(JsSymbol::replace(), context)?; + first_argument_v.get_method(JsSymbol::iterator(), context)?; // 3. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { @@ -567,6 +567,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; + drop(obj_borrow); + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; @@ -919,6 +921,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, @@ -1030,6 +1034,8 @@ impl TypedArray { .into()); } + drop(obj_borrow); + // 15. Repeat, while k < final, while k < r#final { // a. Let Pk be ! ToString(๐”ฝ(k)). @@ -1071,9 +1077,13 @@ impl TypedArray { .into()); } + let typed_array_name = o.typed_array_name(); + // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1118,7 +1128,7 @@ impl TypedArray { } // 9. Let A be ? TypedArraySpeciesCreate(O, ยซ ๐”ฝ(captured) ยป). - let a = Self::species_create(obj, o.typed_array_name(), &[captured.into()], context)?; + let a = Self::species_create(obj, typed_array_name, &[captured.into()], context)?; // 10. Let n be 0. // 11. For each element e of kept, do @@ -1162,6 +1172,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); @@ -1209,6 +1221,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); @@ -1350,6 +1364,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1409,6 +1425,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; + drop(obj_borrow); + // 4. If len is 0, return false. if len == 0 { return Ok(false.into()); @@ -1488,6 +1506,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; + drop(obj_borrow); + // 4. If len is 0, return -1๐”ฝ. if len == 0 { return Ok((-1).into()); @@ -1576,6 +1596,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If separator is undefined, let sep be the single-element String ",". let separator = args.get_or_undefined(0); let sep = if separator.is_undefined() { @@ -1673,6 +1695,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; + drop(obj_borrow); + // 4. If len is 0, return -1๐”ฝ. if len == 0 { return Ok((-1).into()); @@ -1779,9 +1803,13 @@ impl TypedArray { .into()); } + let typed_array_name = o.typed_array_name(); + // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, @@ -1795,7 +1823,7 @@ impl TypedArray { }; // 5. Let A be ? TypedArraySpeciesCreate(O, ยซ ๐”ฝ(len) ยป). - let a = Self::species_create(obj, o.typed_array_name(), &[len.into()], context)?; + let a = Self::species_create(obj, typed_array_name, &[len.into()], context)?; // 6. Let k be 0. // 7. Repeat, while k < len, @@ -1848,6 +1876,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1933,6 +1963,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, @@ -2021,6 +2053,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as f64; + drop(obj_borrow); + // 4. Let middle be floor(len / 2). let middle = (len / 2.0).floor(); @@ -2143,7 +2177,8 @@ impl TypedArray { } let target_buffer_obj = target_array .viewed_array_buffer() - .expect("Already checked for detached buffer"); + .expect("Already checked for detached buffer") + .clone(); // 3. Let targetLength be target.[[ArrayLength]]. let target_length = target_array.array_length(); @@ -2170,6 +2205,8 @@ impl TypedArray { // 9. Let targetByteOffset be target.[[ByteOffset]]. let target_byte_offset = target_array.byte_offset(); + drop(target_borrow); + // 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_name = source_array.typed_array_name(); @@ -2216,7 +2253,7 @@ impl TypedArray { // a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. // 19. Else, let same be SameValue(srcBuffer, targetBuffer). - let same = JsObject::equals(&src_buffer_obj, target_buffer_obj); + let same = JsObject::equals(&src_buffer_obj, &target_buffer_obj); // 20. If same is true, then let mut src_byte_index = if same { @@ -2225,22 +2262,11 @@ impl TypedArray { // 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. - let array_buffer_constructor = context - .intrinsics() - .constructors() - .array_buffer() - .constructor() - .into(); let s = src_buffer_obj .borrow() .as_array_buffer() .expect("Already checked for detached buffer") - .clone_array_buffer( - src_byte_offset, - src_byte_length, - &array_buffer_constructor, - context, - )?; + .clone_array_buffer(src_byte_offset, src_byte_length, context)?; src_buffer_obj = s; // d. Let srcByteIndex be 0. @@ -2251,6 +2277,8 @@ impl TypedArray { src_byte_offset }; + drop(source_borrow); + // 22. Let targetByteIndex be targetOffset ร— targetElementSize + targetByteOffset. let mut target_byte_index = target_offset * target_element_size + target_byte_offset; @@ -2373,6 +2401,8 @@ impl TypedArray { // 7. Let targetByteOffset be target.[[ByteOffset]]. let target_byte_offset = target_array.byte_offset(); + drop(target_borrow); + // 8. Let src be ? ToObject(source). let src = source.to_object(context)?; @@ -2420,6 +2450,10 @@ impl TypedArray { value.to_number(context)?.into() }; + let target_borrow = target.borrow(); + let target_array = target_borrow + .as_typed_array() + .expect("Target must be a typed array"); let target_buffer_obj = target_array .viewed_array_buffer() .expect("Already checked for detached buffer"); @@ -2534,13 +2568,21 @@ impl TypedArray { // 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.typed_array_name(); + // 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.typed_array_name(); + // f. If srcType is different from targetType, then #[allow(clippy::if_not_else)] - if o.typed_array_name() != a_array.typed_array_name() { + if src_type != target_type { + drop(obj_borrow); + drop(a_borrow); + // i. Let n be 0. let mut n = 0; + // ii. Repeat, while k < final, while k < r#final { // 1. Let Pk be ! ToString(๐”ฝ(k)). @@ -2619,11 +2661,14 @@ impl TypedArray { // 4. Set targetByteIndex to targetByteIndex + 1. target_byte_index += 1; } + + drop(target_buffer_obj_borrow); + drop(a_borrow); } + } else { + drop(a_borrow); } - drop(a_borrow); - // 15. Return A. Ok(a.into()) } @@ -2657,6 +2702,8 @@ impl TypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); + drop(obj_borrow); + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { Some(obj) if obj.is_callable() => obj, @@ -3291,86 +3338,82 @@ impl TypedArray { .with_message("Cannot initialize typed array from detached buffer") .into()); } - let src_data_obj = src_array - .viewed_array_buffer() - .expect("Already checked for detached buffer"); - // 3. Let constructorName be the String value of O.[[TypedArrayName]]. - // 4. Let elementType be the Element Type value in Table 73 for constructorName. - // 10. Let elementSize be the Element Size value specified in Table 73 for constructorName. - let constructor_name = o_array.typed_array_name(); + // 3. Let elementType be TypedArrayElementType(O). + let element_type = o_array.typed_array_name(); - // 5. Let elementLength be srcArray.[[ArrayLength]]. - let element_length = src_array.array_length(); + // 4. Let elementSize be TypedArrayElementSize(O). + let element_size = element_type.element_size(); - // 6. Let srcName be the String value of srcArray.[[TypedArrayName]]. - // 7. Let srcType be the Element Type value in Table 73 for srcName. - // 8. Let srcElementSize be the Element Size value specified in Table 73 for srcName. - let src_name = src_array.typed_array_name(); + // 5. Let srcType be TypedArrayElementType(srcArray). + let src_type = src_array.typed_array_name(); - // 9. Let srcByteOffset be srcArray.[[ByteOffset]]. + // 6. Let srcElementSize be TypedArrayElementSize(srcArray). + let src_element_size = src_type.element_size(); + + // 7. Let srcByteOffset be srcArray.[[ByteOffset]]. let src_byte_offset = src_array.byte_offset(); - // 11. Let byteLength be elementSize ร— elementLength. - let byte_length = constructor_name.element_size() * element_length; + // 8. Let elementLength be srcArray.[[ArrayLength]]. + let element_length = src_array.array_length(); - // 12. If IsSharedArrayBuffer(srcData) is false, then - // a. Let bufferConstructor be ? SpeciesConstructor(srcData, %ArrayBuffer%). - // TODO: Shared Array Buffer - // 13. Else, - // a. Let bufferConstructor be %ArrayBuffer%. - let buffer_constructor = - src_data_obj.species_constructor(StandardConstructors::array_buffer, context)?; + // 9. Let byteLength be elementSize ร— elementLength. + let byte_length = element_size * element_length; + let src_data_obj = src_array + .viewed_array_buffer() + .expect("Already checked for detached buffer"); let src_data_obj_b = src_data_obj.borrow(); let src_data = src_data_obj_b .as_array_buffer() .expect("Already checked for detached buffer"); - // 14. If elementType is the same as srcType, then - let data = if constructor_name == src_name { - // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength, bufferConstructor). - src_data.clone_array_buffer( - src_byte_offset, + // 10. If elementType is srcType, then + let data = if element_type == src_type { + // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength). + src_data.clone_array_buffer(src_byte_offset, byte_length, context)? + } + // 11. Else, + else { + // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength). + let data_obj = ArrayBuffer::allocate( + &context + .realm() + .intrinsics() + .constructors() + .array_buffer() + .constructor() + .into(), byte_length, - &buffer_constructor.into(), context, - )? - // 15. Else, - } else { - // a. Let data be ? AllocateArrayBuffer(bufferConstructor, byteLength). - let data_obj = ArrayBuffer::allocate(&buffer_constructor.into(), byte_length, context)?; + )?; let mut data_obj_b = data_obj.borrow_mut(); let data = data_obj_b .as_array_buffer_mut() .expect("Must be ArrayBuffer"); - // b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. - if src_data.is_detached_buffer() { - return Err(JsNativeError::typ() - .with_message("Cannot initialize typed array from detached buffer") - .into()); - } - - // c. If srcArray.[[ContentType]] โ‰  O.[[ContentType]], throw a TypeError exception. - if src_name.content_type() != constructor_name.content_type() { + // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. + if src_type.content_type() != element_type.content_type() { return Err(JsNativeError::typ() .with_message("Cannot initialize typed array from different content type") .into()); } - // d. Let srcByteIndex be srcByteOffset. + // c. Let srcByteIndex be srcByteOffset. let mut src_byte_index = src_byte_offset; - // e. Let targetByteIndex be 0. + + // d. Let targetByteIndex be 0. let mut target_byte_index = 0; - // f. Let count be elementLength. + + // e. Let count be elementLength. let mut count = element_length; - // g. Repeat, while count > 0, + + // f. Repeat, while count > 0, while count > 0 { // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). let value = src_data.get_value_from_buffer( src_byte_index, - src_name, + src_type, true, SharedMemoryOrder::Unordered, None, @@ -3379,7 +3422,7 @@ impl TypedArray { // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). data.set_value_in_buffer( target_byte_index, - constructor_name, + element_type, &value, SharedMemoryOrder::Unordered, None, @@ -3387,31 +3430,33 @@ impl TypedArray { )?; // iii. Set srcByteIndex to srcByteIndex + srcElementSize. - src_byte_index += src_name.element_size(); + src_byte_index += src_element_size; // iv. Set targetByteIndex to targetByteIndex + elementSize. - target_byte_index += constructor_name.element_size(); + target_byte_index += element_size; // v. Set count to count - 1. count -= 1; } + drop(data_obj_b); data_obj }; - // 16. Set O.[[ViewedArrayBuffer]] to data. - // 17. Set O.[[ByteLength]] to byteLength. - // 18. Set O.[[ByteOffset]] to 0. - // 19. Set O.[[ArrayLength]] to elementLength. + // 12. Set O.[[ViewedArrayBuffer]] to data. + // 13. Set O.[[ByteLength]] to byteLength. + // 14. Set O.[[ByteOffset]] to 0. + // 15. Set O.[[ArrayLength]] to elementLength. drop(o_obj); *o.borrow_mut().kind_mut() = ObjectKind::IntegerIndexed(IntegerIndexed::new( Some(data), - constructor_name, + element_type, 0, byte_length, element_length, )); + // 16. Return unused. Ok(()) } @@ -3428,45 +3473,66 @@ impl TypedArray { length: &JsValue, context: &mut Context<'_>, ) -> JsResult<()> { - // 1. Let constructorName be the String value of O.[[TypedArrayName]]. - // 2. Let elementSize be the Element Size value specified in Table 73 for constructorName. - let constructor_name = o + // 1. Let elementSize be TypedArrayElementSize(O). + let element_size = o .borrow() .as_typed_array() - .expect("This must be a typed array") - .typed_array_name(); + .expect("Must be a typed array") + .typed_array_name() + .element_size(); - // 3. Let offset be ? ToIndex(byteOffset). + // 2. Let offset be ? ToIndex(byteOffset). let offset = byte_offset.to_index(context)?; - // 4. If offset modulo elementSize โ‰  0, throw a RangeError exception. - if offset % constructor_name.element_size() != 0 { + // 3. If offset modulo elementSize โ‰  0, throw a RangeError exception. + if offset % element_size != 0 { return Err(JsNativeError::range() .with_message("Invalid length for typed array") .into()); } + // 4. If length is not undefined, then + let new_length = if length.is_undefined() { + None + } else { + // a. Let newLength be ? ToIndex(length). + 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_obj_b = buffer.borrow(); - let buffer_array = buffer_obj_b + let buffer_borrow = buffer.borrow(); + let buffer_array = buffer_borrow .as_array_buffer() - .expect("This must be an ArrayBuffer"); + .expect("Must be an ArrayBuffer"); - // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. if buffer_array.is_detached_buffer() { return Err(JsNativeError::typ() .with_message("Cannot construct typed array from detached buffer") .into()); } - // 7. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. buffer_array.array_buffer_byte_length() }; - // 8. If length is undefined, then - let new_byte_length = if length.is_undefined() { + // 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 new_byte_length = new_length * element_size; + + // b. 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") + .into()); + } + + new_byte_length + } else { // a. If bufferByteLength modulo elementSize โ‰  0, throw a RangeError exception. - if buffer_byte_length % constructor_name.element_size() != 0 { + if buffer_byte_length % element_size != 0 { return Err(JsNativeError::range() .with_message("Invalid length for typed array") .into()); @@ -3483,38 +3549,23 @@ impl TypedArray { } new_byte_length as u64 - // 9. Else, - } else { - // 5. If length is not undefined, then - // a. Let newLength be ? ToIndex(length). - - // a. Let newByteLength be newLength ร— elementSize. - let new_byte_length = length.to_index(context)? * constructor_name.element_size(); - - // b. 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") - .into()); - } - - new_byte_length }; - let mut o_obj_borrow = o.borrow_mut(); - let o = o_obj_borrow + let mut o_borrow = o.borrow_mut(); + let o = o_borrow .as_typed_array_mut() .expect("This must be an ArrayBuffer"); - // 10. Set O.[[ViewedArrayBuffer]] to buffer. + // 9. Set O.[[ViewedArrayBuffer]] to buffer. o.set_viewed_array_buffer(Some(buffer)); - // 11. Set O.[[ByteLength]] to newByteLength. + // 10. Set O.[[ByteLength]] to newByteLength. o.set_byte_length(new_byte_length); - // 12. Set O.[[ByteOffset]] to offset. + // 11. Set O.[[ByteOffset]] to offset. o.set_byte_offset(offset); - // 13. Set O.[[ArrayLength]] to newByteLength / elementSize. - o.set_array_length(new_byte_length / constructor_name.element_size()); + // 12. Set O.[[ArrayLength]] to newByteLength / elementSize. + o.set_array_length(new_byte_length / element_size); + // 13. Return unused. Ok(()) } diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs index e5fb7909db..c73a4a146a 100644 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ b/boa_engine/src/object/internal_methods/integer_indexed.rs @@ -1,10 +1,12 @@ use boa_macros::utf16; use crate::{ - builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType}, + builtins::{ + array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType, Number, + }, object::JsObject, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, JsValue, + Context, JsResult, JsString, JsValue, }; use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; @@ -27,6 +29,30 @@ pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods ..ORDINARY_INTERNAL_METHODS }; +/// `CanonicalNumericIndexString ( argument )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-canonicalnumericindexstring +fn canonical_numeric_index_string(argument: &JsString) -> Option { + // 1. If argument is "-0", return -0๐”ฝ. + if argument == utf16!("-0") { + return Some(-0.0); + } + + // 2. Let n be ! ToNumber(argument). + let n = argument.to_number(); + + // 3. If ! ToString(n) is argument, return n. + if &JsString::from(Number::to_native_string(n)) == argument { + return Some(n); + } + + // 4. Return undefined. + None +} + /// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects. /// /// More information: @@ -38,33 +64,35 @@ pub(crate) fn integer_indexed_exotic_get_own_property( key: &PropertyKey, context: &mut Context<'_>, ) -> JsResult> { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - // b. If numericIndex is not undefined, then - match key { - PropertyKey::Index(index) => { - // i. Let value be ! IntegerIndexedElementGet(O, numericIndex). - // ii. If value is undefined, return undefined. - // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. - Ok( - integer_indexed_element_get(obj, u64::from(*index)).map(|v| { - PropertyDescriptor::builder() - .value(v) - .writable(true) - .enumerable(true) - .configurable(true) - .build() - }), - ) - } - // The following step is taken from https://tc39.es/ecma262/#sec-isvalidintegerindex : - // Step 3. If index is -0๐”ฝ, return false. - PropertyKey::String(string) if string == utf16!("-0") => Ok(None), - key => { - // 2. Return OrdinaryGetOwnProperty(O, P). - super::ordinary_get_own_property(obj, key, context) + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) } + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. Let value be IntegerIndexedElementGet(O, numericIndex). + let value = integer_indexed_element_get(obj, numeric_index); + + // ii. If value is undefined, return undefined. + // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + return Ok(value.map(|v| { + PropertyDescriptor::builder() + .value(v) + .writable(true) + .enumerable(true) + .configurable(true) + .build() + })); } + + // 2. Return OrdinaryGetOwnProperty(O, P). + super::ordinary_get_own_property(obj, key, context) } /// `[[HasProperty]]` internal method for Integer-Indexed exotic objects. @@ -78,21 +106,23 @@ pub(crate) fn integer_indexed_exotic_has_property( key: &PropertyKey, context: &mut Context<'_>, ) -> JsResult { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - match key { - PropertyKey::Index(index) => { - // b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex). - Ok(is_valid_integer_index(obj, u64::from(*index))) - } - // The following step is taken from https://tc39.es/ecma262/#sec-isvalidintegerindex : - // Step 3. If index is -0๐”ฝ, return false. - PropertyKey::String(string) if string == utf16!("-0") => Ok(false), - key => { - // 2. Return ? OrdinaryHasProperty(O, P). - super::ordinary_has_property(obj, key, context) + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) } + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, return IsValidIntegerIndex(O, numericIndex). + if let Some(numeric_index) = p { + return Ok(is_valid_integer_index(obj, numeric_index)); } + + // 2. Return ? OrdinaryHasProperty(O, P). + super::ordinary_has_property(obj, key, context) } /// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects. @@ -107,41 +137,54 @@ pub(crate) fn integer_indexed_exotic_define_own_property( desc: PropertyDescriptor, context: &mut Context<'_>, ) -> JsResult { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - // b. If numericIndex is not undefined, then - match key { - &PropertyKey::Index(index) => { - // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. - // ii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, return false. - // iii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false. - // v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false. - // iv. If ! IsAccessorDescriptor(Desc) is true, return false. - if !is_valid_integer_index(obj, u64::from(index)) - || !desc - .configurable() - .or_else(|| desc.enumerable()) - .or_else(|| desc.writable()) - .unwrap_or(true) - || desc.is_accessor_descriptor() - { - return Ok(false); - } - - // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). - if let Some(value) = desc.value() { - integer_indexed_element_set(obj, index as usize, value, context)?; - } - - // vii. Return true. - Ok(true) + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If IsValidIntegerIndex(O, numericIndex) is false, return false. + if !is_valid_integer_index(obj, numeric_index) { + return Ok(false); + } + + // ii. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is false, return false. + if desc.configurable() == Some(false) { + return Ok(false); + } + + // iii. If Desc has an [[Enumerable]] field and Desc.[[Enumerable]] is false, return false. + if desc.enumerable() == Some(false) { + return Ok(false); + } + + // iv. If IsAccessorDescriptor(Desc) is true, return false. + if desc.is_accessor_descriptor() { + return Ok(false); + } + + // v. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, return false. + if desc.writable() == Some(false) { + return Ok(false); } - PropertyKey::String(string) if string == utf16!("-0") => Ok(false), - key => { - // 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc). - super::ordinary_define_own_property(obj, key, desc, context) + + // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). + if let Some(value) = desc.value() { + integer_indexed_element_set(obj, numeric_index, value, context)?; } + + // vii. Return true. + return Ok(true); } + + // 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + super::ordinary_define_own_property(obj, key, desc, context) } /// Internal method `[[Get]]` for Integer-Indexed exotic objects. @@ -156,22 +199,24 @@ pub(crate) fn integer_indexed_exotic_get( receiver: JsValue, context: &mut Context<'_>, ) -> JsResult { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - // b. If numericIndex is not undefined, then - match key { - PropertyKey::Index(index) => { - // i. Return ! IntegerIndexedElementGet(O, numericIndex). - Ok(integer_indexed_element_get(obj, u64::from(*index)).unwrap_or_default()) - } - // The following step is taken from https://tc39.es/ecma262/#sec-isvalidintegerindex : - // Step 3. If index is -0๐”ฝ, return false. - PropertyKey::String(string) if string == utf16!("-0") => Ok(JsValue::undefined()), - key => { - // 2. Return ? OrdinaryGet(O, P, Receiver). - super::ordinary_get(obj, key, receiver, context) + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) } + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. Return IntegerIndexedElementGet(O, numericIndex). + return Ok(integer_indexed_element_get(obj, numeric_index).unwrap_or_default()); } + + // 2. Return ? OrdinaryGet(O, P, Receiver). + super::ordinary_get(obj, key, receiver, context) } /// Internal method `[[Set]]` for Integer-Indexed exotic objects. @@ -187,25 +232,35 @@ pub(crate) fn integer_indexed_exotic_set( receiver: JsValue, context: &mut Context<'_>, ) -> JsResult { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - // b. If numericIndex is not undefined, then - match key { - PropertyKey::Index(index) => { - // i. Perform ? IntegerIndexedElementSet(O, numericIndex, V). - integer_indexed_element_set(obj, index as usize, &value, context)?; - - // ii. Return true. - Ok(true) + let p = match &key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) } - // The following step is taken from https://tc39.es/ecma262/#sec-isvalidintegerindex : - // Step 3. If index is -0๐”ฝ, return false. - PropertyKey::String(string) if &string == utf16!("-0") => Ok(false), - key => { - // 2. Return ? OrdinarySet(O, P, V, Receiver). - super::ordinary_set(obj, key, value, receiver, context) + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If SameValue(O, Receiver) is true, then + if JsValue::same_value(&obj.clone().into(), &receiver) { + // 1. Perform ? IntegerIndexedElementSet(O, numericIndex, V). + integer_indexed_element_set(obj, numeric_index, &value, context)?; + + // 2. Return true. + return Ok(true); + } + + // ii. If IsValidIntegerIndex(O, numericIndex) is false, return true. + if !is_valid_integer_index(obj, numeric_index) { + return Ok(true); } } + + // 2. Return ? OrdinarySet(O, P, V, Receiver). + super::ordinary_set(obj, key, value, receiver, context) } /// Internal method `[[Delete]]` for Integer-Indexed exotic objects. @@ -219,37 +274,24 @@ pub(crate) fn integer_indexed_exotic_delete( key: &PropertyKey, context: &mut Context<'_>, ) -> JsResult { - // 1. If Type(P) is String, then - // a. Let numericIndex be ! CanonicalNumericIndexString(P). - // b. If numericIndex is not undefined, then - match key { - PropertyKey::Index(index) => { - // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. - Ok(!is_valid_integer_index(obj, u64::from(*index))) - } - // The following step is taken from https://tc39.es/ecma262/#sec-isvalidintegerindex : - // Step 3. If index is -0๐”ฝ, return false. - PropertyKey::String(string) if string == utf16!("-0") => { - let obj = obj.borrow(); - let inner = obj.as_typed_array().expect( - "integer indexed exotic method should only be callable from integer indexed objects", - ); - // 1. If IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. - // From IsValidIntegerIndex: - // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. - // 3. If index is -0๐”ฝ, return false. - // - // NOTE: They are negated, so it should return true. - if inner.is_detached() { - return Ok(true); - } - Ok(true) - } - key => { - // 2. Return ? OrdinaryDelete(O, P). - super::ordinary_delete(obj, key, context) + let p = match &key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) } + PropertyKey::Index(index) => Some((*index).into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. + return Ok(!is_valid_integer_index(obj, numeric_index)); } + + // 2. Return ! OrdinaryDelete(O, P). + super::ordinary_delete(obj, key, context) } /// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects. @@ -297,25 +339,34 @@ pub(crate) fn integer_indexed_exotic_own_property_keys( /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex -pub(crate) fn is_valid_integer_index(obj: &JsObject, index: u64) -> bool { +pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool { let obj = obj.borrow(); let inner = obj.as_typed_array().expect( "integer indexed exotic method should only be callable from integer indexed objects", ); + // 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. - // - // SKIPPED: 2. If ! IsIntegralNumber(index) is false, return false. - // NOTE: This step has already been done when we construct a PropertyKey. - // - // MOVED: 3. If index is -0๐”ฝ, return false. - // NOTE: This step has been moved into the callers of this functions, - // once we get the index it is already converted into unsigned integer - // index, it cannot be `-0`. + if inner.is_detached() { + return false; + } + + // 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. - // 5. Return true. + if index < 0.0 || index >= inner.array_length() as f64 { + return false; + } - !inner.is_detached() && index < inner.array_length() + // 5. Return true. + true } /// Abstract operation `IntegerIndexedElementGet ( O, index )`. @@ -324,7 +375,7 @@ pub(crate) fn is_valid_integer_index(obj: &JsObject, index: u64) -> bool { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget -fn integer_indexed_element_get(obj: &JsObject, index: u64) -> Option { +fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { // 1. If ! IsValidIntegerIndex(O, index) is false, return undefined. if !is_valid_integer_index(obj, index) { return None; @@ -353,7 +404,7 @@ fn integer_indexed_element_get(obj: &JsObject, index: u64) -> Option { let size = elem_type.element_size(); // 5. Let indexedPosition be (โ„(index) ร— elementSize) + offset. - let indexed_position = (index * size) + offset; + let indexed_position = (index as u64 * size) + offset; // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). Some(buffer.get_value_from_buffer( @@ -373,7 +424,7 @@ fn integer_indexed_element_get(obj: &JsObject, index: u64) -> Option { /// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset fn integer_indexed_element_set( obj: &JsObject, - index: usize, + index: f64, value: &JsValue, context: &mut Context<'_>, ) -> JsResult<()> { @@ -391,7 +442,7 @@ fn integer_indexed_element_set( }; // 3. If ! IsValidIntegerIndex(O, index) is true, then - if is_valid_integer_index(obj, index as u64) { + if is_valid_integer_index(obj, index) { // a. Let offset be O.[[ByteOffset]]. let offset = inner.byte_offset();