From e137f39a795b5726648c8b8849ccf44540bf3988 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:25:26 +0100 Subject: [PATCH] add `Array.prototype.splice` --- crates/dash_middle/src/interner.rs | 1 + crates/dash_optimizer/src/type_infer.rs | 2 +- crates/dash_vm/src/js_std/array.rs | 109 +++++++++++++++++++++--- crates/dash_vm/src/lib.rs | 1 + crates/dash_vm/src/statics.rs | 4 +- crates/dash_vm/src/value/array/mod.rs | 13 ++- testrunner/src/cmd/run.rs | 4 +- 7 files changed, 117 insertions(+), 17 deletions(-) diff --git a/crates/dash_middle/src/interner.rs b/crates/dash_middle/src/interner.rs index 98565a1c..1b5c525f 100644 --- a/crates/dash_middle/src/interner.rs +++ b/crates/dash_middle/src/interner.rs @@ -224,6 +224,7 @@ pub mod sym { sort, unshift, slice, + splice, asyncIterator, hasInstance, iterator, diff --git a/crates/dash_optimizer/src/type_infer.rs b/crates/dash_optimizer/src/type_infer.rs index e825cd5c..24413f11 100644 --- a/crates/dash_optimizer/src/type_infer.rs +++ b/crates/dash_optimizer/src/type_infer.rs @@ -394,7 +394,7 @@ impl<'s> TypeInferCtx<'s> { ExprKind::Class(class) => self.visit_class_expression(class), ExprKind::Array(expr) => self.visit_array_expression(expr), ExprKind::Object(expr) => self.visit_object_expression(expr), - ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(&**base), + ExprKind::Chaining(OptionalChainingExpression { base, components: _ }) => self.visit(base), ExprKind::Compiled(..) => None, ExprKind::Empty => None, ExprKind::YieldStar(e) => { diff --git a/crates/dash_vm/src/js_std/array.rs b/crates/dash_vm/src/js_std/array.rs index 41b09e9b..735d5f58 100644 --- a/crates/dash_vm/src/js_std/array.rs +++ b/crates/dash_vm/src/js_std/array.rs @@ -1,19 +1,19 @@ +use ControlFlow::{Break, Continue}; use std::cmp; use std::convert::Infallible; use std::ops::{ControlFlow, Range}; -use ControlFlow::{Break, Continue}; use crate::frame::This; use crate::localscope::LocalScope; use crate::throw; -use crate::value::array::{Array, ArrayIterator}; +use crate::value::array::{Array, ArrayIterator, require_valid_array_length}; use crate::value::function::native::CallContext; -use crate::value::object::{Object as _, PropertyValue}; +use crate::value::object::{Object as _, PropertyKey, PropertyValue}; use crate::value::ops::conversions::ValueConversion; use crate::value::ops::equality::strict_eq; use crate::value::root_ext::RootErrExt; use crate::value::string::JsString; -use crate::value::{array, Root, Unpack, Value, ValueContext}; +use crate::value::{Root, Unpack, Value, ValueContext, array}; use dash_middle::interner::sym; pub fn constructor(cx: CallContext) -> Result { @@ -22,6 +22,17 @@ pub fn constructor(cx: CallContext) -> Result { Ok(cx.scope.register(array).into()) } +fn wrapping_index_val(start: Value, cx: &mut CallContext, len: usize) -> Result { + let start = start.to_integer_or_infinity(cx.scope)?; + Ok(if start == f64::NEG_INFINITY { + 0 + } else if start < 0.0 { + cmp::max(len as isize + start as isize, 0) as usize + } else { + cmp::min(start as isize, len as isize) as usize + }) +} + fn join_inner(sc: &mut LocalScope, array: Value, separator: JsString) -> Result { let length = array.length_of_array_like(sc)?; @@ -168,17 +179,12 @@ pub fn some(cx: CallContext) -> Result { Ok(Value::boolean(any_true)) } -pub fn fill(cx: CallContext) -> Result { +pub fn fill(mut cx: CallContext) -> Result { let this = Value::object(cx.this.to_object(cx.scope)?); let len = this.length_of_array_like(cx.scope)?; let value = cx.args.first().unwrap_or_undefined(); - let relative_start = cx.args.get(1).unwrap_or_undefined().to_integer_or_infinity(cx.scope)?; - let k = if relative_start < 0.0 { - cmp::max(len as isize + relative_start as isize, 0) as usize - } else { - cmp::min(relative_start as isize, len as isize) as usize - }; + let k = wrapping_index_val(cx.args.get(1).unwrap_or_undefined(), &mut cx, len)?; let relative_end = cx .args @@ -529,6 +535,11 @@ fn shift_array( shift_by: isize, range: Range, ) -> Result<(), Value> { + if shift_by == 0 { + // No shifting needs to happen (and can't, this short circuit is required by splice) + return Ok(()); + } + let range = range.start as isize..range.end as isize; let new_len = (range.end + shift_by) as usize; @@ -752,3 +763,79 @@ pub fn sort(cx: CallContext) -> Result { Ok(cx.this) } + +pub fn splice(mut cx: CallContext) -> Result { + let this = Value::object(cx.this.to_object(cx.scope)?); + let len = this.length_of_array_like(cx.scope)?; + + let start = wrapping_index_val(cx.args.first().unwrap_or_undefined(), &mut cx, len)?; + let delete_count = match *cx.args { + // 8. If start is not present, then + [] => 0, + // 9. Else if deleteCount is not present, then + [_] => len - start, + // 10. Else, ... + [_, v, ..] => isize::clamp(v.to_integer_or_infinity(cx.scope)? as isize, 0, (len - start) as isize) as usize, + }; + let item_count = cx.args.len().saturating_sub(2); + + // TODO: often the returned array is unused; it may be possible to pass a "return value is used" flag to CallContexts + + require_valid_array_length(cx.scope, delete_count)?; + let mut values = Vec::with_capacity(delete_count); + for k in 0..delete_count { + let from = cx.scope.intern_usize(start + k); + + if let Some(delete_value) = this + .get_property_descriptor(cx.scope, PropertyKey::String(from.into())) + .root_err(cx.scope)? + { + values.push(delete_value); + } + } + + if item_count <= delete_count { + // Since we delete more than we insert, overwrite elements at the delete index + for (i, value) in cx.args.iter().skip(2).enumerate() { + let i = cx.scope.intern_usize(i + start); + this.set_property( + cx.scope, + PropertyKey::String(i.into()), + PropertyValue::static_default(*value), + )?; + } + + // Now shift the rest to the left and update the length + shift_array( + cx.scope, + &this, + len, + -(delete_count as isize - item_count as isize), + start + delete_count..len, + )?; + } else { + let items_to_insert = item_count - delete_count; + require_valid_array_length(cx.scope, len + items_to_insert)?; + + // We can overwrite `delete_count` number of items without shifting to the right + // IOW, we only need to shift by `item_count - delete_count` to the right + shift_array( + cx.scope, + &this, + len, + items_to_insert as isize, + start + delete_count..len, + )?; + + for (i, value) in cx.args.iter().skip(2).enumerate() { + let i = cx.scope.intern_usize(i + start); + this.set_property( + cx.scope, + PropertyKey::String(i.into()), + PropertyValue::static_default(*value), + )?; + } + } + + Ok(cx.scope.register(Array::from_vec(cx.scope, values)).into()) +} diff --git a/crates/dash_vm/src/lib.rs b/crates/dash_vm/src/lib.rs index 347a0e42..5f93e433 100644 --- a/crates/dash_vm/src/lib.rs +++ b/crates/dash_vm/src/lib.rs @@ -496,6 +496,7 @@ impl Vm { (sym::reverse, scope.statics.array_reverse), (sym::shift, scope.statics.array_shift), (sym::sort, scope.statics.array_sort), + (sym::splice, scope.statics.array_splice), (sym::unshift, scope.statics.array_unshift), (sym::slice, scope.statics.array_slice), (sym::lastIndexOf, scope.statics.array_last_index_of), diff --git a/crates/dash_vm/src/statics.rs b/crates/dash_vm/src/statics.rs index 008641e8..92f56daa 100644 --- a/crates/dash_vm/src/statics.rs +++ b/crates/dash_vm/src/statics.rs @@ -2,12 +2,12 @@ use dash_proc_macro::Trace; use crate::gc::{Allocator, ObjectId}; use crate::js_std; +use crate::value::PureBuiltin; use crate::value::error::{AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError}; use crate::value::function::{Function, FunctionKind}; use crate::value::map::Map; use crate::value::regex::RegExp; use crate::value::set::Set; -use crate::value::PureBuiltin; use dash_middle::interner::{self, sym}; use super::value::array::{Array, ArrayIterator}; @@ -181,6 +181,7 @@ pub struct Statics { pub array_reverse: ObjectId, pub array_shift: ObjectId, pub array_sort: ObjectId, + pub array_splice: ObjectId, pub array_unshift: ObjectId, pub array_slice: ObjectId, pub array_last_index_of: ObjectId, @@ -448,6 +449,7 @@ impl Statics { array_sort: function(gc, sym::sort, js_std::array::sort), array_unshift: function(gc, sym::unshift, js_std::array::unshift), array_slice: function(gc, sym::slice, js_std::array::slice), + array_splice: function(gc, sym::splice, js_std::array::splice), array_last_index_of: function(gc, sym::lastIndexOf, js_std::array::last_index_of), array_from: function(gc, sym::from, js_std::array::from), array_is_array: function(gc, sym::isArray, js_std::array::is_array), diff --git a/crates/dash_vm/src/value/array/mod.rs b/crates/dash_vm/src/value/array/mod.rs index 26ca5ba8..a8abcf8b 100644 --- a/crates/dash_vm/src/value/array/mod.rs +++ b/crates/dash_vm/src/value/array/mod.rs @@ -7,11 +7,11 @@ use dash_proc_macro::Trace; use table::ArrayTable; use crate::frame::This; -use crate::gc::trace::Trace; use crate::gc::ObjectId; +use crate::gc::trace::Trace; use crate::localscope::LocalScope; use crate::value::object::PropertyDataDescriptor; -use crate::{delegate, extract, throw, Vm}; +use crate::{Vm, delegate, extract, throw}; use dash_middle::interner::sym; use super::object::{NamedObject, Object, PropertyKey, PropertyValue, PropertyValueKind}; @@ -23,10 +23,19 @@ use super::{Root, Unpack, Unrooted, Value}; pub mod table; pub const MAX_LENGTH: u32 = 4294967295; +pub const MAX_INDEX: u32 = MAX_LENGTH - 1; + +pub fn require_valid_array_length(scope: &mut LocalScope<'_>, len: usize) -> Result<(), Value> { + if len > MAX_LENGTH as usize { + throw!(scope, RangeError, "Invalid array length"); + } + Ok(()) +} #[derive(Debug)] pub enum ArrayInner { // TODO: store Value, also support holes + // TODO: move away from `Vec`? we don't need a `usize` for the length as the max size fits in a u32 Dense(Vec), Table(ArrayTable), } diff --git a/testrunner/src/cmd/run.rs b/testrunner/src/cmd/run.rs index 2c52e1dd..2fa260ff 100644 --- a/testrunner/src/cmd/run.rs +++ b/testrunner/src/cmd/run.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::panic; use std::sync::atomic::AtomicU32; -use std::sync::{atomic, Mutex}; +use std::sync::{Mutex, atomic}; use clap::ArgMatches; +use dash_vm::Vm; use dash_vm::eval::EvalError; use dash_vm::params::VmParams; -use dash_vm::Vm; use once_cell::sync::Lazy; use serde::Deserialize;