Browse Source

Implement `Typed Array` built-in (#1552)

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
parent
commit
f5d87a899f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      boa/Cargo.toml
  2. 39
      boa/src/builtins/array/array_iterator.rs
  3. 29
      boa/src/builtins/array/mod.rs
  4. 2
      boa/src/builtins/array/tests.rs
  5. 789
      boa/src/builtins/array_buffer/mod.rs
  6. 38
      boa/src/builtins/bigint/mod.rs
  7. 2
      boa/src/builtins/bigint/tests.rs
  8. 7
      boa/src/builtins/date/mod.rs
  9. 117
      boa/src/builtins/iterable/mod.rs
  10. 33
      boa/src/builtins/map/map_iterator.rs
  11. 7
      boa/src/builtins/map/mod.rs
  12. 20
      boa/src/builtins/mod.rs
  13. 1
      boa/src/builtins/number/mod.rs
  14. 27
      boa/src/builtins/regexp/mod.rs
  15. 4
      boa/src/builtins/set/mod.rs
  16. 69
      boa/src/builtins/string/mod.rs
  17. 2
      boa/src/builtins/symbol/mod.rs
  18. 184
      boa/src/builtins/typed_array/integer_indexed_object.rs
  19. 3370
      boa/src/builtins/typed_array/mod.rs
  20. 117
      boa/src/context.rs
  21. 10
      boa/src/exec/tests.rs
  22. 388
      boa/src/object/internal_methods/integer_indexed.rs
  23. 1
      boa/src/object/internal_methods/mod.rs
  24. 42
      boa/src/object/jsobject.rs
  25. 140
      boa/src/object/mod.rs
  26. 129
      boa/src/object/operations.rs
  27. 3
      boa/src/string.rs
  28. 8
      boa/src/symbol.rs
  29. 4
      boa/src/syntax/ast/node/array/mod.rs
  30. 7
      boa/src/syntax/ast/node/call/mod.rs
  31. 4
      boa/src/syntax/ast/node/declaration/mod.rs
  32. 3
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  33. 3
      boa/src/syntax/ast/node/new/mod.rs
  34. 39
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  35. 7
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  36. 320
      boa/src/value/mod.rs
  37. 33
      boa/src/vm/mod.rs
  38. 40
      boa_tester/src/exec/js262.rs
  39. 1
      boa_tester/src/results.rs
  40. 4
      test_ignore.txt

4
boa/Cargo.toml

@ -11,7 +11,7 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"]
edition = "2018" edition = "2018"
[features] [features]
profiler = ["measureme", "once_cell"] profiler = ["measureme"]
deser = [] deser = []
# Enable Bytecode generation & execution instead of tree walking # Enable Bytecode generation & execution instead of tree walking
@ -38,10 +38,10 @@ chrono = "0.4.19"
fast-float = "0.2.0" fast-float = "0.2.0"
unicode-normalization = "0.1.19" unicode-normalization = "0.1.19"
dyn-clone = "1.0.4" dyn-clone = "1.0.4"
once_cell = "1.8.0"
# Optional Dependencies # Optional Dependencies
measureme = { version = "9.1.2", optional = true } measureme = { version = "9.1.2", optional = true }
once_cell = { version = "1.8.0", optional = true }
[target.wasm32-unknown-unknown.dependencies] [target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2.3", features = ["js"] } getrandom = { version = "0.2.3", features = ["js"] }

39
boa/src/builtins/array/array_iterator.rs

@ -15,19 +15,21 @@ use crate::{
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)] #[derive(Debug, Clone, Finalize, Trace)]
pub struct ArrayIterator { pub struct ArrayIterator {
array: JsValue, array: JsObject,
next_index: u32, next_index: usize,
kind: PropertyNameKind, kind: PropertyNameKind,
done: bool,
} }
impl ArrayIterator { impl ArrayIterator {
pub(crate) const NAME: &'static str = "ArrayIterator"; pub(crate) const NAME: &'static str = "ArrayIterator";
fn new(array: JsValue, kind: PropertyNameKind) -> Self { fn new(array: JsObject, kind: PropertyNameKind) -> Self {
ArrayIterator { ArrayIterator {
array, array,
kind, kind,
next_index: 0, next_index: 0,
done: false,
} }
} }
@ -40,7 +42,7 @@ impl ArrayIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator /// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
pub(crate) fn create_array_iterator( pub(crate) fn create_array_iterator(
array: JsValue, array: JsObject,
kind: PropertyNameKind, kind: PropertyNameKind,
context: &Context, context: &Context,
) -> JsValue { ) -> JsValue {
@ -66,21 +68,28 @@ impl ArrayIterator {
let mut object = object.borrow_mut(); let mut object = object.borrow_mut();
if let Some(array_iterator) = object.as_array_iterator_mut() { if let Some(array_iterator) = object.as_array_iterator_mut() {
let index = array_iterator.next_index; let index = array_iterator.next_index;
if array_iterator.array.is_undefined() { if array_iterator.done {
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
JsValue::undefined(), JsValue::undefined(),
true, true,
context, context,
)); ));
} }
let len = array_iterator
.array let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() {
.get_field("length", context)? if f.is_detached() {
.as_number() return context.throw_type_error(
.ok_or_else(|| context.construct_type_error("Not an array"))? "Cannot get value from typed array that has a detached array buffer",
as u32; );
if array_iterator.next_index >= len { }
array_iterator.array = JsValue::undefined();
f.array_length()
} else {
array_iterator.array.length_of_array_like(context)?
};
if index >= len {
array_iterator.done = true;
return Ok(create_iter_result_object( return Ok(create_iter_result_object(
JsValue::undefined(), JsValue::undefined(),
true, true,
@ -93,11 +102,11 @@ impl ArrayIterator {
Ok(create_iter_result_object(index.into(), false, context)) Ok(create_iter_result_object(index.into(), false, context))
} }
PropertyNameKind::Value => { PropertyNameKind::Value => {
let element_value = array_iterator.array.get_field(index, context)?; let element_value = array_iterator.array.get(index, context)?;
Ok(create_iter_result_object(element_value, false, context)) Ok(create_iter_result_object(element_value, false, context))
} }
PropertyNameKind::KeyAndValue => { PropertyNameKind::KeyAndValue => {
let element_value = array_iterator.array.get_field(index, context)?; let element_value = array_iterator.array.get(index, context)?;
let result = let result =
Array::create_array_from_list([index.into(), element_value], context); Array::create_array_from_list([index.into(), element_value], context);
Ok(create_iter_result_object(result.into(), false, context)) Ok(create_iter_result_object(result.into(), false, context))

29
boa/src/builtins/array/mod.rs

@ -758,10 +758,11 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If separator is undefined, let sep be the single-element String ",". // 3. If separator is undefined, let sep be the single-element String ",".
// 4. Else, let sep be ? ToString(separator). // 4. Else, let sep be ? ToString(separator).
let separator = if let Some(separator) = args.get(0) { let separator = args.get_or_undefined(0);
separator.to_string(context)? let separator = if separator.is_undefined() {
} else {
JsString::new(",") JsString::new(",")
} else {
separator.to_string(context)?
}; };
// 5. Let R be the empty String. // 5. Let R be the empty String.
@ -2565,8 +2566,12 @@ impl Array {
_: &[JsValue], _: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, value).
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
this.clone(), o,
PropertyNameKind::Value, PropertyNameKind::Value,
context, context,
)) ))
@ -2580,11 +2585,15 @@ impl Array {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn] /// - [MDN documentation][mdn]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.keys
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, key).
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
this.clone(), o,
PropertyNameKind::Key, PropertyNameKind::Key,
context, context,
)) ))
@ -2598,15 +2607,19 @@ impl Array {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn] /// - [MDN documentation][mdn]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.entries
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn entries( pub(crate) fn entries(
this: &JsValue, this: &JsValue,
_: &[JsValue], _: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, key+value).
Ok(ArrayIterator::create_array_iterator( Ok(ArrayIterator::create_array_iterator(
this.clone(), o,
PropertyNameKind::KeyAndValue, PropertyNameKind::KeyAndValue,
context, context,
)) ))

2
boa/src/builtins/array/tests.rs

@ -1412,7 +1412,7 @@ fn array_spread_non_iterable() {
try { try {
const array2 = [...5]; const array2 = [...5];
} catch (err) { } catch (err) {
err.name === "TypeError" && err.message === "Not an iterable" err.name === "TypeError" && err.message === "Value is not callable"
} }
"#; "#;
assert_eq!(forward(&mut context, init), "true"); assert_eq!(forward(&mut context, init), "true");

789
boa/src/builtins/array_buffer/mod.rs

@ -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,
}

38
boa/src/builtins/bigint/mod.rs

@ -17,9 +17,11 @@ use crate::{
object::ConstructorBuilder, object::ConstructorBuilder,
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::IntegerOrInfinity, value::{IntegerOrInfinity, PreferredType},
BoaProfiler, Context, JsBigInt, JsResult, JsValue, BoaProfiler, Context, JsBigInt, JsResult, JsValue,
}; };
use num_bigint::ToBigInt;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -78,11 +80,35 @@ impl BigInt {
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
fn constructor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn constructor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let data = match args.get(0) { let value = args.get_or_undefined(0);
Some(value) => value.to_bigint(context)?,
None => JsBigInt::zero(), // 2. Let prim be ? ToPrimitive(value, number).
}; let prim = value.to_primitive(context, PreferredType::Number)?;
Ok(JsValue::new(data))
// 3. If Type(prim) is Number, return ? NumberToBigInt(prim).
if let Some(number) = prim.as_number() {
return Self::number_to_bigint(number, context);
}
// 4. Otherwise, return ? ToBigInt(value).
Ok(value.to_bigint(context)?.into())
}
/// `NumberToBigInt ( number )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-numbertobigint
#[inline]
fn number_to_bigint(number: f64, context: &mut Context) -> JsResult<JsValue> {
// 1. If IsIntegralNumber(number) is false, throw a RangeError exception.
if number.is_nan() || number.is_infinite() || number.fract() != 0.0 {
return context.throw_range_error(format!("Cannot convert {} to BigInt", number));
}
// 2. Return the BigInt value that represents ℝ(number).
Ok(JsBigInt::from(number.to_bigint().expect("This conversion must be safe")).into())
} }
/// The abstract operation thisBigIntValue takes argument value. /// The abstract operation thisBigIntValue takes argument value.

2
boa/src/builtins/bigint/tests.rs

@ -90,7 +90,7 @@ fn bigint_function_conversion_from_rational_with_fractional_part() {
"#; "#;
assert_eq!( assert_eq!(
forward(&mut context, scenario), forward(&mut context, scenario),
"\"TypeError: The number 0.1 cannot be converted to a BigInt because it is not an integer\"" "\"RangeError: Cannot convert 0.1 to BigInt\""
); );
} }

7
boa/src/builtins/date/mod.rs

@ -1713,11 +1713,8 @@ impl Date {
} }
// 4. Return ? Invoke(O, "toISOString"). // 4. Return ? Invoke(O, "toISOString").
if let Some(to_iso_string) = o.get_method(context, "toISOString")? { let func = o.get("toISOString", context)?;
to_iso_string.call(this, &[], context) context.call(&func, &o.into(), &[])
} else {
context.throw_type_error("toISOString in undefined")
}
} }
/// `Date.prototype.toString()` /// `Date.prototype.toString()`

117
boa/src/builtins/iterable/mod.rs

@ -82,7 +82,7 @@ impl IteratorPrototypes {
} }
} }
/// CreateIterResultObject( value, done ) /// `CreateIterResultObject( value, done )`
/// ///
/// Generates an object supporting the IteratorResult interface. /// Generates an object supporting the IteratorResult interface.
pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue { pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue {
@ -100,18 +100,70 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte
obj.into() obj.into()
} }
/// Get an iterator record /// Iterator hint for `GetIterator`.
pub fn get_iterator(iterable: &JsValue, context: &mut Context) -> JsResult<IteratorRecord> { #[derive(Debug, Clone, Copy, PartialEq, Eq)]
let iterator_function = iterable.get_field(WellKnownSymbols::iterator(), context)?; pub enum IteratorHint {
if iterator_function.is_null_or_undefined() { Sync,
return Err(context.construct_type_error("Not an iterable")); Async,
}
impl JsValue {
/// `GetIterator ( obj [ , hint [ , method ] ] )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getiterator
pub fn get_iterator(
&self,
context: &mut Context,
hint: Option<IteratorHint>,
method: Option<JsValue>,
) -> JsResult<IteratorRecord> {
// 1. If hint is not present, set hint to sync.
let hint = hint.unwrap_or(IteratorHint::Sync);
// 2. If method is not present, then
let method = if let Some(method) = method {
method
} else {
// a. If hint is async, then
if hint == IteratorHint::Async {
// i. Set method to ? GetMethod(obj, @@asyncIterator).
let method = self.get_method(WellKnownSymbols::async_iterator(), context)?;
// ii. If method is undefined, then
if method.is_undefined() {
// 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self.get_method(WellKnownSymbols::iterator(), context)?;
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let _sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
// 3. Return ! CreateAsyncFromSyncIterator(syncIteratorRecord).
todo!("CreateAsyncFromSyncIterator");
} }
let iterator_object = context.call(&iterator_function, iterable, &[])?;
let next_function = iterator_object.get_field("next", context)?; method
if next_function.is_null_or_undefined() { } else {
return Err(context.construct_type_error("Could not find property `next`")); // b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)?
}
};
// 3. Let iterator be ? Call(method, obj).
let iterator = context.call(&method, self, &[])?;
// 4. If Type(iterator) is not Object, throw a TypeError exception.
if !iterator.is_object() {
return Err(context.construct_type_error("the iterator is not an object"));
}
// 5. Let nextMethod be ? GetV(iterator, "next").
let next_method = iterator.get_v("next", context)?;
// 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 7. Return iteratorRecord.
Ok(IteratorRecord::new(iterator, next_method))
} }
Ok(IteratorRecord::new(iterator_object, next_function))
} }
/// Create the %IteratorPrototype% object /// Create the %IteratorPrototype% object
@ -201,6 +253,49 @@ impl IteratorRecord {
} }
} }
/// `IterableToList ( items [ , method ] )`
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iterabletolist
pub(crate) fn iterable_to_list(
context: &mut Context,
items: JsValue,
method: Option<JsValue>,
) -> JsResult<Vec<JsValue>> {
// 1. If method is present, then
let iterator_record = if let Some(method) = method {
// a. Let iteratorRecord be ? GetIterator(items, sync, method).
items.get_iterator(context, Some(IteratorHint::Sync), Some(method))?
} else {
// 2. Else,
// a. Let iteratorRecord be ? GetIterator(items, sync).
items.get_iterator(context, Some(IteratorHint::Sync), None)?
};
// 3. Let values be a new empty List.
let mut values = Vec::new();
// 4. Let next be true.
// 5. Repeat, while next is not false,
// a. Set next to ? IteratorStep(iteratorRecord).
// b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next).
// ii. Append nextValue to the end of the List values.
loop {
let next = iterator_record.next(context)?;
if next.done {
break;
}
values.push(next.value)
}
// 6. Return values.
Ok(values)
}
#[derive(Debug)] #[derive(Debug)]
pub struct IteratorResult { pub struct IteratorResult {
pub value: JsValue, pub value: JsValue,

33
boa/src/builtins/map/map_iterator.rs

@ -79,39 +79,36 @@ impl MapIterator {
.as_map_iterator_mut() .as_map_iterator_mut()
.expect("checked that obj was a map iterator"); .expect("checked that obj was a map iterator");
let mut index = map_iterator.map_next_index;
let item_kind = map_iterator.map_iteration_kind; let item_kind = map_iterator.map_iteration_kind;
if let Some(obj) = map_iterator.iterated_map.take() { if let Some(obj) = map_iterator.iterated_map.take() {
let e = {
let map = obj.borrow(); let map = obj.borrow();
let entries = map.as_map_ref().expect("iterator should only iterate maps"); let entries = map.as_map_ref().expect("iterator should only iterate maps");
let num_entries = entries.full_len(); let len = entries.full_len();
while index < num_entries { loop {
let e = entries.get_index(index); let element = entries
index += 1; .get_index(map_iterator.map_next_index)
map_iterator.map_next_index = index; .map(|(v, k)| (v.clone(), k.clone()));
if let Some((key, value)) = e { map_iterator.map_next_index += 1;
let item = match item_kind { if element.is_some() || map_iterator.map_next_index >= len {
PropertyNameKind::Key => { break element;
Ok(create_iter_result_object(key.clone(), false, context))
} }
PropertyNameKind::Value => {
Ok(create_iter_result_object(value.clone(), false, context))
} }
};
if let Some((key, value)) = e {
let item = match item_kind {
PropertyNameKind::Key => Ok(create_iter_result_object(key, false, context)),
PropertyNameKind::Value => Ok(create_iter_result_object(value, false, context)),
PropertyNameKind::KeyAndValue => { PropertyNameKind::KeyAndValue => {
let result = Array::create_array_from_list( let result = Array::create_array_from_list([key, value], context);
[key.clone(), value.clone()],
context,
);
Ok(create_iter_result_object(result.into(), false, context)) Ok(create_iter_result_object(result.into(), false, context))
} }
}; };
drop(map);
map_iterator.iterated_map = Some(obj); map_iterator.iterated_map = Some(obj);
return item; return item;
} }
} }
}
Ok(create_iter_result_object( Ok(create_iter_result_object(
JsValue::undefined(), JsValue::undefined(),

7
boa/src/builtins/map/mod.rs

@ -13,10 +13,7 @@
#![allow(clippy::mutable_key_type)] #![allow(clippy::mutable_key_type)]
use crate::{ use crate::{
builtins::{ builtins::{iterable::IteratorResult, BuiltIn},
iterable::{get_iterator, IteratorResult},
BuiltIn,
},
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
@ -555,7 +552,7 @@ pub(crate) fn add_entries_from_iterable(
}; };
// 2. Let iteratorRecord be ? GetIterator(iterable). // 2. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = get_iterator(iterable, context)?; let iterator_record = iterable.get_iterator(context, None, None)?;
// 3. Repeat, // 3. Repeat,
loop { loop {

20
boa/src/builtins/mod.rs

@ -1,6 +1,7 @@
//! Builtins live here, such as Object, String, Math, etc. //! Builtins live here, such as Object, String, Math, etc.
pub mod array; pub mod array;
pub mod array_buffer;
pub mod bigint; pub mod bigint;
pub mod boolean; pub mod boolean;
#[cfg(feature = "console")] #[cfg(feature = "console")]
@ -22,6 +23,7 @@ pub mod regexp;
pub mod set; pub mod set;
pub mod string; pub mod string;
pub mod symbol; pub mod symbol;
pub mod typed_array;
pub mod undefined; pub mod undefined;
pub(crate) use self::{ pub(crate) use self::{
@ -47,9 +49,15 @@ pub(crate) use self::{
set::Set, set::Set,
string::String, string::String,
symbol::Symbol, symbol::Symbol,
typed_array::{
BigInt64Array, BigUint64Array, Float32Array, Float64Array, Int16Array, Int32Array,
Int8Array, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray,
},
undefined::Undefined, undefined::Undefined,
}; };
use crate::{ use crate::{
builtins::array_buffer::ArrayBuffer,
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
Context, JsValue, Context, JsValue,
}; };
@ -113,6 +121,7 @@ pub fn init(context: &mut Context) {
Math, Math,
Json, Json,
Array, Array,
ArrayBuffer,
BigInt, BigInt,
Boolean, Boolean,
Date, Date,
@ -121,6 +130,17 @@ pub fn init(context: &mut Context) {
Set, Set,
String, String,
RegExp, RegExp,
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
BigInt64Array,
BigUint64Array,
Float32Array,
Float64Array,
Symbol, Symbol,
Error, Error,
RangeError, RangeError,

1
boa/src/builtins/number/mod.rs

@ -610,7 +610,6 @@ impl Number {
let integer_cursor = int_iter.next().unwrap().0 + 1; let integer_cursor = int_iter.next().unwrap().0 + 1;
let fraction_cursor = fraction_cursor + BUF_SIZE / 2; let fraction_cursor = fraction_cursor + BUF_SIZE / 2;
// dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor);
String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into() String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into()
} }

27
boa/src/builtins/regexp/mod.rs

@ -136,7 +136,7 @@ impl BuiltIn for RegExp {
.method(Self::to_string, "toString", 0) .method(Self::to_string, "toString", 0)
.method( .method(
Self::r#match, Self::r#match,
(WellKnownSymbols::match_(), "[Symbol.match]"), (WellKnownSymbols::r#match(), "[Symbol.match]"),
1, 1,
) )
.method( .method(
@ -983,7 +983,7 @@ impl RegExp {
let named_groups = match_value.named_groups(); let named_groups = match_value.named_groups();
let groups = if named_groups.clone().count() > 0 { let groups = if named_groups.clone().count() > 0 {
// a. Let groups be ! OrdinaryObjectCreate(null). // a. Let groups be ! OrdinaryObjectCreate(null).
let groups = JsValue::new_object(context); let groups = JsValue::from(JsObject::new(Object::create(JsValue::null())));
// Perform 27.f here // Perform 27.f here
// f. If the ith capture of R was defined with a GroupName, then // f. If the ith capture of R was defined with a GroupName, then
@ -1211,16 +1211,17 @@ impl RegExp {
let c = this let c = this
.as_object() .as_object()
.unwrap_or_default() .unwrap_or_default()
.species_constructor(context.global_object().get(RegExp::NAME, context)?, context)?; .species_constructor(StandardObjects::regexp_object, context)?;
// 5. Let flags be ? ToString(? Get(R, "flags")). // 5. Let flags be ? ToString(? Get(R, "flags")).
let flags = this.get_field("flags", context)?.to_string(context)?; let flags = this.get_field("flags", context)?.to_string(context)?;
// 6. Let matcher be ? Construct(C, « R, flags »). // 6. Let matcher be ? Construct(C, « R, flags »).
let matcher = c let matcher = c.construct(
.as_object() &[this.clone(), flags.clone().into()],
.expect("SpeciesConstructor returned non Object") &c.clone().into(),
.construct(&[this.clone(), flags.clone().into()], &c, context)?; context,
)?;
// 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
let last_index = this.get_field("lastIndex", context)?.to_length(context)?; let last_index = this.get_field("lastIndex", context)?.to_length(context)?;
@ -1582,8 +1583,7 @@ impl RegExp {
.to_string(context)?; .to_string(context)?;
// 4. Let C be ? SpeciesConstructor(rx, %RegExp%). // 4. Let C be ? SpeciesConstructor(rx, %RegExp%).
let constructor = let constructor = rx.species_constructor(StandardObjects::regexp_object, context)?;
rx.species_constructor(context.global_object().get(RegExp::NAME, context)?, context)?;
// 5. Let flags be ? ToString(? Get(rx, "flags")). // 5. Let flags be ? ToString(? Get(rx, "flags")).
let flags = rx.get("flags", context)?.to_string(context)?; let flags = rx.get("flags", context)?.to_string(context)?;
@ -1601,12 +1601,9 @@ impl RegExp {
}; };
// 10. Let splitter be ? Construct(C, « rx, newFlags »). // 10. Let splitter be ? Construct(C, « rx, newFlags »).
let splitter = constructor let splitter = constructor.construct(
.as_object() &[rx.into(), new_flags.into()],
.expect("SpeciesConstructor returned non Object") &constructor.clone().into(),
.construct(
&[JsValue::from(rx), new_flags.into()],
&constructor,
context, context,
)?; )?;

4
boa/src/builtins/set/mod.rs

@ -11,7 +11,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
use crate::{ use crate::{
builtins::{iterable::get_iterator, BuiltIn}, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
@ -152,7 +152,7 @@ impl Set {
} }
// 7 // 7
let iterator_record = get_iterator(iterable, context)?; let iterator_record = iterable.clone().get_iterator(context, None, None)?;
// 8.a // 8.a
let mut next = iterator_record.next(context)?; let mut next = iterator_record.next(context)?;

69
boa/src/builtins/string/mod.rs

@ -758,16 +758,13 @@ impl String {
// 2. If searchValue is neither undefined nor null, then // 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() { if !search_value.is_null_or_undefined() {
// a. Let replacer be ? GetMethod(searchValue, @@replace). // a. Let replacer be ? GetMethod(searchValue, @@replace).
let replacer = search_value let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?;
.as_object()
.unwrap_or_default()
.get_method(context, WellKnownSymbols::replace())?;
// b. If replacer is not undefined, then // b. If replacer is not undefined, then
if let Some(replacer) = replacer { if !replacer.is_undefined() {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return context.call( return context.call(
&replacer.into(), &replacer,
search_value, search_value,
&[this.clone(), replace_value.clone()], &[this.clone(), replace_value.clone()],
); );
@ -892,15 +889,12 @@ impl String {
} }
// c. Let replacer be ? GetMethod(searchValue, @@replace). // c. Let replacer be ? GetMethod(searchValue, @@replace).
let replacer = search_value let replacer = search_value.get_method(WellKnownSymbols::replace(), context)?;
.as_object()
.unwrap_or_default()
.get_method(context, WellKnownSymbols::replace())?;
// d. If replacer is not undefined, then // d. If replacer is not undefined, then
if let Some(replacer) = replacer { if !replacer.is_undefined() {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return replacer.call(search_value, &[o.into(), replace_value.clone()], context); return context.call(&replacer, search_value, &[o.into(), replace_value.clone()]);
} }
} }
@ -1133,12 +1127,11 @@ impl String {
let regexp = args.get_or_undefined(0); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let matcher be ? GetMethod(regexp, @@match). // a. Let matcher be ? GetMethod(regexp, @@match).
let matcher = regexp.get_method(WellKnownSymbols::r#match(), context)?;
// b. If matcher is not undefined, then // b. If matcher is not undefined, then
if let Some(obj) = regexp.as_object() { if !matcher.is_undefined() {
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
return matcher.call(regexp, &[o.clone()], context); return context.call(&matcher, regexp, &[o.clone()]);
}
} }
} }
@ -1149,12 +1142,7 @@ impl String {
let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@match, « S »). // 5. Return ? Invoke(rx, @@match, « S »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context)
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_())? {
matcher.call(&rx, &[JsValue::new(s)], context)
} else {
context.throw_type_error("RegExp[Symbol.match] is undefined")
}
} }
/// Abstract method `StringPad`. /// Abstract method `StringPad`.
@ -1504,14 +1492,11 @@ impl String {
// 2. If separator is neither undefined nor null, then // 2. If separator is neither undefined nor null, then
if !separator.is_null_or_undefined() { if !separator.is_null_or_undefined() {
// a. Let splitter be ? GetMethod(separator, @@split). // a. Let splitter be ? GetMethod(separator, @@split).
let splitter = separator.get_method(WellKnownSymbols::split(), context)?;
// b. If splitter is not undefined, then // b. If splitter is not undefined, then
if let Some(splitter) = separator if !splitter.is_undefined() {
.as_object()
.unwrap_or_default()
.get_method(context, WellKnownSymbols::split())?
{
// i. Return ? Call(splitter, separator, « O, limit »). // i. Return ? Call(splitter, separator, « O, limit »).
return splitter.call(separator, &[this.clone(), limit.clone()], context); return context.call(&splitter, separator, &[this.clone(), limit.clone()]);
} }
} }
@ -1694,12 +1679,11 @@ impl String {
} }
// c. Let matcher be ? GetMethod(regexp, @@matchAll). // c. Let matcher be ? GetMethod(regexp, @@matchAll).
let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?;
// d. If matcher is not undefined, then // d. If matcher is not undefined, then
if let Some(obj) = regexp.as_object() { if !matcher.is_undefined() {
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
return matcher.call(regexp, &[o.clone()], context); return context.call(&matcher, regexp, &[o.clone()]);
}
} }
} }
@ -1710,12 +1694,7 @@ impl String {
let rx = RegExp::create(regexp.clone(), JsValue::new("g"), context)?; let rx = RegExp::create(regexp.clone(), JsValue::new("g"), context)?;
// 5. Return ? Invoke(rx, @@matchAll, « S »). // 5. Return ? Invoke(rx, @@matchAll, « S »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context)
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::match_all())? {
matcher.call(&rx, &[JsValue::new(s)], context)
} else {
context.throw_type_error("RegExp[Symbol.matchAll] is undefined")
}
} }
/// `String.prototype.normalize( [ form ] )` /// `String.prototype.normalize( [ form ] )`
@ -1778,12 +1757,11 @@ impl String {
let regexp = args.get_or_undefined(0); let regexp = args.get_or_undefined(0);
if !regexp.is_null_or_undefined() { if !regexp.is_null_or_undefined() {
// a. Let searcher be ? GetMethod(regexp, @@search). // a. Let searcher be ? GetMethod(regexp, @@search).
let searcher = regexp.get_method(WellKnownSymbols::search(), context)?;
// b. If searcher is not undefined, then // b. If searcher is not undefined, then
if let Some(obj) = regexp.as_object() { if !searcher.is_undefined() {
if let Some(searcher) = obj.get_method(context, WellKnownSymbols::search())? {
// i. Return ? Call(searcher, regexp, « O »). // i. Return ? Call(searcher, regexp, « O »).
return searcher.call(regexp, &[o.clone()], context); return context.call(&searcher, regexp, &[o.clone()]);
}
} }
} }
@ -1794,12 +1772,7 @@ impl String {
let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?; let rx = RegExp::create(regexp.clone(), JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@search, « string »). // 5. Return ? Invoke(rx, @@search, « string »).
let obj = rx.as_object().expect("RegExpCreate must return Object"); rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context)
if let Some(matcher) = obj.get_method(context, WellKnownSymbols::search())? {
matcher.call(&rx, &[JsValue::new(string)], context)
} else {
context.throw_type_error("RegExp[Symbol.search] is undefined")
}
} }
pub(crate) fn iterator( pub(crate) fn iterator(

2
boa/src/builtins/symbol/mod.rs

@ -87,7 +87,7 @@ impl BuiltIn for Symbol {
let symbol_has_instance = WellKnownSymbols::has_instance(); let symbol_has_instance = WellKnownSymbols::has_instance();
let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable(); let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable();
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = WellKnownSymbols::iterator();
let symbol_match = WellKnownSymbols::match_(); let symbol_match = WellKnownSymbols::r#match();
let symbol_match_all = WellKnownSymbols::match_all(); let symbol_match_all = WellKnownSymbols::match_all();
let symbol_replace = WellKnownSymbols::replace(); let symbol_replace = WellKnownSymbols::replace();
let symbol_search = WellKnownSymbols::search(); let symbol_search = WellKnownSymbols::search();

184
boa/src/builtins/typed_array/integer_indexed_object.rs

@ -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],
})
}
}

3370
boa/src/builtins/typed_array/mod.rs

File diff suppressed because it is too large Load Diff

117
boa/src/context.rs

@ -1,7 +1,7 @@
//! Javascript context. //! Javascript context.
use crate::{ use crate::{
builtins::{self, iterable::IteratorPrototypes}, builtins::{self, iterable::IteratorPrototypes, typed_array::TypedArray},
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
exec::Interpreter, exec::Interpreter,
object::{ object::{
@ -92,6 +92,19 @@ pub struct StandardObjects {
uri_error: StandardConstructor, uri_error: StandardConstructor,
map: StandardConstructor, map: StandardConstructor,
set: StandardConstructor, set: StandardConstructor,
typed_array: StandardConstructor,
typed_int8_array: StandardConstructor,
typed_uint8_array: StandardConstructor,
typed_uint8clamped_array: StandardConstructor,
typed_int16_array: StandardConstructor,
typed_uint16_array: StandardConstructor,
typed_int32_array: StandardConstructor,
typed_uint32_array: StandardConstructor,
typed_bigint64_array: StandardConstructor,
typed_biguint64_array: StandardConstructor,
typed_float32_array: StandardConstructor,
typed_float64_array: StandardConstructor,
array_buffer: StandardConstructor,
} }
impl Default for StandardObjects { impl Default for StandardObjects {
@ -115,6 +128,19 @@ impl Default for StandardObjects {
uri_error: StandardConstructor::default(), uri_error: StandardConstructor::default(),
map: StandardConstructor::default(), map: StandardConstructor::default(),
set: StandardConstructor::default(), set: StandardConstructor::default(),
typed_array: StandardConstructor::default(),
typed_int8_array: StandardConstructor::default(),
typed_uint8_array: StandardConstructor::default(),
typed_uint8clamped_array: StandardConstructor::default(),
typed_int16_array: StandardConstructor::default(),
typed_uint16_array: StandardConstructor::default(),
typed_int32_array: StandardConstructor::default(),
typed_uint32_array: StandardConstructor::default(),
typed_bigint64_array: StandardConstructor::default(),
typed_biguint64_array: StandardConstructor::default(),
typed_float32_array: StandardConstructor::default(),
typed_float64_array: StandardConstructor::default(),
array_buffer: StandardConstructor::default(),
} }
} }
} }
@ -209,6 +235,71 @@ impl StandardObjects {
pub fn set_object(&self) -> &StandardConstructor { pub fn set_object(&self) -> &StandardConstructor {
&self.set &self.set
} }
#[inline]
pub fn typed_array_object(&self) -> &StandardConstructor {
&self.typed_array
}
#[inline]
pub fn typed_int8_array_object(&self) -> &StandardConstructor {
&self.typed_int8_array
}
#[inline]
pub fn typed_uint8_array_object(&self) -> &StandardConstructor {
&self.typed_uint8_array
}
#[inline]
pub fn typed_uint8clamped_array_object(&self) -> &StandardConstructor {
&self.typed_uint8clamped_array
}
#[inline]
pub fn typed_int16_array_object(&self) -> &StandardConstructor {
&self.typed_int16_array
}
#[inline]
pub fn typed_uint16_array_object(&self) -> &StandardConstructor {
&self.typed_uint16_array
}
#[inline]
pub fn typed_uint32_array_object(&self) -> &StandardConstructor {
&self.typed_uint32_array
}
#[inline]
pub fn typed_int32_array_object(&self) -> &StandardConstructor {
&self.typed_int32_array
}
#[inline]
pub fn typed_bigint64_array_object(&self) -> &StandardConstructor {
&self.typed_bigint64_array
}
#[inline]
pub fn typed_biguint64_array_object(&self) -> &StandardConstructor {
&self.typed_biguint64_array
}
#[inline]
pub fn typed_float32_array_object(&self) -> &StandardConstructor {
&self.typed_float32_array
}
#[inline]
pub fn typed_float64_array_object(&self) -> &StandardConstructor {
&self.typed_float64_array
}
#[inline]
pub fn array_buffer_object(&self) -> &StandardConstructor {
&self.array_buffer
}
} }
/// Internal representation of the strict mode types. /// Internal representation of the strict mode types.
@ -275,6 +366,9 @@ pub struct Context {
/// Cached iterator prototypes. /// Cached iterator prototypes.
iterator_prototypes: IteratorPrototypes, iterator_prototypes: IteratorPrototypes,
/// Cached TypedArray constructor.
typed_array_constructor: StandardConstructor,
/// Cached standard objects and their prototypes. /// Cached standard objects and their prototypes.
standard_objects: StandardObjects, standard_objects: StandardObjects,
@ -295,6 +389,7 @@ impl Default for Context {
#[cfg(feature = "console")] #[cfg(feature = "console")]
console: Console::default(), console: Console::default(),
iterator_prototypes: IteratorPrototypes::default(), iterator_prototypes: IteratorPrototypes::default(),
typed_array_constructor: StandardConstructor::default(),
standard_objects: Default::default(), standard_objects: Default::default(),
strict: StrictType::Off, strict: StrictType::Off,
#[cfg(feature = "vm")] #[cfg(feature = "vm")]
@ -309,6 +404,14 @@ impl Default for Context {
// Add new builtIns to Context Realm // Add new builtIns to Context Realm
// At a later date this can be removed from here and called explicitly, // At a later date this can be removed from here and called explicitly,
// but for now we almost always want these default builtins // but for now we almost always want these default builtins
let typed_array_constructor_constructor = TypedArray::init(&mut context);
let typed_array_constructor_prototype = typed_array_constructor_constructor
.get("prototype", &mut context)
.expect("prototype must exist")
.as_object()
.expect("prototype must be object");
context.typed_array_constructor.constructor = typed_array_constructor_constructor;
context.typed_array_constructor.prototype = typed_array_constructor_prototype;
context.create_intrinsics(); context.create_intrinsics();
context.iterator_prototypes = IteratorPrototypes::init(&mut context); context.iterator_prototypes = IteratorPrototypes::init(&mut context);
context context
@ -378,7 +481,7 @@ impl Context {
builtins::init(self); builtins::init(self);
} }
/// Construct an empty object. /// Constructs an object with the `%Object.prototype%` prototype.
#[inline] #[inline]
pub fn construct_object(&self) -> JsObject { pub fn construct_object(&self) -> JsObject {
let object_prototype: JsValue = self.standard_objects().object_object().prototype().into(); let object_prototype: JsValue = self.standard_objects().object_object().prototype().into();
@ -394,8 +497,8 @@ impl Context {
args: &[JsValue], args: &[JsValue],
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
match *f { match *f {
JsValue::Object(ref object) => object.call(this, args, self), JsValue::Object(ref object) if object.is_callable() => object.call(this, args, self),
_ => self.throw_type_error("not a function"), _ => self.throw_type_error("Value is not callable"),
} }
} }
@ -931,6 +1034,12 @@ impl Context {
&self.iterator_prototypes &self.iterator_prototypes
} }
/// Return the cached TypedArray constructor.
#[inline]
pub(crate) fn typed_array_constructor(&self) -> &StandardConstructor {
&self.typed_array_constructor
}
/// Return the core standard objects. /// Return the core standard objects.
#[inline] #[inline]
pub fn standard_objects(&self) -> &StandardObjects { pub fn standard_objects(&self) -> &StandardObjects {

10
boa/src/exec/tests.rs

@ -912,8 +912,8 @@ fn to_bigint() {
assert!(JsValue::null().to_bigint(&mut context).is_err()); assert!(JsValue::null().to_bigint(&mut context).is_err());
assert!(JsValue::undefined().to_bigint(&mut context).is_err()); assert!(JsValue::undefined().to_bigint(&mut context).is_err());
assert!(JsValue::new(55).to_bigint(&mut context).is_ok()); assert!(JsValue::new(55).to_bigint(&mut context).is_err());
assert!(JsValue::new(10.0).to_bigint(&mut context).is_ok()); assert!(JsValue::new(10.0).to_bigint(&mut context).is_err());
assert!(JsValue::new("100").to_bigint(&mut context).is_ok()); assert!(JsValue::new("100").to_bigint(&mut context).is_ok());
} }
@ -1262,9 +1262,9 @@ fn not_a_function() {
check_output(&[ check_output(&[
TestAction::Execute(init), TestAction::Execute(init),
TestAction::TestEq(scenario1, "\"TypeError: not a function\""), TestAction::TestEq(scenario1, "\"TypeError: Value is not callable\""),
TestAction::TestEq(scenario2, "\"TypeError: not a function\""), TestAction::TestEq(scenario2, "\"TypeError: Value is not callable\""),
TestAction::TestEq(scenario3, "\"TypeError: not a function\""), TestAction::TestEq(scenario3, "\"TypeError: Value is not callable\""),
]); ]);
} }

388
boa/src/object/internal_methods/integer_indexed.rs

@ -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(())
}

1
boa/src/object/internal_methods/mod.rs

@ -16,6 +16,7 @@ use crate::{
use super::PROTOTYPE; use super::PROTOTYPE;
pub(super) mod array; pub(super) mod array;
pub(super) mod integer_indexed;
pub(super) mod string; pub(super) mod string;
impl JsObject { impl JsObject {

42
boa/src/object/gcobject.rs → boa/src/object/jsobject.rs

@ -507,7 +507,7 @@ impl JsObject {
self.borrow_mut().set_prototype_instance(prototype) self.borrow_mut().set_prototype_instance(prototype)
} }
/// Checks if it an `Array` object. /// Checks if it's an `Array` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -529,6 +529,17 @@ impl JsObject {
self.borrow().is_array_iterator() self.borrow().is_array_iterator()
} }
/// Checks if it's an `ArrayBuffer` object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
#[inline]
#[track_caller]
pub fn is_array_buffer(&self) -> bool {
self.borrow().is_array_buffer()
}
/// Checks if it is a `Map` object.pub /// Checks if it is a `Map` object.pub
/// ///
/// # Panics /// # Panics
@ -540,7 +551,7 @@ impl JsObject {
self.borrow().is_map() self.borrow().is_map()
} }
/// Checks if it a `String` object. /// Checks if it's a `String` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -551,7 +562,7 @@ impl JsObject {
self.borrow().is_string() self.borrow().is_string()
} }
/// Checks if it a `Function` object. /// Checks if it's a `Function` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -562,7 +573,7 @@ impl JsObject {
self.borrow().is_function() self.borrow().is_function()
} }
/// Checks if it a Symbol object. /// Checks if it's a `Symbol` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -573,7 +584,7 @@ impl JsObject {
self.borrow().is_symbol() self.borrow().is_symbol()
} }
/// Checks if it an Error object. /// Checks if it's an `Error` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -584,7 +595,7 @@ impl JsObject {
self.borrow().is_error() self.borrow().is_error()
} }
/// Checks if it a Boolean object. /// Checks if it's a `Boolean` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -595,7 +606,7 @@ impl JsObject {
self.borrow().is_boolean() self.borrow().is_boolean()
} }
/// Checks if it a `Number` object. /// Checks if it's a `Number` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -606,7 +617,7 @@ impl JsObject {
self.borrow().is_number() self.borrow().is_number()
} }
/// Checks if it a `BigInt` object. /// Checks if it's a `BigInt` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -617,7 +628,7 @@ impl JsObject {
self.borrow().is_bigint() self.borrow().is_bigint()
} }
/// Checks if it a `RegExp` object. /// Checks if it's a `RegExp` object.
/// ///
/// # Panics /// # Panics
/// ///
@ -628,7 +639,18 @@ impl JsObject {
self.borrow().is_regexp() self.borrow().is_regexp()
} }
/// Checks if it an ordinary object. /// Checks if it's a `TypedArray` object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
#[inline]
#[track_caller]
pub fn is_typed_array(&self) -> bool {
self.borrow().is_typed_array()
}
/// Checks if it's an ordinary object.
/// ///
/// # Panics /// # Panics
/// ///

140
boa/src/object/mod.rs

@ -2,10 +2,11 @@
use crate::{ use crate::{
builtins::{ builtins::{
array::array_iterator::ArrayIterator, map::map_iterator::MapIterator, array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer,
map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator, map::map_iterator::MapIterator, map::ordered_map::OrderedMap,
set::ordered_set::OrderedSet, set::set_iterator::SetIterator, regexp::regexp_string_iterator::RegExpStringIterator, set::ordered_set::OrderedSet,
string::string_iterator::StringIterator, Date, RegExp, set::set_iterator::SetIterator, string::string_iterator::StringIterator,
typed_array::integer_indexed_object::IntegerIndexed, Date, RegExp,
}, },
context::StandardConstructor, context::StandardConstructor,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
@ -23,20 +24,20 @@ use std::{
mod tests; mod tests;
pub mod function; pub mod function;
mod gcobject;
pub(crate) mod internal_methods; pub(crate) mod internal_methods;
mod jsobject;
mod operations; mod operations;
mod property_map; mod property_map;
use crate::builtins::object::for_in_iterator::ForInIterator; use crate::builtins::object::for_in_iterator::ForInIterator;
pub use gcobject::{JsObject, RecursionLimiter, Ref, RefMut};
use internal_methods::InternalObjectMethods; use internal_methods::InternalObjectMethods;
pub use jsobject::{JsObject, RecursionLimiter, Ref, RefMut};
pub use operations::IntegrityLevel; pub use operations::IntegrityLevel;
pub use property_map::*; pub use property_map::*;
use self::internal_methods::{ use self::internal_methods::{
array::ARRAY_EXOTIC_INTERNAL_METHODS, string::STRING_EXOTIC_INTERNAL_METHODS, array::ARRAY_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
ORDINARY_INTERNAL_METHODS, string::STRING_EXOTIC_INTERNAL_METHODS, ORDINARY_INTERNAL_METHODS,
}; };
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
@ -89,6 +90,7 @@ pub struct ObjectData {
pub enum ObjectKind { pub enum ObjectKind {
Array, Array,
ArrayIterator(ArrayIterator), ArrayIterator(ArrayIterator),
ArrayBuffer(ArrayBuffer),
Map(OrderedMap<JsValue>), Map(OrderedMap<JsValue>),
MapIterator(MapIterator), MapIterator(MapIterator),
RegExp(Box<RegExp>), RegExp(Box<RegExp>),
@ -108,6 +110,7 @@ pub enum ObjectKind {
Date(Date), Date(Date),
Global, Global,
NativeObject(Box<dyn NativeObject>), NativeObject(Box<dyn NativeObject>),
IntegerIndexed(IntegerIndexed),
} }
impl ObjectData { impl ObjectData {
@ -127,6 +130,14 @@ impl ObjectData {
} }
} }
/// Create the `ArrayBuffer` object data
pub fn array_buffer(array_buffer: ArrayBuffer) -> Self {
Self {
kind: ObjectKind::ArrayBuffer(array_buffer),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Map` object data /// Create the `Map` object data
pub fn map(map: OrderedMap<JsValue>) -> Self { pub fn map(map: OrderedMap<JsValue>) -> Self {
Self { Self {
@ -278,16 +289,22 @@ impl ObjectData {
internal_methods: &ORDINARY_INTERNAL_METHODS, internal_methods: &ORDINARY_INTERNAL_METHODS,
} }
} }
/// Creates the `IntegerIndexed` object data
pub fn integer_indexed(integer_indexed: IntegerIndexed) -> Self {
Self {
kind: ObjectKind::IntegerIndexed(integer_indexed),
internal_methods: &INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
}
}
} }
impl Display for ObjectKind { impl Display for ObjectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( f.write_str(match self {
f,
"{}",
match self {
Self::Array => "Array", Self::Array => "Array",
Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayIterator(_) => "ArrayIterator",
Self::ArrayBuffer(_) => "ArrayBuffer",
Self::ForInIterator(_) => "ForInIterator", Self::ForInIterator(_) => "ForInIterator",
Self::Function(_) => "Function", Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp", Self::RegExp(_) => "RegExp",
@ -307,8 +324,8 @@ impl Display for ObjectKind {
Self::Date(_) => "Date", Self::Date(_) => "Date",
Self::Global => "Global", Self::Global => "Global",
Self::NativeObject(_) => "NativeObject", Self::NativeObject(_) => "NativeObject",
} Self::IntegerIndexed(_) => "TypedArray",
) })
} }
} }
@ -353,13 +370,12 @@ impl Object {
} }
} }
/// ObjectCreate is used to specify the runtime creation of new ordinary objects. /// `OrdinaryObjectCreate` is used to specify the runtime creation of new ordinary objects.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-objectcreate /// [spec]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
// TODO: proto should be a &Value here
#[inline] #[inline]
pub fn create(proto: JsValue) -> Self { pub fn create(proto: JsValue) -> Self {
let mut obj = Self::new(); let mut obj = Self::new();
@ -511,6 +527,40 @@ impl Object {
} }
} }
/// Checks if it an `ArrayBuffer` object.
#[inline]
pub fn is_array_buffer(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::ArrayBuffer(_),
..
}
)
}
#[inline]
pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
match &self.data {
ObjectData {
kind: ObjectKind::ArrayBuffer(buffer),
..
} => Some(buffer),
_ => None,
}
}
#[inline]
pub fn as_array_buffer_mut(&mut self) -> Option<&mut ArrayBuffer> {
match &mut self.data {
ObjectData {
kind: ObjectKind::ArrayBuffer(buffer),
..
} => Some(buffer),
_ => None,
}
}
#[inline] #[inline]
pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> { pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> {
match &mut self.data { match &mut self.data {
@ -849,6 +899,7 @@ impl Object {
) )
} }
#[inline]
pub fn as_date(&self) -> Option<&Date> { pub fn as_date(&self) -> Option<&Date> {
match self.data { match self.data {
ObjectData { ObjectData {
@ -871,6 +922,7 @@ impl Object {
) )
} }
/// Gets the regexp data if the object is a regexp.
#[inline] #[inline]
pub fn as_regexp(&self) -> Option<&RegExp> { pub fn as_regexp(&self) -> Option<&RegExp> {
match self.data { match self.data {
@ -882,6 +934,42 @@ impl Object {
} }
} }
/// Checks if it a `TypedArray` object.
#[inline]
pub fn is_typed_array(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::IntegerIndexed(_),
..
}
)
}
/// Gets the typed array data (integer indexed object) if this is a typed array.
#[inline]
pub fn as_typed_array(&self) -> Option<&IntegerIndexed> {
match self.data {
ObjectData {
kind: ObjectKind::IntegerIndexed(ref integer_indexed_object),
..
} => Some(integer_indexed_object),
_ => None,
}
}
/// Gets the typed array data (integer indexed object) if this is a typed array.
#[inline]
pub fn as_typed_array_mut(&mut self) -> Option<&mut IntegerIndexed> {
match self.data {
ObjectData {
kind: ObjectKind::IntegerIndexed(ref mut integer_indexed_object),
..
} => Some(integer_indexed_object),
_ => None,
}
}
/// Checks if it an ordinary object. /// Checks if it an ordinary object.
#[inline] #[inline]
pub fn is_ordinary(&self) -> bool { pub fn is_ordinary(&self) -> bool {
@ -894,6 +982,7 @@ impl Object {
) )
} }
/// Gets the prototype instance of this object.
#[inline] #[inline]
pub fn prototype_instance(&self) -> &JsValue { pub fn prototype_instance(&self) -> &JsValue {
&self.prototype &self.prototype
@ -1357,6 +1446,7 @@ pub struct ConstructorBuilder<'context> {
callable: bool, callable: bool,
constructable: bool, constructable: bool,
inherit: Option<JsValue>, inherit: Option<JsValue>,
custom_prototype: Option<JsValue>,
} }
impl Debug for ConstructorBuilder<'_> { impl Debug for ConstructorBuilder<'_> {
@ -1387,6 +1477,7 @@ impl<'context> ConstructorBuilder<'context> {
callable: true, callable: true,
constructable: true, constructable: true,
inherit: None, inherit: None,
custom_prototype: None,
} }
} }
@ -1406,6 +1497,7 @@ impl<'context> ConstructorBuilder<'context> {
callable: true, callable: true,
constructable: true, constructable: true,
inherit: None, inherit: None,
custom_prototype: None,
} }
} }
@ -1614,6 +1706,15 @@ impl<'context> ConstructorBuilder<'context> {
self self
} }
/// Specify the __proto__ for this constructor.
///
/// Default is `Function.prototype`
#[inline]
pub fn custom_prototype(&mut self, prototype: JsValue) -> &mut Self {
self.custom_prototype = Some(prototype);
self
}
/// Return the current context. /// Return the current context.
#[inline] #[inline]
pub fn context(&mut self) -> &'_ mut Context { pub fn context(&mut self) -> &'_ mut Context {
@ -1645,6 +1746,9 @@ impl<'context> ConstructorBuilder<'context> {
constructor.insert("length", length); constructor.insert("length", length);
constructor.insert("name", name); constructor.insert("name", name);
if let Some(proto) = &self.custom_prototype {
constructor.set_prototype_instance(proto.clone());
} else {
constructor.set_prototype_instance( constructor.set_prototype_instance(
self.context self.context
.standard_objects() .standard_objects()
@ -1652,7 +1756,7 @@ impl<'context> ConstructorBuilder<'context> {
.prototype() .prototype()
.into(), .into(),
); );
}
constructor.insert_property( constructor.insert_property(
PROTOTYPE, PROTOTYPE,
PropertyDescriptor::builder() PropertyDescriptor::builder()

129
boa/src/object/operations.rs

@ -1,5 +1,6 @@
use crate::{ use crate::{
builtins::Array, builtins::Array,
context::{StandardConstructor, StandardObjects},
object::JsObject, object::JsObject,
property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, property::{PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -211,35 +212,6 @@ impl JsObject {
Ok(success) Ok(success)
} }
/// Retrieves value of specific property, when the value of the property is expected to be a function.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub(crate) fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
let value = self.get(key, context)?;
// 3. If func is either undefined or null, return undefined.
if value.is_null_or_undefined() {
return Ok(None);
}
// 4. If IsCallable(func) is false, throw a TypeError exception.
// 5. Return func.
match value.as_object() {
Some(object) if object.is_callable() => Ok(Some(object)),
_ => Err(context
.construct_type_error("value returned for property of object is not a function")),
}
}
/// Check if object has property. /// Check if object has property.
/// ///
/// More information: /// More information:
@ -452,11 +424,14 @@ impl JsObject {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor /// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
pub(crate) fn species_constructor( pub(crate) fn species_constructor<F>(
&self, &self,
default_constructor: JsValue, default_constructor: F,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject>
where
F: FnOnce(&StandardObjects) -> &StandardConstructor,
{
// 1. Assert: Type(O) is Object. // 1. Assert: Type(O) is Object.
// 2. Let C be ? Get(O, "constructor"). // 2. Let C be ? Get(O, "constructor").
@ -464,12 +439,12 @@ impl JsObject {
// 3. If C is undefined, return defaultConstructor. // 3. If C is undefined, return defaultConstructor.
if c.is_undefined() { if c.is_undefined() {
return Ok(default_constructor); return Ok(default_constructor(context.standard_objects()).constructor());
} }
// 4. If Type(C) is not Object, throw a TypeError exception. // 4. If Type(C) is not Object, throw a TypeError exception.
if !c.is_object() { if !c.is_object() {
return context.throw_type_error("property 'constructor' is not an object"); return Err(context.construct_type_error("property 'constructor' is not an object"));
} }
// 5. Let S be ? Get(C, @@species). // 5. Let S be ? Get(C, @@species).
@ -477,19 +452,19 @@ impl JsObject {
// 6. If S is either undefined or null, return defaultConstructor. // 6. If S is either undefined or null, return defaultConstructor.
if s.is_null_or_undefined() { if s.is_null_or_undefined() {
return Ok(default_constructor); return Ok(default_constructor(context.standard_objects()).constructor());
} }
// 7. If IsConstructor(S) is true, return S. // 7. If IsConstructor(S) is true, return S.
// 8. Throw a TypeError exception. // 8. Throw a TypeError exception.
if let Some(obj) = s.as_object() { if let Some(obj) = s.as_object() {
if obj.is_constructable() { if obj.is_constructable() {
Ok(s) Ok(obj)
} else { } else {
context.throw_type_error("property 'constructor' is not a constructor") Err(context.construct_type_error("property 'constructor' is not a constructor"))
} }
} else { } else {
context.throw_type_error("property 'constructor' is not an object") Err(context.construct_type_error("property 'constructor' is not an object"))
} }
} }
@ -575,9 +550,58 @@ impl JsObject {
} }
impl JsValue { impl JsValue {
// todo: GetV /// Abstract operation `GetV ( V, P )`.
///
/// Retrieves the value of a specific property of an ECMAScript language value. If the value is
/// not an object, the property lookup is performed using a wrapper object appropriate for the
/// type of the value.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub(crate) fn get_v<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
// 1. Let O be ? ToObject(V).
let o = self.to_object(context)?;
// todo: GetMethod // 2. Return ? O.[[Get]](P, V).
o.__get__(&key.into(), self.clone(), context)
}
/// Abstract operation `GetMethod ( V, P )`
///
/// Retrieves the value of a specific property, when the value of the property is expected to be a function.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
let func = self.get_v(key, context)?;
// 3. If func is either undefined or null, return undefined.
if func.is_null_or_undefined() {
return Ok(JsValue::undefined());
}
// 4. If IsCallable(func) is false, throw a TypeError exception.
if !func.is_callable() {
Err(context
.construct_type_error("value returned for property of object is not a function"))
} else {
// 5. Return func.
Ok(func)
}
}
/// It is used to create List value whose elements are provided by the indexed properties of /// It is used to create List value whose elements are provided by the indexed properties of
/// self. /// self.
@ -636,4 +660,29 @@ impl JsValue {
// 7. Return list. // 7. Return list.
Ok(list) Ok(list)
} }
/// Abstract operation `( V, P [ , argumentsList ] )
///
/// Calls a method property of an ECMAScript language value.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-invoke
pub(crate) fn invoke<K>(
&self,
key: K,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. Let func be ? GetV(V, P).
let func = self.get_v(key, context)?;
// 3. Return ? Call(func, V, argumentsList)
context.call(&func, self, args)
}
} }

3
boa/src/string.rs

@ -305,6 +305,7 @@ impl Inner {
/// on the stack and a pointer to the data (this is also known as fat pointers). /// on the stack and a pointer to the data (this is also known as fat pointers).
/// The `JsString` length and data is stored on the heap. and just an non-null /// The `JsString` length and data is stored on the heap. and just an non-null
/// pointer is kept, so its size is the size of a pointer. /// pointer is kept, so its size is the size of a pointer.
#[derive(Finalize)]
pub struct JsString { pub struct JsString {
inner: NonNull<Inner>, inner: NonNull<Inner>,
_marker: PhantomData<std::rc::Rc<str>>, _marker: PhantomData<std::rc::Rc<str>>,
@ -487,8 +488,6 @@ impl JsString {
} }
} }
impl Finalize for JsString {}
// Safety: [`JsString`] does not contain any objects which recquire trace, // Safety: [`JsString`] does not contain any objects which recquire trace,
// so this is safe. // so this is safe.
unsafe impl Trace for JsString { unsafe impl Trace for JsString {

8
boa/src/symbol.rs

@ -42,7 +42,7 @@ pub struct WellKnownSymbols {
has_instance: JsSymbol, has_instance: JsSymbol,
is_concat_spreadable: JsSymbol, is_concat_spreadable: JsSymbol,
iterator: JsSymbol, iterator: JsSymbol,
match_: JsSymbol, r#match: JsSymbol,
match_all: JsSymbol, match_all: JsSymbol,
replace: JsSymbol, replace: JsSymbol,
search: JsSymbol, search: JsSymbol,
@ -106,7 +106,7 @@ impl WellKnownSymbols {
has_instance, has_instance,
is_concat_spreadable, is_concat_spreadable,
iterator, iterator,
match_, r#match: match_,
match_all, match_all,
replace, replace,
search, search,
@ -161,8 +161,8 @@ impl WellKnownSymbols {
/// A regular expression method that matches the regular expression /// A regular expression method that matches the regular expression
/// against a string. Called by the `String.prototype.match` method. /// against a string. Called by the `String.prototype.match` method.
#[inline] #[inline]
pub fn match_() -> JsSymbol { pub fn r#match() -> JsSymbol {
WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_.clone()) WELL_KNOW_SYMBOLS.with(|symbols| symbols.r#match.clone())
} }
/// The `Symbol.matchAll` well known symbol. /// The `Symbol.matchAll` well known symbol.

4
boa/src/syntax/ast/node/array/mod.rs

@ -2,7 +2,7 @@
use super::{join_nodes, Node}; use super::{join_nodes, Node};
use crate::{ use crate::{
builtins::{iterable, Array}, builtins::Array,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
@ -46,7 +46,7 @@ impl Executable for ArrayDecl {
for elem in self.as_ref() { for elem in self.as_ref() {
if let Node::Spread(ref x) = elem { if let Node::Spread(ref x) = elem {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(&val, context)?; let iterator_record = val.get_iterator(context, None, None)?;
// TODO after proper internal Array representation as per https://github.com/boa-dev/boa/pull/811#discussion_r502460858 // TODO after proper internal Array representation as per https://github.com/boa-dev/boa/pull/811#discussion_r502460858
// next_index variable should be utilized here as per https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation // next_index variable should be utilized here as per https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation
// let mut next_index = 0; // let mut next_index = 0;

7
boa/src/syntax/ast/node/call/mod.rs

@ -1,5 +1,4 @@
use crate::{ use crate::{
builtins::iterable,
exec::Executable, exec::Executable,
exec::InterpreterState, exec::InterpreterState,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
@ -66,7 +65,7 @@ impl Executable for Call {
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(context)?; let mut obj = get_const_field.obj().run(context)?;
if !obj.is_object() { if !obj.is_object() {
obj = JsValue::Object(obj.to_object(context)?); obj = JsValue::from(obj.to_object(context)?);
} }
( (
obj.clone(), obj.clone(),
@ -76,7 +75,7 @@ impl Executable for Call {
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let mut obj = get_field.obj().run(context)?; let mut obj = get_field.obj().run(context)?;
if !obj.is_object() { if !obj.is_object() {
obj = JsValue::Object(obj.to_object(context)?); obj = JsValue::from(obj.to_object(context)?);
} }
let field = get_field.field().run(context)?; let field = get_field.field().run(context)?;
( (
@ -94,7 +93,7 @@ impl Executable for Call {
for arg in self.args() { for arg in self.args() {
if let Node::Spread(ref x) = arg { if let Node::Spread(ref x) = arg {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(&val, context)?; let iterator_record = val.get_iterator(context, None, None)?;
loop { loop {
let next = iterator_record.next(context)?; let next = iterator_record.next(context)?;
if next.done { if next.done {

4
boa/src/syntax/ast/node/declaration/mod.rs

@ -1,6 +1,6 @@
//! Declaration nodes //! Declaration nodes
use crate::{ use crate::{
builtins::{iterable::get_iterator, Array}, builtins::Array,
environment::lexical_environment::VariableScope, environment::lexical_environment::VariableScope,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
@ -673,7 +673,7 @@ impl DeclarationPatternArray {
} }
// 1. Let iteratorRecord be ? GetIterator(value). // 1. Let iteratorRecord be ? GetIterator(value).
let iterator = get_iterator(&value, context)?; let iterator = value.get_iterator(context, None, None)?;
let mut result = Vec::new(); let mut result = Vec::new();
// 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment. // 2. Let result be IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment.

3
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -1,5 +1,4 @@
use crate::{ use crate::{
builtins::iterable::get_iterator,
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope, lexical_environment::VariableScope,
@ -83,7 +82,7 @@ impl Executable for ForOfLoop {
fn run(&self, context: &mut Context) -> JsResult<JsValue> { fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("ForOf", "exec"); let _timer = BoaProfiler::global().start_event("ForOf", "exec");
let iterable = self.iterable().run(context)?; let iterable = self.iterable().run(context)?;
let iterator = get_iterator(&iterable, context)?; let iterator = iterable.get_iterator(context, None, None)?;
let mut result = JsValue::undefined(); let mut result = JsValue::undefined();
loop { loop {

3
boa/src/syntax/ast/node/new/mod.rs

@ -1,5 +1,4 @@
use crate::{ use crate::{
builtins::iterable,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
syntax::ast::node::{Call, Node}, syntax::ast::node::{Call, Node},
@ -56,7 +55,7 @@ impl Executable for New {
for arg in self.args() { for arg in self.args() {
if let Node::Spread(ref x) = arg { if let Node::Spread(ref x) = arg {
let val = x.run(context)?; let val = x.run(context)?;
let iterator_record = iterable::get_iterator(&val, context)?; let iterator_record = val.get_iterator(context, None, None)?;
loop { loop {
let next = iterator_record.next(context)?; let next = iterator_record.next(context)?;
if next.done { if next.done {

39
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -147,28 +147,33 @@ impl Executable for BinOp {
context.has_property(&y, &key)? context.has_property(&y, &key)?
} }
CompOp::InstanceOf => { CompOp::InstanceOf => {
if let Some(object) = y.as_object() { // <https://tc39.es/ecma262/#sec-instanceofoperator>
let key = WellKnownSymbols::has_instance(); // TODO: move to a separate instance_of_operator function
// 1. If Type(target) is not Object, throw a TypeError exception.
match object.get_method(context, key)? { if !y.is_object() {
Some(instance_of_handler) => {
instance_of_handler.call(&y, &[x], context)?.to_boolean()
}
None if object.is_callable() => {
object.ordinary_has_instance(context, &x)?
}
None => {
return context.throw_type_error(
"right-hand side of 'instanceof' is not callable",
);
}
}
} else {
return context.throw_type_error(format!( return context.throw_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}", "right-hand side of 'instanceof' should be an object, got {}",
y.type_of() y.type_of()
)); ));
} }
// 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
let inst_of_handler =
y.get_method(WellKnownSymbols::has_instance(), context)?;
// 3. If instOfHandler is not undefined, then
if !inst_of_handler.is_undefined() {
// a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).
context.call(&inst_of_handler, &y, &[x])?.to_boolean()
} else if !y.is_callable() {
// 4. If IsCallable(target) is false, throw a TypeError exception.
return Err(context.construct_type_error(
"right-hand side of 'instanceof' is not callable",
));
} else {
// 5. Return ? OrdinaryHasInstance(target, V).
y.ordinary_has_instance(context, &x)?
}
} }
})) }))
} }

7
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -57,8 +57,11 @@ impl Executable for UnaryOp {
op::UnaryOp::IncrementPost => { op::UnaryOp::IncrementPost => {
let x = self.target().run(context)?; let x = self.target().run(context)?;
let ret = x.clone(); let ret = x.clone();
let result = x.to_number(context)? + 1.0; let result = match x.to_numeric(context)? {
context.set_value(self.target(), result.into())?; Numeric::Number(n) => (n + 1.0).into(),
Numeric::BigInt(b) => (JsBigInt::add(&b, &JsBigInt::from(1))).into(),
};
context.set_value(self.target(), result)?;
ret ret
} }
op::UnaryOp::IncrementPre => { op::UnaryOp::IncrementPre => {

320
boa/src/value/mod.rs

@ -16,10 +16,15 @@ use crate::{
BoaProfiler, Context, JsBigInt, JsResult, JsString, BoaProfiler, Context, JsBigInt, JsResult, JsString,
}; };
use gc::{Finalize, Trace}; use gc::{Finalize, Trace};
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::Zero;
use once_cell::sync::Lazy;
use std::{ use std::{
collections::HashSet, collections::HashSet,
convert::TryFrom, convert::TryFrom,
fmt::{self, Display}, fmt::{self, Display},
ops::{Deref, Sub},
str::FromStr, str::FromStr,
}; };
@ -37,6 +42,16 @@ pub use hash::*;
pub use operations::*; pub use operations::*;
pub use r#type::Type; pub use r#type::Type;
static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_64: u128 = 2u128.pow(64);
BigInt::from(TWO_E_64)
});
static TWO_E_63: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_63: u128 = 2u128.pow(63);
BigInt::from(TWO_E_63)
});
/// A Javascript value /// A Javascript value
#[derive(Trace, Finalize, Debug, Clone)] #[derive(Trace, Finalize, Debug, Clone)]
pub enum JsValue { pub enum JsValue {
@ -108,7 +123,7 @@ impl JsValue {
Self::Rational(f64::NEG_INFINITY) Self::Rational(f64::NEG_INFINITY)
} }
/// Returns a new empty object /// Returns a new empty object with the `%Object.prototype%` prototype.
pub(crate) fn new_object(context: &Context) -> Self { pub(crate) fn new_object(context: &Context) -> Self {
let _timer = BoaProfiler::global().start_event("new_object", "value"); let _timer = BoaProfiler::global().start_event("new_object", "value");
context.construct_object().into() context.construct_object().into()
@ -378,17 +393,28 @@ impl JsValue {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check) // 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then // 2. If Type(input) is Object, then
if let JsValue::Object(obj) = self { if self.is_object() {
if let Some(exotic_to_prim) = // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
obj.get_method(context, WellKnownSymbols::to_primitive())? let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?;
{
// b. If exoticToPrim is not undefined, then
if !exotic_to_prim.is_undefined() {
// i. If preferredType is not present, let hint be "default".
// ii. Else if preferredType is string, let hint be "string".
// iii. Else,
// 1. Assert: preferredType is number.
// 2. Let hint be "number".
let hint = match preferred_type { let hint = match preferred_type {
PreferredType::Default => "default",
PreferredType::String => "string", PreferredType::String => "string",
PreferredType::Number => "number", PreferredType::Number => "number",
PreferredType::Default => "default",
} }
.into(); .into();
let result = exotic_to_prim.call(self, &[hint], context)?;
// iv. Let result be ? Call(exoticToPrim, input, « hint »).
let result = context.call(&exotic_to_prim, self, &[hint])?;
// v. If Type(result) is not Object, return result.
// vi. Throw a TypeError exception.
return if result.is_object() { return if result.is_object() {
Err(context.construct_type_error("Symbol.toPrimitive cannot return an object")) Err(context.construct_type_error("Symbol.toPrimitive cannot return an object"))
} else { } else {
@ -396,23 +422,28 @@ impl JsValue {
}; };
} }
let mut hint = preferred_type; // c. If preferredType is not present, let preferredType be number.
let preferred_type = match preferred_type {
if hint == PreferredType::Default { PreferredType::Default | PreferredType::Number => PreferredType::Number,
hint = PreferredType::Number; PreferredType::String => PreferredType::String,
}; };
// g. Return ? OrdinaryToPrimitive(input, hint). // d. Return ? OrdinaryToPrimitive(input, preferredType).
obj.ordinary_to_primitive(context, hint) self.as_object()
.expect("self was not an object")
.ordinary_to_primitive(context, preferred_type)
} else { } else {
// 3. Return input. // 3. Return input.
Ok(self.clone()) Ok(self.clone())
} }
} }
/// Converts the value to a `BigInt`. /// `7.1.13 ToBigInt ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// ///
/// This function is equivelent to `BigInt(value)` in JavaScript. /// [spec]: https://tc39.es/ecma262/#sec-tobigint
pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> { pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
match self { match self {
JsValue::Null => Err(context.construct_type_error("cannot convert null to a BigInt")), JsValue::Null => Err(context.construct_type_error("cannot convert null to a BigInt")),
@ -431,15 +462,8 @@ impl JsValue {
} }
JsValue::Boolean(true) => Ok(JsBigInt::one()), JsValue::Boolean(true) => Ok(JsBigInt::one()),
JsValue::Boolean(false) => Ok(JsBigInt::zero()), JsValue::Boolean(false) => Ok(JsBigInt::zero()),
JsValue::Integer(num) => Ok(JsBigInt::new(*num)), JsValue::Integer(_) | JsValue::Rational(_) => {
JsValue::Rational(num) => { Err(context.construct_type_error("cannot convert Number to a BigInt"))
if let Ok(bigint) = JsBigInt::try_from(*num) {
return Ok(bigint);
}
Err(context.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
num
)))
} }
JsValue::BigInt(b) => Ok(b.clone()), JsValue::BigInt(b) => Ok(b.clone()),
JsValue::Object(_) => { JsValue::Object(_) => {
@ -613,6 +637,200 @@ impl JsValue {
Ok(f64_to_int32(number)) Ok(f64_to_int32(number))
} }
/// `7.1.10 ToInt8 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-toint8
pub fn to_int8(&self, context: &mut Context) -> JsResult<i8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.floor() as i64;
// 4. Let int8bit be int modulo 2^8.
let int_8_bit = int % 2i64.pow(8);
// 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit).
if int_8_bit >= 2i64.pow(7) {
Ok((int_8_bit - 2i64.pow(8)) as i8)
} else {
Ok(int_8_bit as i8)
}
}
/// `7.1.11 ToUint8 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint8
pub fn to_uint8(&self, context: &mut Context) -> JsResult<u8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.floor() as i64;
// 4. Let int8bit be int modulo 2^8.
let int_8_bit = int % 2i64.pow(8);
// 5. Return 𝔽(int8bit).
Ok(int_8_bit as u8)
}
/// `7.1.12 ToUint8Clamp ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint8clamp
pub fn to_uint8_clamp(&self, context: &mut Context) -> JsResult<u8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, return +0𝔽.
if number.is_nan() {
return Ok(0);
}
// 3. If ℝ(number) ≤ 0, return +0𝔽.
if number <= 0.0 {
return Ok(0);
}
// 4. If ℝ(number) ≥ 255, return 255𝔽.
if number >= 255.0 {
return Ok(255);
}
// 5. Let f be floor(ℝ(number)).
let f = number.floor();
// 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1).
if f + 0.5 < number {
return Ok(f as u8 + 1);
}
// 7. If ℝ(number) < f + 0.5, return 𝔽(f).
if number < f + 0.5 {
return Ok(f as u8);
}
// 8. If f is odd, return 𝔽(f + 1).
if f as u8 % 2 != 0 {
return Ok(f as u8 + 1);
}
// 9. Return 𝔽(f).
Ok(f as u8)
}
/// `7.1.8 ToInt16 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-toint16
pub fn to_int16(&self, context: &mut Context) -> JsResult<i16> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.floor() as i64;
// 4. Let int16bit be int modulo 2^16.
let int_16_bit = int % 2i64.pow(16);
// 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit).
if int_16_bit >= 2i64.pow(15) {
Ok((int_16_bit - 2i64.pow(16)) as i16)
} else {
Ok(int_16_bit as i16)
}
}
/// `7.1.9 ToUint16 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint16
pub fn to_uint16(&self, context: &mut Context) -> JsResult<u16> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.floor() as i64;
// 4. Let int16bit be int modulo 2^16.
let int_16_bit = int % 2i64.pow(16);
// 5. Return 𝔽(int16bit).
Ok(int_16_bit as u16)
}
/// `7.1.15 ToBigInt64 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tobigint64
pub fn to_big_int64(&self, context: &mut Context) -> JsResult<BigInt> {
// 1. Let n be ? ToBigInt(argument).
let n = self.to_bigint(context)?;
// 2. Let int64bit be ℝ(n) modulo 2^64.
let int64_bit = n.as_inner().mod_floor(&TWO_E_64);
// 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit).
if &int64_bit >= TWO_E_63.deref() {
Ok(int64_bit.sub(TWO_E_64.deref()))
} else {
Ok(int64_bit)
}
}
/// `7.1.16 ToBigUint64 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tobiguint64
pub fn to_big_uint64(&self, context: &mut Context) -> JsResult<BigInt> {
let two_e_64: u128 = 0x1_0000_0000_0000_0000;
let two_e_64 = BigInt::from(two_e_64);
// 1. Let n be ? ToBigInt(argument).
let n = self.to_bigint(context)?;
// 2. Let int64bit be ℝ(n) modulo 2^64.
// 3. Return ℤ(int64bit).
Ok(n.as_inner().mod_floor(&two_e_64))
}
/// Converts a value to a non-negative integer if it is a valid integer index value. /// Converts a value to a non-negative integer if it is a valid integer index value.
/// ///
/// See: <https://tc39.es/ecma262/#sec-toindex> /// See: <https://tc39.es/ecma262/#sec-toindex>
@ -821,6 +1039,39 @@ impl JsValue {
Ok(false) Ok(false)
} }
} }
/// It determines if the value is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[track_caller]
pub(crate) fn is_callable(&self) -> bool {
if let Self::Object(obj) = self {
obj.is_callable()
} else {
false
}
}
/// Determines if `value` inherits from the instance object inheritance path.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
pub(crate) fn ordinary_has_instance(
&self,
context: &mut Context,
value: &JsValue,
) -> JsResult<bool> {
if let Self::Object(obj) = self {
obj.ordinary_has_instance(context, value)
} else {
Ok(false)
}
}
} }
impl Default for JsValue { impl Default for JsValue {
@ -853,6 +1104,20 @@ impl From<f64> for Numeric {
} }
} }
impl From<f32> for Numeric {
#[inline]
fn from(value: f32) -> Self {
Self::Number(value.into())
}
}
impl From<i64> for Numeric {
#[inline]
fn from(value: i64) -> Self {
Self::BigInt(value.into())
}
}
impl From<i32> for Numeric { impl From<i32> for Numeric {
#[inline] #[inline]
fn from(value: i32) -> Self { fn from(value: i32) -> Self {
@ -874,6 +1139,13 @@ impl From<i8> for Numeric {
} }
} }
impl From<u64> for Numeric {
#[inline]
fn from(value: u64) -> Self {
Self::BigInt(value.into())
}
}
impl From<u32> for Numeric { impl From<u32> for Numeric {
#[inline] #[inline]
fn from(value: u32) -> Self { fn from(value: u32) -> Self {

33
boa/src/vm/mod.rs

@ -226,29 +226,24 @@ impl Context {
self.vm.push(value); self.vm.push(value);
} }
Opcode::InstanceOf => { Opcode::InstanceOf => {
let y = self.vm.pop(); let target = self.vm.pop();
let x = self.vm.pop(); let v = self.vm.pop();
let value = if let Some(object) = y.as_object() { if !target.is_object() {
let key = WellKnownSymbols::has_instance();
match object.get_method(self, key)? {
Some(instance_of_handler) => {
instance_of_handler.call(&y, &[x], self)?.to_boolean()
}
None if object.is_callable() => object.ordinary_has_instance(self, &x)?,
None => {
return Err(self.construct_type_error(
"right-hand side of 'instanceof' is not callable",
));
}
}
} else {
return Err(self.construct_type_error(format!( return Err(self.construct_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}", "right-hand side of 'instanceof' should be an object, got {}",
y.type_of() target.type_of()
))); )));
}; };
let handler = target.get_method(WellKnownSymbols::has_instance(), self)?;
if !handler.is_undefined() {
let value = self.call(&handler, &target, &[v.clone()])?.to_boolean();
self.vm.push(value);
}
if !target.is_callable() {
return Err(self
.construct_type_error("right-hand side of 'instanceof' is not callable"));
}
let value = target.ordinary_has_instance(self, &v)?;
self.vm.push(value); self.vm.push(value);
} }
Opcode::Void => { Opcode::Void => {

40
boa_tester/src/exec/js262.rs

@ -1,4 +1,5 @@
use boa::{ use boa::{
builtins::JsArgs,
exec::Executable, exec::Executable,
object::{JsObject, ObjectInitializer}, object::{JsObject, ObjectInitializer},
property::Attribute, property::Attribute,
@ -11,6 +12,7 @@ pub(super) fn init(context: &mut Context) -> JsObject {
let obj = ObjectInitializer::new(context) let obj = ObjectInitializer::new(context)
.function(create_realm, "createRealm", 0) .function(create_realm, "createRealm", 0)
.function(detach_array_buffer, "detachArrayBuffer", 2)
.function(eval_script, "evalScript", 1) .function(eval_script, "evalScript", 1)
.property("global", global_obj, Attribute::default()) .property("global", global_obj, Attribute::default())
// .property("agent", agent, Attribute::default()) // .property("agent", agent, Attribute::default())
@ -39,13 +41,43 @@ fn create_realm(_this: &JsValue, _: &[JsValue], _context: &mut Context) -> JsRes
/// The `$262.detachArrayBuffer()` function. /// The `$262.detachArrayBuffer()` function.
/// ///
/// Implements the `DetachArrayBuffer` abstract operation. /// Implements the `DetachArrayBuffer` abstract operation.
#[allow(dead_code)]
fn detach_array_buffer( fn detach_array_buffer(
_this: &JsValue, _this: &JsValue,
_: &[JsValue], args: &[JsValue],
_context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
todo!() #[inline]
fn type_err(context: &mut Context) -> JsValue {
context.construct_type_error("The provided object was not an ArrayBuffer")
}
let array_buffer = args
.get(0)
.map(JsValue::as_object)
.flatten()
.ok_or_else(|| type_err(context))?;
let mut array_buffer = array_buffer.borrow_mut();
let array_buffer = array_buffer
.as_array_buffer_mut()
.ok_or_else(|| type_err(context))?;
// 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. TODO
// 2. If key is not present, set key to undefined.
let key = args.get_or_undefined(1);
// 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception.
if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) {
return Err(context.construct_type_error("Cannot detach array buffer with different key"));
}
// 4. Set arrayBuffer.[[ArrayBufferData]] to null.
array_buffer.array_buffer_data = None;
// 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0.
array_buffer.array_buffer_byte_length = 0;
// 6. Return NormalCompletion(null).
Ok(JsValue::null())
} }
/// The `$262.evalScript()` function. /// The `$262.evalScript()` function.

1
boa_tester/src/results.rs

@ -441,6 +441,7 @@ fn compute_result_diff(
match (base_test.result, new_test.result) { match (base_test.result, new_test.result) {
(a, b) if a == b => {} (a, b) if a == b => {}
(TestOutcomeResult::Ignored, TestOutcomeResult::Failed) => {}
(_, TestOutcomeResult::Passed) => final_diff.fixed.push(test_name), (_, TestOutcomeResult::Passed) => final_diff.fixed.push(test_name),
(TestOutcomeResult::Panic, _) => final_diff.panic_fixes.push(test_name), (TestOutcomeResult::Panic, _) => final_diff.panic_fixes.push(test_name),

4
test_ignore.txt

@ -3,8 +3,10 @@ flag:module
flag:async flag:async
// Non-implemented features: // Non-implemented features:
feature:TypedArray
feature:json-modules feature:json-modules
feature:DataView
feature:SharedArrayBuffer
feature:resizable-arraybuffer
//feature:generators //feature:generators
//feature:async-iteration //feature:async-iteration
//feature:class //feature:class

Loading…
Cancel
Save