mirror of https://github.com/boa-dev/boa.git
Browse Source
Co-authored-by: raskad <32105367+raskad@users.noreply.github.com> Co-authored-by: jedel1043 <jedel0124@gmail.com>pull/1567/head
Iban Eguia
3 years ago
committed by
GitHub
40 changed files with 5789 additions and 344 deletions
@ -0,0 +1,789 @@
|
||||
use crate::{ |
||||
builtins::{typed_array::TypedArrayName, BuiltIn, JsArgs}, |
||||
context::StandardObjects, |
||||
gc::{Finalize, Trace}, |
||||
object::{ |
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, |
||||
JsObject, ObjectData, |
||||
}, |
||||
profiler::BoaProfiler, |
||||
property::Attribute, |
||||
symbol::WellKnownSymbols, |
||||
value::{IntegerOrInfinity, Numeric}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use num_traits::{Signed, ToPrimitive}; |
||||
use std::convert::TryInto; |
||||
|
||||
#[derive(Debug, Clone, Trace, Finalize)] |
||||
pub struct ArrayBuffer { |
||||
pub array_buffer_data: Option<Vec<u8>>, |
||||
pub array_buffer_byte_length: usize, |
||||
pub array_buffer_detach_key: JsValue, |
||||
} |
||||
|
||||
impl ArrayBuffer { |
||||
pub(crate) fn array_buffer_byte_length(&self) -> usize { |
||||
self.array_buffer_byte_length |
||||
} |
||||
} |
||||
|
||||
impl BuiltIn for ArrayBuffer { |
||||
const NAME: &'static str = "ArrayBuffer"; |
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::WRITABLE |
||||
.union(Attribute::NON_ENUMERABLE) |
||||
.union(Attribute::CONFIGURABLE); |
||||
|
||||
fn init(context: &mut Context) -> JsValue { |
||||
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let get_species = FunctionBuilder::native(context, Self::get_species) |
||||
.name("get [Symbol.species]") |
||||
.constructable(false) |
||||
.build(); |
||||
|
||||
ConstructorBuilder::with_standard_object( |
||||
context, |
||||
Self::constructor, |
||||
context.standard_objects().array_buffer_object().clone(), |
||||
) |
||||
.name(Self::NAME) |
||||
.length(Self::LENGTH) |
||||
.static_accessor( |
||||
WellKnownSymbols::species(), |
||||
Some(get_species), |
||||
None, |
||||
Attribute::CONFIGURABLE, |
||||
) |
||||
.static_method(Self::is_view, "isView", 1) |
||||
.method(Self::byte_length, "byteLength", 0) |
||||
.method(Self::slice, "slice", 2) |
||||
.property( |
||||
WellKnownSymbols::to_string_tag(), |
||||
Self::NAME, |
||||
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
||||
) |
||||
.build() |
||||
.into() |
||||
} |
||||
} |
||||
|
||||
impl ArrayBuffer { |
||||
const LENGTH: usize = 1; |
||||
|
||||
/// `25.1.3.1 ArrayBuffer ( length )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer-length
|
||||
fn constructor( |
||||
new_target: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() { |
||||
return context |
||||
.throw_type_error("ArrayBuffer.constructor called with undefined new target"); |
||||
} |
||||
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
let byte_length = args.get_or_undefined(0).to_index(context)?; |
||||
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength).
|
||||
Ok(Self::allocate(new_target, byte_length, context)?.into()) |
||||
} |
||||
|
||||
/// `25.1.4.3 get ArrayBuffer [ @@species ]`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer-@@species
|
||||
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { |
||||
// 1. Return the this value.
|
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// `25.1.4.1 ArrayBuffer.isView ( arg )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview
|
||||
fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult<JsValue> { |
||||
// 1. If Type(arg) is not Object, return false.
|
||||
// 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true.
|
||||
// 3. Return false.
|
||||
Ok(args |
||||
.get_or_undefined(0) |
||||
.as_object() |
||||
.map(|obj| obj.borrow().is_typed_array()) |
||||
.unwrap_or_default() |
||||
.into()) |
||||
} |
||||
|
||||
/// `25.1.5.1 get ArrayBuffer.prototype.byteLength`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength
|
||||
fn byte_length(this: &JsValue, _args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() { |
||||
obj |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer.byteLength called with non-object value"); |
||||
}; |
||||
let obj = obj.borrow(); |
||||
let o = if let Some(o) = obj.as_array_buffer() { |
||||
o |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer.byteLength called with invalid object"); |
||||
}; |
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
if Self::is_detached_buffer(o) { |
||||
return Ok(0.into()); |
||||
} |
||||
|
||||
// 5. Let length be O.[[ArrayBufferByteLength]].
|
||||
// 6. Return 𝔽(length).
|
||||
Ok(o.array_buffer_byte_length.into()) |
||||
} |
||||
|
||||
/// `25.1.5.3 ArrayBuffer.prototype.slice ( start, end )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
|
||||
fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||
// 1. Let O be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
let obj = if let Some(obj) = this.as_object() { |
||||
obj |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer.slice called with non-object value"); |
||||
}; |
||||
let obj_borrow = obj.borrow(); |
||||
let o = if let Some(o) = obj_borrow.as_array_buffer() { |
||||
o |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer.slice called with invalid object"); |
||||
}; |
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
|
||||
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) { |
||||
return context.throw_type_error("ArrayBuffer.slice called with detached buffer"); |
||||
} |
||||
|
||||
// 5. Let len be O.[[ArrayBufferByteLength]].
|
||||
let len = o.array_buffer_byte_length as i64; |
||||
|
||||
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
let relative_start = args.get_or_undefined(0).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 => std::cmp::max(len + i, 0), |
||||
// 9. Else, let first be min(relativeStart, len).
|
||||
IntegerOrInfinity::Integer(i) => std::cmp::min(i, len), |
||||
IntegerOrInfinity::PositiveInfinity => len, |
||||
}; |
||||
|
||||
// 10. 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)? |
||||
}; |
||||
|
||||
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. Let newLen be max(final - first, 0).
|
||||
let new_len = std::cmp::max(r#final - first, 0) as usize; |
||||
|
||||
// 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%).
|
||||
let ctor = obj.species_constructor(StandardObjects::array_buffer_object, context)?; |
||||
|
||||
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
|
||||
let new = Self::constructor(&ctor.into(), &[new_len.into()], context)?; |
||||
|
||||
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
|
||||
let new_obj = if let Some(obj) = new.as_object() { |
||||
obj |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer constructor returned non-object value"); |
||||
}; |
||||
let mut new_obj_borrow = new_obj.borrow_mut(); |
||||
let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() { |
||||
b |
||||
} else { |
||||
return context.throw_type_error("ArrayBuffer constructor returned invalid object"); |
||||
}; |
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
|
||||
|
||||
// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(new_array_buffer) { |
||||
return context |
||||
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer"); |
||||
} |
||||
|
||||
// 20. If SameValue(new, O) is true, throw a TypeError exception.
|
||||
if JsValue::same_value(&new, this) { |
||||
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); |
||||
} |
||||
|
||||
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
|
||||
if new_array_buffer.array_buffer_byte_length < new_len { |
||||
return context.throw_type_error("New ArrayBuffer length too small"); |
||||
} |
||||
|
||||
// 22. NOTE: Side-effects of the above steps may have detached O.
|
||||
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if Self::is_detached_buffer(o) { |
||||
return context |
||||
.throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running"); |
||||
} |
||||
|
||||
// 24. Let fromBuf be O.[[ArrayBufferData]].
|
||||
let from_buf = o |
||||
.array_buffer_data |
||||
.as_ref() |
||||
.expect("ArrayBuffer cannot be detached here"); |
||||
|
||||
// 25. Let toBuf be new.[[ArrayBufferData]].
|
||||
let to_buf = new_array_buffer |
||||
.array_buffer_data |
||||
.as_mut() |
||||
.expect("ArrayBuffer cannot be detached here"); |
||||
|
||||
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len); |
||||
|
||||
// 27. Return new.
|
||||
Ok(new) |
||||
} |
||||
|
||||
/// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-allocatearraybuffer
|
||||
pub(crate) fn allocate( |
||||
constructor: &JsValue, |
||||
byte_length: usize, |
||||
context: &mut Context, |
||||
) -> JsResult<JsObject> { |
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »).
|
||||
let prototype = get_prototype_from_constructor( |
||||
constructor, |
||||
StandardObjects::array_buffer_object, |
||||
context, |
||||
)?; |
||||
let obj = context.construct_object(); |
||||
obj.set_prototype_instance(prototype.into()); |
||||
|
||||
// 2. Let block be ? CreateByteDataBlock(byteLength).
|
||||
// TODO: for now just a arbitrary limit to not OOM.
|
||||
if byte_length > 8589934592 { |
||||
return Err(context.construct_range_error("ArrayBuffer allocation failed")); |
||||
} |
||||
let block = vec![0; byte_length]; |
||||
|
||||
// 3. Set obj.[[ArrayBufferData]] to block.
|
||||
// 4. Set obj.[[ArrayBufferByteLength]] to byteLength.
|
||||
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { |
||||
array_buffer_data: Some(block), |
||||
array_buffer_byte_length: byte_length, |
||||
array_buffer_detach_key: JsValue::Undefined, |
||||
}); |
||||
|
||||
// 5. Return obj.
|
||||
Ok(obj) |
||||
} |
||||
|
||||
/// `25.1.2.2 IsDetachedBuffer ( arrayBuffer )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
pub(crate) fn is_detached_buffer(&self) -> bool { |
||||
// 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
|
||||
// 2. Return false.
|
||||
self.array_buffer_data.is_none() |
||||
} |
||||
|
||||
/// `25.1.2.4 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-clonearraybuffer
|
||||
pub(crate) fn clone_array_buffer( |
||||
&self, |
||||
src_byte_offset: usize, |
||||
src_length: usize, |
||||
clone_constructor: &JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsObject> { |
||||
// 1. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength).
|
||||
let target_buffer = Self::allocate(clone_constructor, src_length, context)?; |
||||
|
||||
// 2. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
|
||||
// 3. Let srcBlock be srcBuffer.[[ArrayBufferData]].
|
||||
let src_block = if let Some(b) = &self.array_buffer_data { |
||||
b |
||||
} else { |
||||
return Err(context.construct_syntax_error("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, |
||||
src_length, |
||||
); |
||||
} |
||||
|
||||
// 6. Return targetBuffer.
|
||||
Ok(target_buffer) |
||||
} |
||||
|
||||
/// `25.1.2.6 IsUnclampedIntegerElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isunclampedintegerelementtype
|
||||
fn is_unclamped_integer_element_type(t: TypedArrayName) -> bool { |
||||
// 1. If type is Int8, Uint8, Int16, Uint16, Int32, or Uint32, return true.
|
||||
// 2. Return false.
|
||||
matches!( |
||||
t, |
||||
TypedArrayName::Int8Array |
||||
| TypedArrayName::Uint8Array |
||||
| TypedArrayName::Int16Array |
||||
| TypedArrayName::Uint16Array |
||||
| TypedArrayName::Int32Array |
||||
| TypedArrayName::Uint32Array |
||||
) |
||||
} |
||||
|
||||
/// `25.1.2.7 IsBigIntElementType ( type )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isbigintelementtype
|
||||
fn is_big_int_element_type(t: TypedArrayName) -> bool { |
||||
// 1. If type is BigUint64 or BigInt64, return true.
|
||||
// 2. Return false.
|
||||
matches!( |
||||
t, |
||||
TypedArrayName::BigUint64Array | TypedArrayName::BigInt64Array |
||||
) |
||||
} |
||||
|
||||
/// `25.1.2.8 IsNoTearConfiguration ( type, order )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isnotearconfiguration
|
||||
// TODO: Allow unused function until shared array buffers are implemented.
|
||||
#[allow(dead_code)] |
||||
fn is_no_tear_configuration(t: TypedArrayName, order: SharedMemoryOrder) -> bool { |
||||
// 1. If ! IsUnclampedIntegerElementType(type) is true, return true.
|
||||
if Self::is_unclamped_integer_element_type(t) { |
||||
return true; |
||||
} |
||||
|
||||
// 2. If ! IsBigIntElementType(type) is true and order is not Init or Unordered, return true.
|
||||
if Self::is_big_int_element_type(t) |
||||
&& !matches!( |
||||
order, |
||||
SharedMemoryOrder::Init | SharedMemoryOrder::Unordered |
||||
) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
// 3. Return false.
|
||||
false |
||||
} |
||||
|
||||
/// `25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-rawbytestonumeric
|
||||
fn raw_bytes_to_numeric(t: TypedArrayName, bytes: &[u8], is_little_endian: bool) -> JsValue { |
||||
let n: Numeric = match t { |
||||
TypedArrayName::Int8Array => { |
||||
if is_little_endian { |
||||
i8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() |
||||
} else { |
||||
i8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() |
||||
} |
||||
} |
||||
TypedArrayName::Uint8Array | TypedArrayName::Uint8ClampedArray => { |
||||
if is_little_endian { |
||||
u8::from_le_bytes(bytes.try_into().expect("slice with incorrect length")).into() |
||||
} else { |
||||
u8::from_be_bytes(bytes.try_into().expect("slice with incorrect length")).into() |
||||
} |
||||
} |
||||
TypedArrayName::Int16Array => { |
||||
if is_little_endian { |
||||
i16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
i16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::Uint16Array => { |
||||
if is_little_endian { |
||||
u16::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
u16::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::Int32Array => { |
||||
if is_little_endian { |
||||
i32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
i32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::Uint32Array => { |
||||
if is_little_endian { |
||||
u32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
u32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::BigInt64Array => { |
||||
if is_little_endian { |
||||
i64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
i64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::BigUint64Array => { |
||||
if is_little_endian { |
||||
u64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
u64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::Float32Array => { |
||||
if is_little_endian { |
||||
f32::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
f32::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
TypedArrayName::Float64Array => { |
||||
if is_little_endian { |
||||
f64::from_le_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} else { |
||||
f64::from_be_bytes(bytes.try_into().expect("slice with incorrect length")) |
||||
.into() |
||||
} |
||||
} |
||||
}; |
||||
|
||||
n.into() |
||||
} |
||||
|
||||
/// `25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getvaluefrombuffer
|
||||
pub(crate) fn get_value_from_buffer( |
||||
&self, |
||||
byte_index: usize, |
||||
t: TypedArrayName, |
||||
_is_typed_array: bool, |
||||
_order: SharedMemoryOrder, |
||||
is_little_endian: Option<bool>, |
||||
) -> JsValue { |
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self |
||||
.array_buffer_data |
||||
.as_ref() |
||||
.expect("ArrayBuffer cannot be detached here"); |
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
let element_size = t.element_size(); |
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 5. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 6. Else, let rawValue be a List whose elements are bytes from block at indices byteIndex (inclusive) through byteIndex + elementSize (exclusive).
|
||||
// 7. Assert: The number of elements in rawValue is elementSize.
|
||||
let raw_value = &block[byte_index..byte_index + element_size]; |
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true); |
||||
|
||||
// 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian).
|
||||
Self::raw_bytes_to_numeric(t, raw_value, is_little_endian) |
||||
} |
||||
|
||||
/// `25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-numerictorawbytes
|
||||
fn numeric_to_raw_bytes( |
||||
t: TypedArrayName, |
||||
value: JsValue, |
||||
is_little_endian: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<Vec<u8>> { |
||||
Ok(match t { |
||||
TypedArrayName::Int8Array if is_little_endian => { |
||||
value.to_int8(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Int8Array => value.to_int8(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::Uint8Array if is_little_endian => { |
||||
value.to_uint8(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Uint8Array => value.to_uint8(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::Uint8ClampedArray if is_little_endian => { |
||||
value.to_uint8_clamp(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Uint8ClampedArray => { |
||||
value.to_uint8_clamp(context)?.to_be_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Int16Array if is_little_endian => { |
||||
value.to_int16(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Int16Array => value.to_int16(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::Uint16Array if is_little_endian => { |
||||
value.to_uint16(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Uint16Array => value.to_uint16(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::Int32Array if is_little_endian => { |
||||
value.to_i32(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Int32Array => value.to_i32(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::Uint32Array if is_little_endian => { |
||||
value.to_u32(context)?.to_le_bytes().to_vec() |
||||
} |
||||
TypedArrayName::Uint32Array => value.to_u32(context)?.to_be_bytes().to_vec(), |
||||
TypedArrayName::BigInt64Array if is_little_endian => { |
||||
let big_int = value.to_big_int64(context)?; |
||||
big_int |
||||
.to_i64() |
||||
.unwrap_or_else(|| { |
||||
if big_int.is_positive() { |
||||
i64::MAX |
||||
} else { |
||||
i64::MIN |
||||
} |
||||
}) |
||||
.to_le_bytes() |
||||
.to_vec() |
||||
} |
||||
TypedArrayName::BigInt64Array => { |
||||
let big_int = value.to_big_int64(context)?; |
||||
big_int |
||||
.to_i64() |
||||
.unwrap_or_else(|| { |
||||
if big_int.is_positive() { |
||||
i64::MAX |
||||
} else { |
||||
i64::MIN |
||||
} |
||||
}) |
||||
.to_be_bytes() |
||||
.to_vec() |
||||
} |
||||
TypedArrayName::BigUint64Array if is_little_endian => value |
||||
.to_big_uint64(context)? |
||||
.to_u64() |
||||
.unwrap_or(u64::MAX) |
||||
.to_le_bytes() |
||||
.to_vec(), |
||||
TypedArrayName::BigUint64Array => value |
||||
.to_big_uint64(context)? |
||||
.to_u64() |
||||
.unwrap_or(u64::MAX) |
||||
.to_be_bytes() |
||||
.to_vec(), |
||||
TypedArrayName::Float32Array => match value.to_number(context)? { |
||||
f if is_little_endian => (f as f32).to_le_bytes().to_vec(), |
||||
f => (f as f32).to_be_bytes().to_vec(), |
||||
}, |
||||
TypedArrayName::Float64Array => match value.to_number(context)? { |
||||
f if is_little_endian => f.to_le_bytes().to_vec(), |
||||
f => f.to_be_bytes().to_vec(), |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
/// `25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-setvalueinbuffer
|
||||
pub(crate) fn set_value_in_buffer( |
||||
&mut self, |
||||
byte_index: usize, |
||||
t: TypedArrayName, |
||||
value: JsValue, |
||||
_order: SharedMemoryOrder, |
||||
is_little_endian: Option<bool>, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
|
||||
// 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
|
||||
// 3. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number.
|
||||
// 4. Let block be arrayBuffer.[[ArrayBufferData]].
|
||||
let block = self |
||||
.array_buffer_data |
||||
.as_mut() |
||||
.expect("ArrayBuffer cannot be detached here"); |
||||
|
||||
// 5. Let elementSize be the Element Size value specified in Table 73 for Element Type type.
|
||||
|
||||
// TODO: Agent Record [[LittleEndian]] filed
|
||||
// 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
|
||||
let is_little_endian = is_little_endian.unwrap_or(true); |
||||
|
||||
// 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
|
||||
let raw_bytes = Self::numeric_to_raw_bytes(t, value, is_little_endian, context)?; |
||||
|
||||
// TODO: Shared Array Buffer
|
||||
// 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
|
||||
|
||||
// 9. Else, store the individual bytes of rawBytes into block, starting at block[byteIndex].
|
||||
for (i, raw_byte) in raw_bytes.iter().enumerate() { |
||||
block[byte_index + i] = *raw_byte; |
||||
} |
||||
|
||||
// 10. Return NormalCompletion(undefined).
|
||||
Ok(JsValue::undefined()) |
||||
} |
||||
} |
||||
|
||||
/// `6.2.8.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-copydatablockbytes
|
||||
fn copy_data_block_bytes( |
||||
to_block: &mut Vec<u8>, |
||||
mut to_index: usize, |
||||
from_block: &[u8], |
||||
mut from_index: usize, |
||||
mut count: usize, |
||||
) { |
||||
// 1. Assert: fromBlock and toBlock are distinct values.
|
||||
// 2. Let fromSize be the number of bytes in fromBlock.
|
||||
let from_size = from_block.len(); |
||||
|
||||
// 3. Assert: fromIndex + count ≤ fromSize.
|
||||
assert!(from_index + count <= from_size); |
||||
|
||||
// 4. Let toSize be the number of bytes in toBlock.
|
||||
let to_size = to_block.len(); |
||||
|
||||
// 5. Assert: toIndex + count ≤ toSize.
|
||||
assert!(to_index + count <= to_size); |
||||
|
||||
// 6. Repeat, while count > 0,
|
||||
while count > 0 { |
||||
// a. If fromBlock is a Shared Data Block, then
|
||||
// TODO: Shared Data Block
|
||||
|
||||
// b. Else,
|
||||
// i. Assert: toBlock is not a Shared Data Block.
|
||||
// ii. Set toBlock[toIndex] to fromBlock[fromIndex].
|
||||
to_block[to_index] = from_block[from_index]; |
||||
|
||||
// c. Set toIndex to toIndex + 1.
|
||||
to_index += 1; |
||||
|
||||
// d. Set fromIndex to fromIndex + 1.
|
||||
from_index += 1; |
||||
|
||||
// e. Set count to count - 1.
|
||||
count -= 1; |
||||
} |
||||
|
||||
// 7. Return NormalCompletion(empty).
|
||||
} |
||||
|
||||
// TODO: Allow unused variants until shared array buffers are implemented.
|
||||
#[allow(dead_code)] |
||||
#[derive(Debug, PartialEq)] |
||||
pub(crate) enum SharedMemoryOrder { |
||||
Init, |
||||
SeqCst, |
||||
Unordered, |
||||
} |
@ -0,0 +1,184 @@
|
||||
//! This module implements the `Integer-Indexed` exotic object.
|
||||
//!
|
||||
//! An `Integer-Indexed` exotic object is an exotic object that performs
|
||||
//! special handling of integer index property keys.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
|
||||
|
||||
use crate::{ |
||||
builtins::typed_array::TypedArrayName, |
||||
gc::{empty_trace, Finalize, Trace}, |
||||
object::{JsObject, ObjectData}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// Type of the array content.
|
||||
#[derive(Debug, Clone, Copy, Finalize, PartialEq)] |
||||
pub(crate) enum ContentType { |
||||
Number, |
||||
BigInt, |
||||
} |
||||
|
||||
unsafe impl Trace for ContentType { |
||||
// safe because `ContentType` is `Copy`
|
||||
empty_trace!(); |
||||
} |
||||
|
||||
/// <https://tc39.es/ecma262/#integer-indexed-exotic-object>
|
||||
#[derive(Debug, Clone, Trace, Finalize)] |
||||
pub struct IntegerIndexed { |
||||
viewed_array_buffer: Option<JsObject>, |
||||
typed_array_name: TypedArrayName, |
||||
byte_offset: usize, |
||||
byte_length: usize, |
||||
array_length: usize, |
||||
} |
||||
|
||||
impl IntegerIndexed { |
||||
pub(crate) fn new( |
||||
viewed_array_buffer: Option<JsObject>, |
||||
typed_array_name: TypedArrayName, |
||||
byte_offset: usize, |
||||
byte_length: usize, |
||||
array_length: usize, |
||||
) -> Self { |
||||
Self { |
||||
viewed_array_buffer, |
||||
typed_array_name, |
||||
byte_offset, |
||||
byte_length, |
||||
array_length, |
||||
} |
||||
} |
||||
|
||||
/// `IntegerIndexedObjectCreate ( prototype )`
|
||||
///
|
||||
/// Create a new `JsObject from a prototype and a `IntergetIndexedObject`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedobjectcreate
|
||||
pub(super) fn create(prototype: JsObject, data: Self, context: &Context) -> JsObject { |
||||
// 1. Let internalSlotsList be « [[Prototype]], [[Extensible]], [[ViewedArrayBuffer]], [[TypedArrayName]], [[ContentType]], [[ByteLength]], [[ByteOffset]], [[ArrayLength]] ».
|
||||
// 2. Let A be ! MakeBasicObject(internalSlotsList).
|
||||
let a = context.construct_object(); |
||||
|
||||
// 3. Set A.[[GetOwnProperty]] as specified in 10.4.5.1.
|
||||
// 4. Set A.[[HasProperty]] as specified in 10.4.5.2.
|
||||
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.5.3.
|
||||
// 6. Set A.[[Get]] as specified in 10.4.5.4.
|
||||
// 7. Set A.[[Set]] as specified in 10.4.5.5.
|
||||
// 8. Set A.[[Delete]] as specified in 10.4.5.6.
|
||||
// 9. Set A.[[OwnPropertyKeys]] as specified in 10.4.5.7.
|
||||
a.borrow_mut().data = ObjectData::integer_indexed(data); |
||||
|
||||
// 10. Set A.[[Prototype]] to prototype.
|
||||
a.set_prototype_instance(prototype.into()); |
||||
|
||||
// 11. Return A.
|
||||
a |
||||
} |
||||
|
||||
/// Abstract operation `IsDetachedBuffer ( arrayBuffer )`.
|
||||
///
|
||||
/// Check if `[[ArrayBufferData]]` is null.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
pub(crate) fn is_detached(&self) -> bool { |
||||
if let Some(obj) = &self.viewed_array_buffer { |
||||
obj.borrow() |
||||
.as_array_buffer() |
||||
.expect("Typed array must have internal array buffer object") |
||||
.is_detached_buffer() |
||||
} else { |
||||
false |
||||
} |
||||
} |
||||
|
||||
/// Get the integer indexed object's byte offset.
|
||||
pub(crate) fn byte_offset(&self) -> usize { |
||||
self.byte_offset |
||||
} |
||||
|
||||
/// Set the integer indexed object's byte offset.
|
||||
pub(crate) fn set_byte_offset(&mut self, byte_offset: usize) { |
||||
self.byte_offset = byte_offset; |
||||
} |
||||
|
||||
/// Get the integer indexed object's typed array name.
|
||||
pub(crate) fn typed_array_name(&self) -> TypedArrayName { |
||||
self.typed_array_name |
||||
} |
||||
|
||||
/// Get a reference to the integer indexed object's viewed array buffer.
|
||||
pub fn viewed_array_buffer(&self) -> Option<&JsObject> { |
||||
self.viewed_array_buffer.as_ref() |
||||
} |
||||
|
||||
///(crate) Set the integer indexed object's viewed array buffer.
|
||||
pub fn set_viewed_array_buffer(&mut self, viewed_array_buffer: Option<JsObject>) { |
||||
self.viewed_array_buffer = viewed_array_buffer; |
||||
} |
||||
|
||||
/// Get the integer indexed object's byte length.
|
||||
pub fn byte_length(&self) -> usize { |
||||
self.byte_length |
||||
} |
||||
|
||||
/// Set the integer indexed object's byte length.
|
||||
pub(crate) fn set_byte_length(&mut self, byte_length: usize) { |
||||
self.byte_length = byte_length; |
||||
} |
||||
|
||||
/// Get the integer indexed object's array length.
|
||||
pub fn array_length(&self) -> usize { |
||||
self.array_length |
||||
} |
||||
|
||||
/// Set the integer indexed object's array length.
|
||||
pub(crate) fn set_array_length(&mut self, array_length: usize) { |
||||
self.array_length = array_length; |
||||
} |
||||
} |
||||
|
||||
/// A Data Block
|
||||
///
|
||||
/// The Data Block specification type is used to describe a distinct and mutable sequence of
|
||||
/// byte-sized (8 bit) numeric values. A byte value is an integer value in the range `0` through
|
||||
/// `255`, inclusive. A Data Block value is created with a fixed number of bytes that each have
|
||||
/// the initial value `0`.
|
||||
///
|
||||
/// For more information, check the [spec][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-data-blocks
|
||||
#[derive(Debug, Clone, Default, Trace, Finalize)] |
||||
pub struct DataBlock { |
||||
inner: Vec<u8>, |
||||
} |
||||
|
||||
impl DataBlock { |
||||
/// `CreateByteDataBlock ( size )` abstract operation.
|
||||
///
|
||||
/// The abstract operation `CreateByteDataBlock` takes argument `size` (a non-negative
|
||||
/// integer). For more information, check the [spec][spec].
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createbytedatablock
|
||||
pub fn create_byte_data_block(size: usize) -> JsResult<Self> { |
||||
// 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.
|
||||
// 2. Set all of the bytes of db to 0.
|
||||
// 3. Return db.
|
||||
// TODO: waiting on <https://github.com/rust-lang/rust/issues/48043> for having fallible
|
||||
// allocation.
|
||||
Ok(Self { |
||||
inner: vec![0u8; size], |
||||
}) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,388 @@
|
||||
use crate::{ |
||||
builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType}, |
||||
object::JsObject, |
||||
property::{PropertyDescriptor, PropertyKey}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
|
||||
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; |
||||
|
||||
/// Definitions of the internal object methods for integer-indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects
|
||||
pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = |
||||
InternalObjectMethods { |
||||
__get_own_property__: integer_indexed_exotic_get_own_property, |
||||
__has_property__: integer_indexed_exotic_has_property, |
||||
__define_own_property__: integer_indexed_exotic_define_own_property, |
||||
__get__: integer_indexed_exotic_get, |
||||
__set__: integer_indexed_exotic_set, |
||||
__delete__: integer_indexed_exotic_delete, |
||||
__own_property_keys__: integer_indexed_exotic_own_property_keys, |
||||
..ORDINARY_INTERNAL_METHODS |
||||
}; |
||||
|
||||
/// InternalMethod `[[GetOwnProperty]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_get_own_property( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<Option<PropertyDescriptor>> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key { |
||||
// 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, *index as usize).map(|v| { |
||||
PropertyDescriptor::builder() |
||||
.value(v) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build() |
||||
})) |
||||
} else { |
||||
// 2. Return OrdinaryGetOwnProperty(O, P).
|
||||
super::ordinary_get_own_property(obj, key, context) |
||||
} |
||||
} |
||||
|
||||
/// InternalMethod `[[HasProperty]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_has_property( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
if let PropertyKey::Index(index) = key { |
||||
// b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex).
|
||||
Ok(is_valid_integer_index(obj, *index as usize)) |
||||
} else { |
||||
// 2. Return ? OrdinaryHasProperty(O, P).
|
||||
super::ordinary_has_property(obj, key, context) |
||||
} |
||||
} |
||||
|
||||
/// InternalMethod `[[DefineOwnProperty]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_define_own_property( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key { |
||||
// 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, index as usize) |
||||
|| !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) |
||||
} else { |
||||
// 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc).
|
||||
super::ordinary_define_own_property(obj, key, desc, context) |
||||
} |
||||
} |
||||
|
||||
/// Internal method `[[Get]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_get( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key { |
||||
// i. Return ! IntegerIndexedElementGet(O, numericIndex).
|
||||
Ok(integer_indexed_element_get(obj, *index as usize).unwrap_or_default()) |
||||
} else { |
||||
// 2. Return ? OrdinaryGet(O, P, Receiver).
|
||||
super::ordinary_get(obj, key, receiver, context) |
||||
} |
||||
} |
||||
|
||||
/// Internal method `[[Set]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_set( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
value: JsValue, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key { |
||||
// i. Perform ? IntegerIndexedElementSet(O, numericIndex, V).
|
||||
integer_indexed_element_set(obj, index as usize, &value, context)?; |
||||
|
||||
// ii. Return true.
|
||||
Ok(true) |
||||
} else { |
||||
// 2. Return ? OrdinarySet(O, P, V, Receiver).
|
||||
super::ordinary_set(obj, key, value, receiver, context) |
||||
} |
||||
} |
||||
|
||||
/// Internal method `[[Delete]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_delete( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. If Type(P) is String, then
|
||||
// a. Let numericIndex be ! CanonicalNumericIndexString(P).
|
||||
// b. If numericIndex is not undefined, then
|
||||
if let PropertyKey::Index(index) = key { |
||||
// i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false.
|
||||
Ok(!is_valid_integer_index(obj, *index as usize)) |
||||
} else { |
||||
// 2. Return ? OrdinaryDelete(O, P).
|
||||
super::ordinary_delete(obj, key, context) |
||||
} |
||||
} |
||||
|
||||
/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
|
||||
#[inline] |
||||
pub(crate) fn integer_indexed_exotic_own_property_keys( |
||||
obj: &JsObject, |
||||
_context: &mut Context, |
||||
) -> JsResult<Vec<PropertyKey>> { |
||||
let obj = obj.borrow(); |
||||
let inner = obj.as_typed_array().expect( |
||||
"integer indexed exotic method should only be callable from integer indexed 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()) |
||||
.into_iter() |
||||
.map(|index| PropertyKey::Index(index as u32)) |
||||
.collect() |
||||
}; |
||||
|
||||
// 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.
|
||||
keys.extend( |
||||
obj.properties |
||||
.string_property_keys() |
||||
.cloned() |
||||
.map(|s| s.into()), |
||||
); |
||||
|
||||
// 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.
|
||||
keys.extend( |
||||
obj.properties |
||||
.symbol_property_keys() |
||||
.cloned() |
||||
.map(|sym| sym.into()), |
||||
); |
||||
|
||||
// 5. Return keys.
|
||||
Ok(keys) |
||||
} |
||||
|
||||
/// Abstract operation `IsValidIntegerIndex ( O, index )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex
|
||||
pub(crate) fn is_valid_integer_index(obj: &JsObject, index: usize) -> 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.
|
||||
// 2. If ! IsIntegralNumber(index) is false, return false.
|
||||
// 3. If index is -0𝔽, return false.
|
||||
// 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false.
|
||||
// 5. Return true.
|
||||
!inner.is_detached() && index < inner.array_length() |
||||
} |
||||
|
||||
/// Abstract operation `IntegerIndexedElementGet ( O, index )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget
|
||||
fn integer_indexed_element_get(obj: &JsObject, index: usize) -> Option<JsValue> { |
||||
// 1. If ! IsValidIntegerIndex(O, index) is false, return undefined.
|
||||
if !is_valid_integer_index(obj, index) { |
||||
return None; |
||||
} |
||||
|
||||
let obj = obj.borrow(); |
||||
let inner = obj |
||||
.as_typed_array() |
||||
.expect("Already checked for detached buffer"); |
||||
let buffer_obj = inner |
||||
.viewed_array_buffer() |
||||
.expect("Already checked for detached buffer"); |
||||
let buffer_obj_borrow = buffer_obj.borrow(); |
||||
let buffer = buffer_obj_borrow |
||||
.as_array_buffer() |
||||
.expect("Already checked for detached buffer"); |
||||
|
||||
// 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.typed_array_name(); |
||||
|
||||
// 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName.
|
||||
let size = elem_type.element_size(); |
||||
|
||||
// 5. Let indexedPosition be (ℝ(index) × elementSize) + offset.
|
||||
let indexed_position = (index * size) + offset; |
||||
|
||||
// 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered).
|
||||
Some(buffer.get_value_from_buffer( |
||||
indexed_position, |
||||
elem_type, |
||||
true, |
||||
SharedMemoryOrder::Unordered, |
||||
None, |
||||
)) |
||||
} |
||||
|
||||
/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset
|
||||
fn integer_indexed_element_set( |
||||
obj: &JsObject, |
||||
index: usize, |
||||
value: &JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
let obj_borrow = obj.borrow(); |
||||
let inner = obj_borrow.as_typed_array().expect( |
||||
"integer indexed exotic method should only be callable from integer indexed objects", |
||||
); |
||||
|
||||
let num_value = if inner.typed_array_name().content_type() == ContentType::BigInt { |
||||
// 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value).
|
||||
value.to_bigint(context)?.into() |
||||
} else { |
||||
// 2. Otherwise, let numValue be ? ToNumber(value).
|
||||
value.to_number(context)?.into() |
||||
}; |
||||
|
||||
// 3. If ! IsValidIntegerIndex(O, index) is true, then
|
||||
if is_valid_integer_index(obj, index) { |
||||
// a. Let offset be O.[[ByteOffset]].
|
||||
let offset = inner.byte_offset(); |
||||
|
||||
// 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.typed_array_name(); |
||||
|
||||
// c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName.
|
||||
let size = elem_type.element_size(); |
||||
|
||||
// d. Let indexedPosition be (ℝ(index) × elementSize) + offset.
|
||||
let indexed_position = (index * size) + offset; |
||||
|
||||
let buffer_obj = inner |
||||
.viewed_array_buffer() |
||||
.expect("Already checked for detached buffer"); |
||||
let mut buffer_obj_borrow = buffer_obj.borrow_mut(); |
||||
let buffer = buffer_obj_borrow |
||||
.as_array_buffer_mut() |
||||
.expect("Already checked for detached buffer"); |
||||
|
||||
// f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered).
|
||||
buffer |
||||
.set_value_in_buffer( |
||||
indexed_position, |
||||
elem_type, |
||||
num_value, |
||||
SharedMemoryOrder::Unordered, |
||||
None, |
||||
context, |
||||
) |
||||
.expect("SetValueInBuffer cannot fail here"); |
||||
} |
||||
|
||||
// 4. Return NormalCompletion(undefined).
|
||||
Ok(()) |
||||
} |
Loading…
Reference in new issue