From bb276fe54dadff4294a8e2dfe7395e92b28a0ab2 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 8 Feb 2022 16:32:54 +0100 Subject: [PATCH 1/8] Initial implementation of NaN-boxing Improve the API and document unsafe code Co-authored-by: jedel1043 --- boa_engine/src/bigint.rs | 13 +- .../src/builtins/array/array_iterator.rs | 3 +- boa_engine/src/builtins/array/mod.rs | 16 +- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- boa_engine/src/builtins/date/mod.rs | 33 +- boa_engine/src/builtins/date/tests.rs | 2 +- boa_engine/src/builtins/function/arguments.rs | 1 - boa_engine/src/builtins/function/mod.rs | 4 +- boa_engine/src/builtins/intl/mod.rs | 14 +- boa_engine/src/builtins/intl/tests.rs | 20 +- boa_engine/src/builtins/iterable/mod.rs | 4 +- boa_engine/src/builtins/map/mod.rs | 43 +- boa_engine/src/builtins/mod.rs | 4 +- boa_engine/src/builtins/number/mod.rs | 31 +- boa_engine/src/builtins/object/mod.rs | 47 +- boa_engine/src/builtins/proxy/mod.rs | 2 +- boa_engine/src/builtins/reflect/mod.rs | 9 +- boa_engine/src/builtins/set/mod.rs | 4 +- boa_engine/src/builtins/string/mod.rs | 45 +- boa_engine/src/builtins/symbol/mod.rs | 5 +- boa_engine/src/builtins/typed_array/mod.rs | 22 +- boa_engine/src/class.rs | 6 +- boa_engine/src/environments/compile.rs | 2 +- boa_engine/src/lib.rs | 1 + .../object/internal_methods/bound_function.rs | 6 +- .../src/object/internal_methods/proxy.rs | 22 +- boa_engine/src/object/jsobject.rs | 13 +- boa_engine/src/object/operations.rs | 11 +- boa_engine/src/string.rs | 19 +- boa_engine/src/symbol.rs | 13 +- boa_engine/src/value/conversions.rs | 174 ++- boa_engine/src/value/display.rs | 38 +- boa_engine/src/value/equality.rs | 109 +- boa_engine/src/value/hash.rs | 22 +- boa_engine/src/value/mod.rs | 995 ++++++++++++++---- boa_engine/src/value/operations.rs | 296 +++--- boa_engine/src/value/serde_json.rs | 39 +- boa_engine/src/value/type.rs | 20 +- boa_engine/src/vm/code_block.rs | 6 +- boa_engine/src/vm/mod.rs | 36 +- boa_engine/src/vm/tests.rs | 2 +- boa_examples/src/bin/modulehandler.rs | 2 +- 42 files changed, 1406 insertions(+), 750 deletions(-) diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index 61463e51d0c..7edfb8e335a 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -1,11 +1,12 @@ //! This module implements the JavaScript bigint primitive rust type. -use crate::{builtins::Number, Context, JsValue}; +use crate::{builtins::Number, value::PointerType, Context, JsValue}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use num_integer::Integer; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; use std::{ fmt::{self, Display}, + mem::{self, ManuallyDrop}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, rc::Rc, }; @@ -23,6 +24,16 @@ pub struct JsBigInt { inner: Rc, } +unsafe impl PointerType for JsBigInt { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(bigint: ManuallyDrop) -> *mut () { + mem::transmute(bigint) + } +} + // Safety: BigInt does not contain any objects which needs to be traced, // so this is safe. unsafe impl Trace for JsBigInt { diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 2820337b1e9..8eb86230531 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -67,7 +67,8 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut array_iterator = this.as_object().map(JsObject::borrow_mut); + let array_iterator = this.as_object(); + let mut array_iterator = array_iterator.map(JsObject::borrow_mut); let array_iterator = array_iterator .as_mut() .and_then(|obj| obj.as_array_iterator_mut()) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 7404a876cfb..5f01df391bb 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -29,7 +29,7 @@ use crate::{ }, property::{Attribute, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, - value::{IntegerOrInfinity, JsValue}, + value::{IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, JsString, }; use std::cmp::{max, min, Ordering}; @@ -401,9 +401,9 @@ impl Array { // 3. Else, // a. If IsCallable(mapfn) is false, throw a TypeError exception. // b. Let mapping be true. - let mapping = match mapfn { - JsValue::Undefined => None, - JsValue::Object(o) if o.is_callable() => Some(o), + let mapping = match mapfn.variant() { + JsVariant::Undefined => None, + JsVariant::Object(o) if o.is_callable() => Some(o), _ => return context.throw_type_error(format!("{} is not a function", mapfn.type_of())), }; @@ -2334,9 +2334,9 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0) { - JsValue::Object(ref obj) if obj.is_callable() => Some(obj), - JsValue::Undefined => None, + let comparefn = match args.get_or_undefined(0).variant() { + JsVariant::Object(obj) if obj.is_callable() => Some(obj), + JsVariant::Undefined => None, _ => { return context.throw_type_error( "The comparison function must be either a function or undefined", @@ -2367,7 +2367,7 @@ impl Array { let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). let v = cmp - .call(&JsValue::Undefined, &args, context)? + .call(&JsValue::undefined(), &args, context)? .to_number(context)?; // b. If v is NaN, return +0𝔽. // c. Return v. diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index e36ddf7a7f8..e3cb85f568f 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -332,7 +332,7 @@ impl ArrayBuffer { obj.borrow_mut().data = ObjectData::array_buffer(Self { array_buffer_data: Some(block), array_buffer_byte_length: byte_length, - array_buffer_detach_key: JsValue::Undefined, + array_buffer_detach_key: JsValue::undefined(), }); // 5. Return obj. diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index d6d6d43dd6f..90a49164312 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -9,7 +9,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, symbol::WellKnownSymbols, - value::{JsValue, PreferredType}, + value::{JsValue, JsVariant, PreferredType}, Context, JsResult, JsString, }; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; @@ -393,24 +393,25 @@ impl Date { context: &mut Context, ) -> JsResult { let value = &args[0]; - let tv = match this_time_value(value, context) { - Ok(dt) => dt.0, - _ => match value.to_primitive(context, PreferredType::Default)? { - JsValue::String(ref str) => match chrono::DateTime::parse_from_rfc3339(str) { + let tv = if let Ok(dt) = this_time_value(value, context) { + dt.0 + } else { + let tv = value.to_primitive(context, PreferredType::Default)?; + if let JsVariant::String(str) = tv.variant() { + match chrono::DateTime::parse_from_rfc3339(str) { Ok(dt) => Some(dt.naive_utc()), _ => None, - }, - tv => { - let tv = tv.to_number(context)?; - if tv.is_nan() { - None - } else { - let secs = (tv / 1_000f64) as i64; - let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32; - NaiveDateTime::from_timestamp_opt(secs, nano_secs) - } } - }, + } else { + let tv = tv.to_number(context)?; + if tv.is_nan() { + None + } else { + let secs = (tv / 1_000f64) as i64; + let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32; + NaiveDateTime::from_timestamp_opt(secs, nano_secs) + } + } }; let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some()); diff --git a/boa_engine/src/builtins/date/tests.rs b/boa_engine/src/builtins/date/tests.rs index 057fd576fd1..7918cae0852 100644 --- a/boa_engine/src/builtins/date/tests.rs +++ b/boa_engine/src/builtins/date/tests.rs @@ -13,7 +13,7 @@ fn forward_dt_utc(context: &mut Context, src: &str) -> Option { panic!("expected success") }; - if let JsValue::Object(ref date_time) = date_time { + if let Some(date_time) = date_time.as_object() { if let Some(date_time) = date_time.borrow().as_date() { date_time.0 } else { diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index cc28c657a3c..f61f34b10e9 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -196,7 +196,6 @@ impl Arguments { // In the case of duplicate parameter names, the last one is bound as the environment binding. // // The following logic implements the steps 17-19 adjusted for our environment structure. - let mut bindings = FxHashMap::default(); let mut property_index = 0; 'outer: for formal in formals.parameters.iter() { diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index cc4af912b79..1f6cf9ee4cf 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -397,9 +397,7 @@ impl BuiltInFunctionObject { let target_name = target.get("name", context)?; // 9. If Type(targetName) is not String, set targetName to the empty String. - let target_name = target_name - .as_string() - .map_or(JsString::new(""), Clone::clone); + let target_name = target_name.as_string().cloned().unwrap_or_default(); // 10. Perform SetFunctionName(F, targetName, "bound"). set_function_name(&f, &target_name.into(), Some("bound"), context); diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 08c7264edcd..e5ba08c51db 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -143,7 +143,7 @@ impl Intl { // 1. Let ll be ? CanonicalizeLocaleList(locales). let ll = Self::canonicalize_locale_list(args, context)?; // 2. Return CreateArrayFromList(ll). - Ok(JsValue::Object(Array::create_array_from_list( + Ok(JsValue::new(Array::create_array_from_list( ll.into_iter().map(Into::into), context, ))) @@ -548,7 +548,7 @@ fn resolve_locale( // e. Let value be keyLocaleData[0]. // TODO f. Assert: Type(value) is either String or Null. let mut value = match key_locale_data.get(0) { - Some(first_elt) => JsValue::String(first_elt.clone()), + Some(first_elt) => JsValue::from(first_elt.clone()), None => JsValue::null(), }; @@ -569,7 +569,7 @@ fn resolve_locale( // a. If keyLocaleData contains requestedValue, then if key_locale_data.contains(requested_value) { // i. Let value be requestedValue. - value = JsValue::String(JsString::new(requested_value)); + value = JsValue::from(JsString::new(requested_value)); // ii. Let supportedExtensionAddition be the string-concatenation // of "-", key, "-", and value. supported_extension_addition = @@ -578,7 +578,7 @@ fn resolve_locale( // 4. Else if keyLocaleData contains "true", then } else if key_locale_data.contains(&JsString::new("true")) { // a. Let value be "true". - value = JsValue::String(JsString::new("true")); + value = JsValue::from(JsString::new("true")); // b. Let supportedExtensionAddition be the string-concatenation of "-" and key. supported_extension_addition = JsString::concat_array(&["-", key]); } @@ -612,7 +612,7 @@ fn resolve_locale( if let Some(options_val_str) = options_value.as_string() { if options_val_str.is_empty() { // a. Let optionsValue be "true". - options_value = JsValue::String(JsString::new("true")); + options_value = JsValue::from(JsString::new("true")); } } } @@ -695,13 +695,13 @@ pub(crate) fn get_option( // 7. If values is not undefined and values does not contain an element equal to value, // throw a RangeError exception. value = match r#type { - GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()), + GetOptionType::Boolean => JsValue::from(value.to_boolean()), GetOptionType::String => { let string_value = value.to_string(context)?; if !values.is_empty() && !values.contains(&string_value) { return context.throw_range_error("GetOption: values array does not contain value"); } - JsValue::String(string_value) + JsValue::from(string_value) } }; diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 388af54d086..7e4466fbf2b 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -241,7 +241,7 @@ fn get_opt() { let mut context = Context::default(); let values = Vec::::new(); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); let option_type = GetOptionType::String; let get_option_result = get_option( @@ -256,9 +256,9 @@ fn get_opt() { assert_eq!(get_option_result, fallback); let values = Vec::::new(); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); - let locale_value = JsValue::String(JsString::new("en-US")); + let locale_value = JsValue::from(JsString::new("en-US")); options_obj .set("Locale", locale_value.clone(), true, &mut context) .expect("Setting a property should not fail"); @@ -274,10 +274,10 @@ fn get_opt() { .expect("GetOption should not fail on string test"); assert_eq!(get_option_result, locale_value); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); let locale_string = JsString::new("en-US"); - let locale_value = JsValue::String(locale_string.clone()); + let locale_value = JsValue::from(locale_string.clone()); let values = vec![locale_string]; options_obj .set("Locale", locale_value.clone(), true, &mut context) @@ -313,9 +313,9 @@ fn get_opt() { .expect("GetOption should not fail on boolean test"); assert_eq!(get_option_result, boolean_value); - let fallback = JsValue::String(JsString::new("fallback")); + let fallback = JsValue::from(JsString::new("fallback")); let options_obj = JsObject::empty(); - let locale_value = JsValue::String(JsString::new("en-US")); + let locale_value = JsValue::from(JsString::new("en-US")); let other_locale_str = JsString::new("de-DE"); let values = vec![other_locale_str]; options_obj @@ -430,7 +430,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail in date test"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("year", &mut context), Ok(numeric_jsstring.clone()) @@ -452,7 +452,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail in time test"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("hour", &mut context), Ok(numeric_jsstring.clone()) @@ -474,7 +474,7 @@ fn to_date_time_opts() { ) .expect("toDateTimeOptions should not fail when testing required = 'any'"); - let numeric_jsstring = JsValue::String(JsString::new("numeric")); + let numeric_jsstring = JsValue::from(JsString::new("numeric")); assert_eq!( date_time_opts.get("year", &mut context), Ok(numeric_jsstring.clone()) diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 08eebd96ea0..2a1390cefc6 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -144,7 +144,7 @@ impl JsValue { // 1. Let syncMethod be ? GetMethod(obj, @@iterator). let sync_method = self .get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from); + .map_or(Self::undefined(), Self::from); // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). let _sync_iterator_record = self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method)); @@ -154,7 +154,7 @@ impl JsValue { } else { // b. Otherwise, set method to ? GetMethod(obj, @@iterator). self.get_method(WellKnownSymbols::iterator(), context)? - .map_or(Self::Undefined, Self::from) + .map_or(Self::undefined(), Self::from) } }; diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 673d7f9b386..cf06e73209c 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -23,6 +23,7 @@ use crate::{ }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -221,11 +222,11 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow_mut().as_map_mut() { - let key = match key { - JsValue::Rational(r) => { + let key = match key.variant() { + JsVariant::Rational(r) => { // 5. If key is -0𝔽, set key to +0𝔽. if r.is_zero() { - JsValue::Rational(0f64) + JsValue::new(0f64) } else { key.clone() } @@ -326,29 +327,21 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Rational(0f64); - let key = args.get_or_undefined(0); - let key = match key { - JsValue::Rational(r) => { - if r.is_zero() { - JS_ZERO - } else { - key - } - } - _ => key, + let key = match key.variant() { + JsVariant::Rational(r) if r.is_zero() => JsValue::new(0f64), + _ => key.clone(), }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow().as_map_ref() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. // 5. Return undefined. - return Ok(map.get(key).cloned().unwrap_or_default()); + return Ok(map.get(&key).cloned().unwrap_or_default()); } } @@ -398,29 +391,21 @@ impl Map { args: &[JsValue], context: &mut Context, ) -> JsResult { - const JS_ZERO: &JsValue = &JsValue::Rational(0f64); - let key = args.get_or_undefined(0); - let key = match key { - JsValue::Rational(r) => { - if r.is_zero() { - JS_ZERO - } else { - key - } - } - _ => key, + let key = match key.variant() { + JsVariant::Rational(r) if r.is_zero() => JsValue::new(0f64), + _ => key.clone(), }; // 1. Let M be the this value. - if let JsValue::Object(ref object) = this { + if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. if let Some(map) = object.borrow().as_map_ref() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. // 5. Return false. - return Ok(map.contains_key(key).into()); + return Ok(map.contains_key(&key).into()); } } diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 7da47d784f8..7a54e32566f 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -205,7 +205,7 @@ pub trait JsArgs { impl JsArgs for [JsValue] { fn get_or_undefined(&self, index: usize) -> &JsValue { - const UNDEFINED: &JsValue = &JsValue::Undefined; - self.get(index).unwrap_or(UNDEFINED) + static UNDEFINED: JsValue = JsValue::undefined(); + self.get(index).unwrap_or(&UNDEFINED) } } diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index dcbf38cf722..f48122492a7 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -21,7 +21,7 @@ use crate::{ JsObject, ObjectData, }, property::Attribute, - value::{AbstractRelation, IntegerOrInfinity, JsValue}, + value::{AbstractRelation, IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, }; use boa_profiler::Profiler; @@ -220,7 +220,8 @@ impl Number { // 1. Let x be ? thisNumberValue(this value). let this_num = Self::this_number_value(this, context)?; let precision = match args.get(0) { - None | Some(JsValue::Undefined) => None, + None => None, + Some(n) if n.is_undefined() => None, // 2. Let f be ? ToIntegerOrInfinity(fractionDigits). Some(n) => Some(n.to_integer_or_infinity(context)?), }; @@ -992,9 +993,9 @@ impl Number { _ctx: &mut Context, ) -> JsResult { Ok(JsValue::new(if let Some(val) = args.get(0) { - match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => number.is_finite(), + match val.variant() { + JsVariant::Rational(number) => number.is_finite(), + JsVariant::Integer(_) => true, _ => false, } } else { @@ -1041,13 +1042,7 @@ impl Number { args: &[JsValue], _ctx: &mut Context, ) -> JsResult { - Ok(JsValue::new( - if let Some(&JsValue::Rational(number)) = args.get(0) { - number.is_nan() - } else { - false - }, - )) + Ok(JsValue::new(args.get(0).map_or(false, JsValue::is_nan))) } /// `Number.isSafeInteger( number )` @@ -1070,9 +1065,9 @@ impl Number { args: &[JsValue], _ctx: &mut Context, ) -> JsResult { - Ok(JsValue::new(match args.get(0) { - Some(JsValue::Integer(_)) => true, - Some(JsValue::Rational(number)) if Self::is_float_integer(*number) => { + Ok(JsValue::new(match args.get(0).map(JsValue::variant) { + Some(JsVariant::Integer(_)) => true, + Some(JsVariant::Rational(number)) if Self::is_float_integer(number) => { number.abs() <= Self::MAX_SAFE_INTEGER } _ => false, @@ -1087,9 +1082,9 @@ impl Number { /// [spec]: https://tc39.es/ecma262/#sec-isinteger #[inline] pub(crate) fn is_integer(val: &JsValue) -> bool { - match val { - JsValue::Integer(_) => true, - JsValue::Rational(number) => Self::is_float_integer(*number), + match val.variant() { + JsVariant::Integer(_) => true, + JsVariant::Rational(number) => Self::is_float_integer(number), _ => false, } } diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 6bdb4b6a158..5bac89d8250 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -23,7 +23,7 @@ use crate::{ }, property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, - value::JsValue, + value::{JsValue, JsVariant}, Context, JsResult, JsString, }; use boa_profiler::Profiler; @@ -129,8 +129,8 @@ impl Object { let prototype = args.get_or_undefined(0); let properties = args.get_or_undefined(1); - let obj = match prototype { - JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data( + let obj = match prototype.variant() { + JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data( prototype.as_object().cloned(), ObjectData::ordinary(), ), @@ -220,7 +220,7 @@ impl Object { } // 5. Return descriptors. - Ok(descriptors.into()) + Ok(JsValue::new(descriptors)) } /// The abstract operation `FromPropertyDescriptor`. @@ -314,7 +314,7 @@ impl Object { // 2. Return ? obj.[[GetPrototypeOf]](). Ok(obj .__get_prototype_of__(ctx)? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Set the `prototype` of an object. @@ -322,9 +322,13 @@ impl Object { /// [More information][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof - pub fn set_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { + pub fn set_prototype_of( + _: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { if args.len() < 2 { - return ctx.throw_type_error(format!( + return context.throw_type_error(format!( "Object.setPrototypeOf: At least 2 arguments required, but only {} passed", args.len() )); @@ -335,16 +339,19 @@ impl Object { .get(0) .cloned() .unwrap_or_default() - .require_object_coercible(ctx)? + .require_object_coercible(context)? .clone(); - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = args.get_or_undefined(1); + let proto = match proto.variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. - val => { - return ctx - .throw_type_error(format!("expected an object or null, got {}", val.type_of())) + _ => { + return context.throw_type_error(format!( + "expected an object or null, got {}", + proto.type_of() + )) } }; @@ -356,11 +363,11 @@ impl Object { }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = obj.__set_prototype_of__(proto, ctx)?; + let status = obj.__set_prototype_of__(proto, context)?; // 5. If status is false, throw a TypeError exception. if !status { - return ctx.throw_type_error("can't set prototype of this object"); + return context.throw_type_error("can't set prototype of this object"); } // 6. Return O. @@ -406,14 +413,14 @@ impl Object { context: &mut Context, ) -> JsResult { let object = args.get_or_undefined(0); - if let JsValue::Object(object) = object { + if let Some(object) = object.as_object() { let key = args .get(1) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_key(context)?; let desc = args .get(2) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .to_property_descriptor(context)?; object.define_property_or_throw(key, desc, context)?; @@ -440,7 +447,7 @@ impl Object { context: &mut Context, ) -> JsResult { let arg = args.get_or_undefined(0); - if let JsValue::Object(obj) = arg { + if let Some(obj) = arg.as_object() { let props = args.get_or_undefined(1); object_define_properties(obj, props, context)?; Ok(arg.clone()) diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 5f580f74bfd..e709300af62 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -158,7 +158,7 @@ impl Proxy { .data = None; // d. Set F.[[RevocableProxy]] to null. - *revocable_proxy = JsValue::Null; + *revocable_proxy = JsValue::null(); // h. Return undefined. Ok(JsValue::undefined()) diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 0adad88ba67..82cdb08633a 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -16,6 +16,7 @@ use crate::{ object::{JsObject, ObjectInitializer}, property::Attribute, symbol::WellKnownSymbols, + value::JsVariant, Context, JsResult, JsValue, }; use boa_profiler::Profiler; @@ -248,7 +249,7 @@ impl Reflect { .ok_or_else(|| context.construct_type_error("target must be an object"))?; Ok(target .__get_prototype_of__(context)? - .map_or(JsValue::Null, JsValue::new)) + .map_or(JsValue::null(), JsValue::new)) } /// Returns `true` if the object has the property, `false` otherwise. @@ -381,9 +382,9 @@ impl Reflect { .get(0) .and_then(JsValue::as_object) .ok_or_else(|| context.construct_type_error("target must be an object"))?; - let proto = match args.get_or_undefined(1) { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let proto = match args.get_or_undefined(1).variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => return context.throw_type_error("proto must be an object or null"), }; Ok(target.__set_prototype_of__(proto, context)?.into()) diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 1ff23fb706d..53c5501805a 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -200,7 +200,7 @@ impl Set { if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { set.add(if value.as_number().map_or(false, |n| n == -0f64) { - JsValue::Integer(0) + JsValue::new(0) } else { value.clone() }); @@ -350,7 +350,7 @@ impl Set { index += 1; } - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) } /// `Map.prototype.has( key )` diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index ec5cefb313f..1728827314d 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -179,8 +179,12 @@ impl String { let string = match args.get(0) { // 2. Else, // a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value). - Some(JsValue::Symbol(ref sym)) if new_target.is_undefined() => { - return Ok(sym.descriptive_string().into()) + Some(value) if new_target.is_undefined() && value.is_symbol() => { + return Ok(value + .as_symbol() + .expect("Already checked for a symbol") + .descriptive_string() + .into()) } // b. Let s be ? ToString(value). Some(value) => value.to_string(context)?, @@ -395,7 +399,7 @@ impl String { // If codeUnits is empty, the empty String is returned. let s = std::string::String::from_utf16_lossy(elements.as_slice()); - Ok(JsValue::String(JsString::new(s))) + Ok(JsValue::new(JsString::new(s))) } /// `String.prototype.toString ( )` @@ -793,9 +797,11 @@ impl String { let len = string.encode_utf16().count() as i64; // 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position). - let pos = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(0), - position => position.to_integer_or_infinity(context)?, + let pos = args.get_or_undefined(1); + let pos = if pos.is_undefined() { + IntegerOrInfinity::Integer(0) + } else { + pos.to_integer_or_infinity(context)? }; // 8. Let start be the result of clamping pos between 0 and len. @@ -1380,7 +1386,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) @@ -1676,9 +1682,11 @@ impl String { let int_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). - let int_end = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(len), - end => end.to_integer_or_infinity(context)?, + let end = args.get_or_undefined(1); + let int_end = if end.is_undefined() { + IntegerOrInfinity::Integer(len) + } else { + end.to_integer_or_infinity(context)? }; // 6. Let finalStart be the result of clamping intStart between 0 and len. @@ -1733,9 +1741,11 @@ impl String { // 7. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length). // Moved it before to ensure an error throws before returning the empty string on `match int_start` - let int_length = match args.get_or_undefined(1) { - &JsValue::Undefined => IntegerOrInfinity::Integer(size), - val => val.to_integer_or_infinity(context)?, + let length = args.get_or_undefined(1); + let int_length = if length.is_undefined() { + IntegerOrInfinity::Integer(size) + } else { + length.to_integer_or_infinity(context)? }; let int_start = match int_start { @@ -2080,7 +2090,7 @@ impl String { let string = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; + let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) @@ -2324,9 +2334,10 @@ fn split_match(s_str: &str, q: usize, r_str: &str) -> Option { /// [spec]: https://tc39.es/ecma262/#sec-isregexp fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(argument) is not Object, return false. - let argument = match argument { - JsValue::Object(o) => o, - _ => return Ok(false), + let argument = if let Some(o) = argument.as_object() { + o + } else { + return Ok(false); }; is_reg_exp_object(argument, context) diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 3dd58078e45..e2707bbf12e 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -192,6 +192,7 @@ impl Symbol { fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { value .as_symbol() + .cloned() .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) .ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) } @@ -236,7 +237,7 @@ impl Symbol { ) -> JsResult { // 1. Return ? thisSymbolValue(this value). let symbol = Self::this_symbol_value(this, context)?; - Ok(JsValue::Symbol(symbol)) + Ok(JsValue::new(symbol)) } /// `get Symbol.prototype.description` @@ -314,7 +315,7 @@ impl Symbol { // 4. Return undefined. let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { let registry = registry.borrow(); - registry.get_symbol(&sym) + registry.get_symbol(sym) }); Ok(symbol.map(JsValue::from).unwrap_or_default()) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 1b30d9a9580..56eb4880358 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -26,7 +26,7 @@ use crate::{ }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, - value::{IntegerOrInfinity, JsValue}, + value::{IntegerOrInfinity, JsValue, JsVariant}, Context, JsResult, JsString, }; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; @@ -162,7 +162,7 @@ macro_rules! typed_array { // ii. If firstArgument has a [[TypedArrayName]] internal slot, then if first_argument.is_typed_array() { // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). - TypedArray::initialize_from_typed_array(&o, first_argument, context)?; + TypedArray::initialize_from_typed_array(&o, &first_argument, context)?; } else if first_argument.is_array_buffer() { // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then @@ -408,7 +408,9 @@ impl TypedArray { let mapping = match args.get(1) { // 3. If mapfn is undefined, let mapping be false. - None | Some(JsValue::Undefined) => None, + None => None, + Some(v) if v.is_undefined() => None, + // 4. Else, Some(v) => match v.as_object() { // b. Let mapping be true. @@ -1975,9 +1977,9 @@ impl TypedArray { } let source = args.get_or_undefined(0); - match source { + match source.variant() { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsValue::Object(source) if source.is_typed_array() => { + JsVariant::Object(source) if source.is_typed_array() => { // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). Self::set_typed_array_from_typed_array(target, target_offset, source, context)?; } @@ -2564,9 +2566,9 @@ impl TypedArray { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let compare_fn = match args.get(0) { - None | Some(JsValue::Undefined) => None, - Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), + let compare_fn = match args.get(0).map(JsValue::variant) { + None | Some(JsVariant::Undefined) => None, + Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj), _ => { return context .throw_type_error("TypedArray.sort called with non-callable comparefn") @@ -2650,7 +2652,7 @@ impl TypedArray { return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal)); } - if let (JsValue::BigInt(x), JsValue::BigInt(y)) = (x, y) { + if let (Some(x), Some(y)) = (x.as_bigint(), y.as_bigint()) { // 6. If x < y, return -1𝔽. if x < y { return Ok(Ordering::Less); @@ -2902,7 +2904,7 @@ impl TypedArray { .as_typed_array() .map(|o| o.typed_array_name().name().into()) }) - .unwrap_or(JsValue::Undefined)) + .unwrap_or_else(JsValue::undefined)) } /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 94f81452692..ed9986f010a 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -111,7 +111,7 @@ impl ClassConstructor for T { } let class_constructor = context.global_object().clone().get(T::NAME, context)?; - let class_constructor = if let JsValue::Object(ref obj) = class_constructor { + let class_constructor = if let Some(obj) = class_constructor.as_object() { obj } else { return context.throw_type_error(format!( @@ -120,8 +120,8 @@ impl ClassConstructor for T { )); }; let class_prototype = - if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? { - obj.clone() + if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() { + object.clone() } else { return context.throw_type_error(format!( "invalid default prototype for native class `{}`", diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 9bea4ba6f28..2aff17505aa 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -300,7 +300,7 @@ impl Context { self.global_bindings_mut().insert( name_str, PropertyDescriptor::builder() - .value(JsValue::Undefined) + .value(JsValue::undefined()) .writable(true) .enumerable(true) .configurable(true) diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 65d128ec149..cb92337537f 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -64,6 +64,7 @@ clippy::missing_errors_doc, clippy::as_conversions, clippy::let_unit_value, + clippy::transmute_ptr_to_ptr, rustdoc::missing_doc_code_examples )] diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 3949cd93d3b..2cf3309bc8e 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -1,4 +1,4 @@ -use crate::{object::JsObject, Context, JsResult, JsValue}; +use crate::{object::JsObject, value::JsVariant, Context, JsResult, JsValue}; use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; @@ -90,8 +90,8 @@ fn bound_function_exotic_construct( args.extend_from_slice(arguments_list); // 5. If SameValue(F, newTarget) is true, set newTarget to target. - let new_target = match new_target { - JsValue::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(), + let new_target = match new_target.variant() { + JsVariant::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(), _ => new_target.clone(), }; diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 5b5be57dbf6..4db7a601d23 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -2,7 +2,7 @@ use crate::{ builtins::{array, object::Object}, object::{InternalObjectMethods, JsObject, JsPrototype}, property::{PropertyDescriptor, PropertyKey}, - value::Type, + value::{JsVariant, Type}, Context, JsResult, JsValue, }; use rustc_hash::FxHashSet; @@ -97,9 +97,9 @@ pub(crate) fn proxy_exotic_get_prototype_of( let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. - let handler_proto = match &handler_proto { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, + let handler_proto = match handler_proto.variant() { + JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Null => None, _ => return context.throw_type_error("Proxy trap result is neither object nor null"), }; @@ -159,7 +159,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( &handler.into(), &[ target.clone().into(), - val.clone().map_or(JsValue::Null, Into::into), + val.clone().map_or(JsValue::null(), Into::into), ], context, )? @@ -700,7 +700,11 @@ pub(crate) fn proxy_exotic_set( if target_desc.is_accessor_descriptor() { // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. match target_desc.set() { - None | Some(&JsValue::Undefined) => { + None => { + return context + .throw_type_error("Proxy trap set unexpected accessor descriptor"); + } + Some(v) if v.is_undefined() => { return context .throw_type_error("Proxy trap set unexpected accessor descriptor"); } @@ -821,8 +825,8 @@ pub(crate) fn proxy_exotic_own_property_keys( let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); let mut trap_result = Vec::new(); for value in &trap_result_raw { - match value { - JsValue::String(s) => { + match value.variant() { + JsVariant::String(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate string property keys", @@ -830,7 +834,7 @@ pub(crate) fn proxy_exotic_own_property_keys( } trap_result.push(s.clone().into()); } - JsValue::Symbol(s) => { + JsVariant::Symbol(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate symbol property keys", diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index cf75a8b3a04..662bca35599 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -6,7 +6,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap}; use crate::{ object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, - value::PreferredType, + value::{PointerType, PreferredType}, Context, JsResult, JsValue, }; use boa_gc::{self, Finalize, Gc, Trace}; @@ -16,6 +16,7 @@ use std::{ collections::HashMap, error::Error, fmt::{self, Debug, Display}, + mem::{self, ManuallyDrop}, result::Result as StdResult, }; @@ -31,6 +32,16 @@ pub struct JsObject { inner: Gc>, } +unsafe impl PointerType for JsObject { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(object: ManuallyDrop) -> *mut () { + mem::transmute(object) + } +} + impl JsObject { /// Create a new `JsObject` from an internal `Object`. #[inline] diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 91b22c31662..01177612401 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -4,7 +4,7 @@ use crate::{ object::JsObject, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, - value::Type, + value::{JsVariant, Type}, Context, JsResult, JsValue, }; @@ -587,11 +587,14 @@ impl JsObject { // 1. Assert: IsPropertyKey(P) is true. // 2. Let func be ? GetV(V, P). - match &self.__get__(&key.into(), self.clone().into(), context)? { + match self + .__get__(&key.into(), self.clone().into(), context)? + .variant() + { // 3. If func is either undefined or null, return undefined. - JsValue::Undefined | JsValue::Null => Ok(None), + JsVariant::Undefined | JsVariant::Null => Ok(None), // 5. Return func. - JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), + JsVariant::Object(object) if object.is_callable() => Ok(Some(object.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. _ => { context.throw_type_error("value returned for property of object is not a function") diff --git a/boa_engine/src/string.rs b/boa_engine/src/string.rs index 7b4eb837220..e4f7ea326c9 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -1,4 +1,4 @@ -use crate::builtins::string::is_trimmable_whitespace; +use crate::{builtins::string::is_trimmable_whitespace, value::PointerType}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use rustc_hash::{FxHashMap, FxHasher}; use std::{ @@ -8,6 +8,7 @@ use std::{ hash::BuildHasherDefault, hash::{Hash, Hasher}, marker::PhantomData, + mem::ManuallyDrop, ops::Deref, ptr::{copy_nonoverlapping, NonNull}, rc::Rc, @@ -635,6 +636,21 @@ pub struct JsString { _marker: PhantomData>, } +unsafe impl PointerType for JsString { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + let string = Self { + inner: TaggedInner(NonNull::new_unchecked(ptr.cast())), + _marker: PhantomData, + }; + + ManuallyDrop::new(string) + } + + unsafe fn into_void_ptr(string: ManuallyDrop) -> *mut () { + string.inner.0.as_ptr().cast() + } +} + /// This struct uses a technique called tagged pointer to benefit from the fact that newly /// allocated pointers are always word aligned on 64-bits platforms, making it impossible /// to have a LSB equal to 1. More details about this technique on the article of Wikipedia @@ -707,6 +723,7 @@ impl TaggedInner { } } + impl Default for JsString { #[inline] fn default() -> Self { diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index f869c6a0a6b..375ab1e095c 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -15,12 +15,13 @@ //! [spec]: https://tc39.es/ecma262/#sec-symbol-value //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol -use crate::JsString; +use crate::{value::PointerType, JsString}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use std::{ cell::Cell, fmt::{self, Display}, hash::{Hash, Hasher}, + mem::{self, ManuallyDrop}, rc::Rc, }; @@ -253,6 +254,16 @@ pub struct JsSymbol { inner: Rc, } +unsafe impl PointerType for JsSymbol { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop { + ManuallyDrop::new(mem::transmute(ptr)) + } + + unsafe fn into_void_ptr(symbol: ManuallyDrop) -> *mut () { + mem::transmute(symbol) + } +} + impl JsSymbol { /// Create a new symbol. #[inline] diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index 771acbbf8c2..bf9bf06e7c8 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -1,4 +1,6 @@ -use super::{Display, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; +use crate::JsString; + +use super::{Display, JsValue}; impl From<&Self> for JsValue { #[inline] @@ -7,90 +9,31 @@ impl From<&Self> for JsValue { } } -impl From for JsValue -where - T: Into, -{ - #[inline] - fn from(value: T) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - - Self::String(value.into()) - } -} - -impl From for JsValue { - #[inline] - fn from(value: char) -> Self { - Self::new(value.to_string()) - } -} - -impl From for JsValue { - #[inline] - fn from(value: JsSymbol) -> Self { - Self::Symbol(value) - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct TryFromCharError; - -impl Display for TryFromCharError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Could not convert value to a char type") - } -} - -impl From for JsValue { - #[allow(clippy::float_cmp)] - #[inline] - fn from(value: f32) -> Self { - // if value as i32 as f64 == value { - // Self::Integer(value as i32) - // } else { - Self::Rational(value.into()) - // } - } -} - -impl From for JsValue { - #[allow(clippy::float_cmp)] - #[inline] - fn from(value: f64) -> Self { - // if value as i32 as f64 == value { - // Self::Integer(value as i32) - // } else { - Self::Rational(value) - // } - } -} - impl From for JsValue { #[inline] fn from(value: u8) -> Self { - Self::Integer(value.into()) + Self::from(value as i32) } } impl From for JsValue { #[inline] fn from(value: i8) -> Self { - Self::Integer(value.into()) + Self::from(value as i32) } } impl From for JsValue { #[inline] fn from(value: u16) -> Self { - Self::Integer(value.into()) + Self::from(value as i32) } } impl From for JsValue { #[inline] fn from(value: i16) -> Self { - Self::Integer(value.into()) + Self::from(value as i32) } } @@ -98,34 +41,31 @@ impl From for JsValue { #[inline] fn from(value: u32) -> Self { if let Ok(integer) = i32::try_from(value) { - Self::Integer(integer) + Self::from(integer) } else { - Self::Rational(value.into()) + Self::from(value as f64) } } } -impl From for JsValue { - #[inline] - fn from(value: i32) -> Self { - Self::Integer(value) - } -} - -impl From for JsValue { +impl From for JsValue { #[inline] - fn from(value: JsBigInt) -> Self { - Self::BigInt(value) + fn from(value: usize) -> Self { + if let Ok(value) = i32::try_from(value) { + Self::from(value) + } else { + Self::from(value as f64) + } } } -impl From for JsValue { +impl From for JsValue { #[inline] - fn from(value: usize) -> Self { + fn from(value: isize) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::from(value) } else { - Self::Rational(value as f64) + Self::from(value as f64) } } } @@ -134,9 +74,9 @@ impl From for JsValue { #[inline] fn from(value: u64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::from(value) } else { - Self::Rational(value as f64) + Self::from(value as f64) } } } @@ -145,32 +85,76 @@ impl From for JsValue { #[inline] fn from(value: i64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::Integer(value) + Self::from(value) } else { - Self::Rational(value as f64) + Self::from(value as f64) } } } -impl From for JsValue { +impl From for JsValue { + #[allow(clippy::float_cmp)] + #[inline] + fn from(value: f32) -> Self { + // if value as i32 as f64 == value { + // Self::Integer(value as i32) + // } else { + Self::from(value as f64) + // } + } +} + +impl From for JsValue { + #[inline] + fn from(value: char) -> Self { + Self::from(value.to_string()) + } +} + +impl From<&str> for JsValue { + #[inline] + fn from(string: &str) -> Self { + Self::from(JsString::new(string)) + } +} + +impl From> for JsValue { #[inline] - fn from(value: bool) -> Self { - Self::Boolean(value) + fn from(string: Box) -> Self { + Self::from(JsString::new(string)) } } -impl From for JsValue { +impl From<&String> for JsValue { #[inline] - fn from(object: JsObject) -> Self { - let _timer = Profiler::global().start_event("From", "value"); - Self::Object(object) + fn from(string: &String) -> Self { + Self::from(JsString::new(string.as_str())) } } -impl From<()> for JsValue { +impl From for JsValue { + #[inline] + fn from(string: String) -> Self { + Self::from(JsString::new(string)) + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct TryFromObjectError; + +impl Display for TryFromObjectError { #[inline] - fn from(_: ()) -> Self { - Self::null() + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert value to an Object type") + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct TryFromCharError; + +impl Display for TryFromCharError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Could not convert value to a char type") } } @@ -186,3 +170,5 @@ where self.map_or_else(JsValue::undefined, Into::into) } } + + diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 1cee7522626..abb31537c94 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -1,6 +1,6 @@ use crate::{object::ObjectKind, property::PropertyDescriptor}; -use super::{fmt, Display, HashSet, JsValue, PropertyKey}; +use super::{fmt, Display, HashSet, JsValue, JsVariant, PropertyKey}; /// This object is used for displaying a `Value`. #[derive(Debug, Clone, Copy)] @@ -55,7 +55,7 @@ macro_rules! print_obj_value { vec![format!( "{:>width$}: {}", "__proto__", - JsValue::Null.display(), + JsValue::null().display(), width = $indent, )] } @@ -96,9 +96,9 @@ macro_rules! print_obj_value { } pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { - match x { + match x.variant() { // We don't want to print private (compiler) or prototype properties - JsValue::Object(ref v) => { + JsVariant::Object(v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing match v.borrow().kind() { @@ -196,7 +196,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children _ => display_obj(x, print_internals), } } - JsValue::Symbol(ref symbol) => symbol.to_string(), + JsVariant::Symbol(ref symbol) => symbol.to_string(), _ => x.display().to_string(), } } @@ -216,7 +216,7 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { indent: usize, print_internals: bool, ) -> String { - if let JsValue::Object(ref v) = *data { + if let Some(v) = data.as_object() { // The in-memory address of the current object let addr = address_of(v.as_ref()); @@ -254,20 +254,20 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { // in-memory address in this set let mut encounters = HashSet::new(); - if let JsValue::Object(object) = v { + if let Some(object) = v.as_object() { if object.borrow().is_error() { let name = v .get_property("name") .as_ref() .and_then(PropertyDescriptor::value) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .display() .to_string(); let message = v .get_property("message") .as_ref() .and_then(PropertyDescriptor::value) - .unwrap_or(&JsValue::Undefined) + .unwrap_or(&JsValue::undefined()) .display() .to_string(); return format!("{name}: {message}"); @@ -279,21 +279,21 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String { impl Display for ValueDisplay<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.value { - JsValue::Null => write!(f, "null"), - JsValue::Undefined => write!(f, "undefined"), - JsValue::Boolean(v) => write!(f, "{v}"), - JsValue::Symbol(ref symbol) => match symbol.description() { + match self.value.variant() { + JsVariant::Null => write!(f, "null"), + JsVariant::Undefined => write!(f, "undefined"), + JsVariant::Boolean(v) => write!(f, "{v}"), + JsVariant::Symbol(symbol) => match symbol.description() { Some(description) => write!(f, "Symbol({description})"), None => write!(f, "Symbol()"), }, - JsValue::String(ref v) => write!(f, "\"{v}\""), - JsValue::Rational(v) => format_rational(*v, f), - JsValue::Object(_) => { + JsVariant::String(v) => write!(f, "\"{v}\""), + JsVariant::Rational(v) => format_rational(v, f), + JsVariant::Object(_) => { write!(f, "{}", log_string_from(self.value, self.internals, true)) } - JsValue::Integer(v) => write!(f, "{v}"), - JsValue::BigInt(ref num) => write!(f, "{num}n"), + JsVariant::Integer(v) => write!(f, "{v}"), + JsVariant::BigInt(num) => write!(f, "{num}n"), } } } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index c7d4d65235c..e8e5a236f21 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -1,4 +1,4 @@ -use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; +use super::{JsBigInt, JsObject, JsResult, JsValue, JsVariant, PreferredType}; use crate::{builtins::Number, Context}; impl JsValue { @@ -12,20 +12,20 @@ impl JsValue { return false; } - match (self, other) { + match (self.variant(), other.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::equal(x, y), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::equal(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::equal(x, f64::from(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::equal(f64::from(x), y), + (JsVariant::Integer(x), JsVariant::Integer(y)) => x == y, //Null has to be handled specially because "typeof null" returns object and if we managed //this without a special case we would compare self and other as if they were actually //objects which unfortunately fails //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator - (Self::Null, Self::Null) => true, + (JsVariant::Null, JsVariant::Null) => true, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(self, other), @@ -44,17 +44,22 @@ impl JsValue { return Ok(self.strict_equals(other)); } - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // 2. If x is null and y is undefined, return true. // 3. If x is undefined and y is null, return true. - (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true, + (JsVariant::Null, JsVariant::Undefined) | (JsVariant::Undefined, JsVariant::Null) => { + true + } // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. // // https://github.com/rust-lang/rust/issues/54883 - (Self::Integer(_) | Self::Rational(_), Self::String(_) | Self::Boolean(_)) - | (Self::String(_), Self::Integer(_) | Self::Rational(_)) => { + ( + JsVariant::Integer(_) | JsVariant::Rational(_), + JsVariant::String(_) | JsVariant::Boolean(_), + ) + | (JsVariant::String(_), JsVariant::Integer(_) | JsVariant::Rational(_)) => { let x = self.to_number(context)?; let y = other.to_number(context)?; Number::equal(x, y) @@ -64,32 +69,32 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) { + (JsVariant::BigInt(a), JsVariant::String(b)) => match JsBigInt::from_string(b) { Some(ref b) => a == b, None => false, }, // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) { + (JsVariant::String(a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) { Some(ref a) => a == b, None => false, }, // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. - (Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context), + (JsVariant::Boolean(x), _) => return other.equals(&Self::new(i32::from(x)), context), // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). - (_, Self::Boolean(y)) => return self.equals(&Self::new(i32::from(*y)), context), + (_, JsVariant::Boolean(y)) => return self.equals(&Self::new(i32::from(y)), context), // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result // of the comparison x == ? ToPrimitive(y). ( - Self::Object(_), - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), + JsVariant::Object(_), + JsVariant::String(_) + | JsVariant::Rational(_) + | JsVariant::Integer(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), ) => { let primitive = self.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -100,12 +105,12 @@ impl JsValue { // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // of the comparison ? ToPrimitive(x) == y. ( - Self::String(_) - | Self::Rational(_) - | Self::Integer(_) - | Self::BigInt(_) - | Self::Symbol(_), - Self::Object(_), + JsVariant::String(_) + | JsVariant::Rational(_) + | JsVariant::Integer(_) + | JsVariant::BigInt(_) + | JsVariant::Symbol(_), + JsVariant::Object(_), ) => { let primitive = other.to_primitive(context, PreferredType::Default)?; return Ok(primitive @@ -116,10 +121,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (Self::BigInt(ref a), Self::Rational(ref b)) => a == b, - (Self::Rational(ref a), Self::BigInt(ref b)) => a == b, - (Self::BigInt(ref a), Self::Integer(ref b)) => a == b, - (Self::Integer(ref a), Self::BigInt(ref b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Rational(ref b)) => a == b, + (JsVariant::Rational(ref a), JsVariant::BigInt(b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Integer(ref b)) => a == b, + (JsVariant::Integer(ref a), JsVariant::BigInt(b)) => a == b, // 13. Return false. _ => false, @@ -139,14 +144,14 @@ impl JsValue { return false; } - match (x, y) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y), - (Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y), - (Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)), - (Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y), - (Self::Integer(x), Self::Integer(y)) => x == y, + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value(x, y), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::same_value(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::same_value(x, f64::from(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::same_value(f64::from(x), y), + (JsVariant::Integer(x), JsVariant::Integer(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -167,19 +172,19 @@ impl JsValue { return false; } - match (x, y) { + match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value_zero(x, y), - (JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value_zero(*x, *y), - (JsValue::Rational(x), JsValue::Integer(y)) => { - Number::same_value_zero(*x, f64::from(*y)) + (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::same_value_zero(x, y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Number::same_value_zero(x, f64::from(y)) } - (JsValue::Integer(x), JsValue::Rational(y)) => { - Number::same_value_zero(f64::from(*x), *y) + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Number::same_value_zero(f64::from(x), y) } - (JsValue::Integer(x), JsValue::Integer(y)) => x == y, + (JsVariant::Integer(x), JsVariant::Integer(y)) => x == y, // 3. Return ! SameValueNonNumeric(x, y). (_, _) => Self::same_value_non_numeric(x, y), @@ -188,12 +193,12 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); - match (x, y) { - (Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true, - (Self::String(ref x), Self::String(ref y)) => x == y, - (Self::Boolean(x), Self::Boolean(y)) => x == y, - (Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y), - (Self::Symbol(ref x), Self::Symbol(ref y)) => x == y, + match (x.variant(), y.variant()) { + (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => true, + (JsVariant::String(x), JsVariant::String(y)) => x == y, + (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, + (JsVariant::Object(x), JsVariant::Object(y)) => JsObject::equals(x, y), + (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, _ => false, } } diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 4498eca035a..50b2e67eb9a 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{JsValue, JsVariant}; use crate::builtins::Number; use std::hash::{Hash, Hasher}; @@ -37,16 +37,16 @@ impl Hash for RationalHashable { impl Hash for JsValue { fn hash(&self, state: &mut H) { - match self { - Self::Undefined => UndefinedHashable.hash(state), - Self::Null => NullHashable.hash(state), - Self::String(ref string) => string.hash(state), - Self::Boolean(boolean) => boolean.hash(state), - Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state), - Self::BigInt(ref bigint) => bigint.hash(state), - Self::Rational(rational) => RationalHashable(*rational).hash(state), - Self::Symbol(ref symbol) => Hash::hash(symbol, state), - Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), + match self.variant() { + JsVariant::Undefined => UndefinedHashable.hash(state), + JsVariant::Null => NullHashable.hash(state), + JsVariant::String(ref string) => string.hash(state), + JsVariant::Boolean(boolean) => boolean.hash(state), + JsVariant::Integer(integer) => RationalHashable(f64::from(integer)).hash(state), + JsVariant::BigInt(ref bigint) => bigint.hash(state), + JsVariant::Rational(rational) => RationalHashable(rational).hash(state), + JsVariant::Symbol(ref symbol) => Hash::hash(symbol, state), + JsVariant::Object(ref object) => std::ptr::hash(object.as_ref(), state), } } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 509249529f9..4347c14adc0 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -1,3 +1,4 @@ +#![warn(unsafe_op_in_unsafe_fn)] //! This module implements the JavaScript Value. //! //! Javascript values, utility methods and conversion between Javascript values and Rust values. @@ -22,8 +23,10 @@ use num_integer::Integer; use num_traits::Zero; use once_cell::sync::Lazy; use std::{ + cell::Cell, collections::HashSet, fmt::{self, Display}, + mem::ManuallyDrop, ops::Sub, str::FromStr, }; @@ -55,29 +58,70 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -/// A Javascript value -#[derive(Trace, Finalize, Debug, Clone)] -pub enum JsValue { - /// `null` - A null value, for when a value doesn't exist. - Null, - /// `undefined` - An undefined value, for when a field or index doesn't exist. - Undefined, - /// `boolean` - A `true` / `false` value, for if a certain criteria is met. - Boolean(bool), - /// `String` - A UTF-8 string, such as `"Hello, world"`. - String(JsString), - /// `Number` - A 64-bit floating point number, such as `3.1415` - Rational(f64), - /// `Number` - A 32-bit integer, such as `42`. - Integer(i32), - /// `BigInt` - holds any arbitrary large signed integer. - BigInt(JsBigInt), - /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. - Object(JsObject), - /// `Symbol` - A Symbol Primitive type. - Symbol(JsSymbol), +const SIGN_BIT: u64 = 0x8000000000000000; +const EXPONENT: u64 = 0x7FF0000000000000; +// const MANTISA: u64 = 0x0008000000000000; +const SIGNAL_BIT: u64 = 0x0008000000000000; +const QNAN: u64 = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000 + +pub const CANONICALIZED_NAN: u64 = QNAN; +// const PAYLOAD: u64 = 0x00007FFFFFFFFFFF; +// const TYPE: u64 = !PAYLOAD; + +pub const TAG_MASK: u64 = 0xFFFF000000000000; + +pub const DOUBLE_TYPE: u64 = QNAN; +pub const INTEGER_TYPE: u64 = QNAN | (0b001 << 48); +pub const BOOLEAN_TYPE: u64 = QNAN | (0b010 << 48); +pub const UNDEFINED_TYPE: u64 = QNAN | (0b011 << 48); +pub const NULL_TYPE: u64 = QNAN | (0b100 << 48); + +pub const RESERVED1_TYPE: u64 = QNAN | (0b101 << 48); +pub const RESERVED2_TYPE: u64 = QNAN | (0b110 << 48); +pub const RESERVED3_TYPE: u64 = QNAN | (0b111 << 48); + +pub const POINTER_TYPE: u64 = SIGN_BIT | QNAN; +pub const OBJECT_TYPE: u64 = POINTER_TYPE | (0b001 << 48); +pub const STRING_TYPE: u64 = POINTER_TYPE | (0b010 << 48); +pub const SYMBOL_TYPE: u64 = POINTER_TYPE | (0b011 << 48); +pub const BIGINT_TYPE: u64 = POINTER_TYPE | (0b100 << 48); + +pub const RESERVED4_TYPE: u64 = POINTER_TYPE | (0b101 << 48); +pub const RESERVED5_TYPE: u64 = POINTER_TYPE | (0b110 << 48); +pub const RESERVED6_TYPE: u64 = POINTER_TYPE | (0b111 << 48); + +pub const MASK_INT_PAYLOAD: u64 = 0x00000000FFFFFFFF; +pub const MASK_POINTER_PAYLOAD: u64 = 0x0000FFFFFFFFFFFF; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum ValueTag { + Double = (DOUBLE_TYPE >> 48) as _, + Integer = (INTEGER_TYPE >> 48) as _, + Boolean = (BOOLEAN_TYPE >> 48) as _, + Undefined = (UNDEFINED_TYPE >> 48) as _, + Null = (NULL_TYPE >> 48) as _, + Object = (OBJECT_TYPE >> 48) as _, + String = (STRING_TYPE >> 48) as _, + Symbol = (SYMBOL_TYPE >> 48) as _, + BigInt = (BIGINT_TYPE >> 48) as _, + // Reserved1 = RESERVED1_TYPE, + // Reserved2 = RESERVED2_TYPE, + // Reserved3 = RESERVED3_TYPE, + // Reserved4 = RESERVED4_TYPE, + // Reserved5 = RESERVED5_TYPE, + // Reserved6 = RESERVED6_TYPE, +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct JsValue { + value: Cell, } +// TODO: Remove this !!!! +unsafe impl Sync for JsValue {} + impl JsValue { /// Create a new [`JsValue`]. #[inline] @@ -88,184 +132,735 @@ impl JsValue { value.into() } + fn tag(&self) -> ValueTag { + if self.is_rational() { + return ValueTag::Double; + } + unsafe { std::mem::transmute(((self.value.get() & TAG_MASK) >> 48) as u16) } + } + + /// Creates a new number with `NaN` value. + #[inline] + pub const fn nan() -> Self { + Self { + value: Cell::new(CANONICALIZED_NAN), + } + } + + pub fn is_nan(&self) -> bool { + self.value.get() == CANONICALIZED_NAN + } + /// Creates a new `undefined` value. #[inline] - pub fn undefined() -> Self { - Self::Undefined + pub const fn undefined() -> Self { + Self { + value: Cell::new(UNDEFINED_TYPE), + } + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + self.value.get() == UNDEFINED_TYPE } /// Creates a new `null` value. #[inline] - pub fn null() -> Self { - Self::Null + pub const fn null() -> Self { + Self { + value: Cell::new(NULL_TYPE), + } } - /// Creates a new number with `NaN` value. + /// Returns true if the value is null. #[inline] - pub fn nan() -> Self { - Self::Rational(f64::NAN) + pub fn is_null(&self) -> bool { + self.value.get() == NULL_TYPE } - /// Creates a new number with `Infinity` value. + /// Returns true if the value is null or undefined. #[inline] - pub fn positive_infinity() -> Self { - Self::Rational(f64::INFINITY) + pub fn is_null_or_undefined(&self) -> bool { + self.is_null() || self.is_undefined() } - /// Creates a new number with `-Infinity` value. + pub fn is_rational(&self) -> bool { + (self.value.get() & !SIGN_BIT) <= QNAN + } + + fn as_rational_unchecked(&self) -> f64 { + f64::from_bits(self.value.get()) + } + + pub fn as_rational(&self) -> Option { + if self.is_rational() { + return Some(self.as_rational_unchecked()); + } + + None + } + + pub fn is_i32(&self) -> bool { + self.value.get() & TAG_MASK == INTEGER_TYPE + } + + pub fn as_i32_uncheched(&self) -> i32 { + (self.value.get() & MASK_INT_PAYLOAD) as u32 as i32 + } + + pub fn as_i32(&self) -> Option { + if self.is_i32() { + return Some(self.as_i32_uncheched()); + } + + None + } + + /// Returns true if the value is a boolean. #[inline] - pub fn negative_infinity() -> Self { - Self::Rational(f64::NEG_INFINITY) + pub fn is_boolean(&self) -> bool { + self.value.get() & TAG_MASK == BOOLEAN_TYPE + } + + pub fn as_boolean_uncheched(&self) -> bool { + (self.value.get() & 0xFF) != 0 + } + + #[inline] + pub fn as_boolean(&self) -> Option { + if self.is_boolean() { + return Some(self.as_boolean_uncheched()); + } + + None + } + + pub fn as_pointer(&self) -> *mut () { + (self.value.get() & MASK_POINTER_PAYLOAD) as *mut () } /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { - matches!(self, Self::Object(_)) + self.value.get() & TAG_MASK == OBJECT_TYPE + } + + /// Returns a reference to the boxed [`JsObject`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsObject`] is undefined behaviour. + pub unsafe fn as_object_unchecked(&self) -> &JsObject { + // SAFETY: The safety contract must be upheld by the caller + let object = unsafe { + JsObject::from_void_ptr(self.as_pointer()); + }; + + // SAFETY: Assuming the above is safe, this is safe because + // we currently are manipulating a [`ManuallyDrop`] that is + // manually dropped until this [`JsValue`] is dropped, + // which implies that the lifetime of `&object` can be extended + // to the lifetime of `&self`. + unsafe { std::mem::transmute(&object) } } - #[inline] pub fn as_object(&self) -> Option<&JsObject> { - match *self { - Self::Object(ref o) => Some(o), - _ => None, + if self.is_object() { + return unsafe { Some(self.as_object_unchecked()) }; } + + None } - /// It determines if the value is a callable function with a `[[Call]]` internal method. + /// Returns true if the value is a string. + #[inline] + pub fn is_string(&self) -> bool { + self.value.get() & TAG_MASK == STRING_TYPE + } + + /// Returns a reference to the boxed [`JsString`] without checking + /// if the tag of `self` is valid. /// - /// More information: - /// - [ECMAScript reference][spec] + /// # Safety /// - /// [spec]: https://tc39.es/ecma262/#sec-iscallable + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsString`] is undefined behaviour. + pub unsafe fn as_string_unchecked(&self) -> &JsString { + // SAFETY: The safety contract must be upheld by the caller + let string = unsafe { + JsString::from_void_ptr(self.as_pointer()); + }; + + // SAFETY: Assuming the above is safe, this is safe because + // we currently are manipulating a [`ManuallyDrop`] that is + // manually dropped until this [`JsValue`] is dropped, + // which implies that the lifetime of `&string` can be extended + // to the lifetime of `&self`. + unsafe { std::mem::transmute(&string) } + } + + /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn is_callable(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_callable()) + pub fn as_string(&self) -> Option<&JsString> { + if self.is_string() { + return unsafe { Some(self.as_string_unchecked()) }; + } + + None + } + + pub fn is_symbol(&self) -> bool { + self.value.get() & TAG_MASK == SYMBOL_TYPE + } + + /// Returns a reference to the boxed [`JsSymbol`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsSymbol`] is undefined behaviour. + pub unsafe fn as_symbol_unchecked(&self) -> &JsSymbol { + // SAFETY: The safety contract must be upheld by the caller + let symbol = unsafe { JsSymbol::from_void_ptr(self.as_pointer()) }; + + // SAFETY: Assuming the above is safe, this is safe because + // we currently are manipulating a [`ManuallyDrop`] that is + // manually dropped until this [`JsValue`] is dropped, + // which implies that the lifetime of `&symbol` can be extended + // to the lifetime of `&self`. + unsafe { std::mem::transmute(&symbol) } + } + + pub fn as_symbol(&self) -> Option<&JsSymbol> { + if self.is_symbol() { + return unsafe { Some(self.as_symbol_unchecked()) }; + } + + None } + /// Returns true if the value is a bigint. #[inline] - pub fn as_callable(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_callable()) + pub fn is_bigint(&self) -> bool { + self.value.get() & TAG_MASK == BIGINT_TYPE } - /// Returns true if the value is a constructor object + /// Returns a reference to the boxed [`JsBigInt`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsBigInt`] is undefined behaviour. #[inline] - pub fn is_constructor(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_constructor()) + pub unsafe fn as_bigint_unchecked(&self) -> &JsBigInt { + // SAFETY: The safety contract must be upheld by the caller + let bigint = unsafe { + JsBigInt::from_void_ptr(self.as_pointer()); + }; + + // SAFETY: Assuming the above is safe, this is safe because + // we currently are manipulating a [`ManuallyDrop`] that is + // manually dropped until this [`JsValue`] is dropped, + // which implies that the lifetime of `&bigint` can be extended + // to the lifetime of `&self`. + unsafe { std::mem::transmute(&bigint) } } + pub fn as_bigint(&self) -> Option<&JsBigInt> { + if self.is_bigint() { + return unsafe { Some(self.as_bigint_unchecked()) }; + } + + None + } + + pub fn variant(&self) -> JsVariant<'_> { + unsafe { + match self.tag() { + ValueTag::Null => JsVariant::Null, + ValueTag::Undefined => JsVariant::Undefined, + ValueTag::Integer => JsVariant::Integer(self.as_i32_uncheched()), + ValueTag::Double => JsVariant::Rational(self.as_rational_unchecked()), + ValueTag::Boolean => JsVariant::Boolean(self.as_boolean_uncheched()), + ValueTag::Object => JsVariant::Object(self.as_object_unchecked()), + ValueTag::String => JsVariant::String(self.as_string_unchecked()), + ValueTag::Symbol => JsVariant::Symbol(self.as_symbol_unchecked()), + ValueTag::BigInt => JsVariant::BigInt(self.as_bigint_unchecked()), + } + } + } +} + +#[derive(Debug, Clone)] +pub enum JsVariant<'a> { + Null, + Undefined, + Rational(f64), + Integer(i32), + Boolean(bool), + String(&'a JsString), + Symbol(&'a JsSymbol), + BigInt(&'a JsBigInt), + Object(&'a JsObject), +} + +impl From for JsValue { #[inline] - pub fn as_constructor(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_constructor()) + fn from(value: bool) -> Self { + let value = Self { + value: Cell::new(BOOLEAN_TYPE | u64::from(value)), + }; + debug_assert!(value.is_boolean()); + debug_assert_eq!(value.tag(), ValueTag::Boolean); + value } +} - /// Returns true if the value is a symbol. +impl From for JsValue { #[inline] - pub fn is_symbol(&self) -> bool { - matches!(self, Self::Symbol(_)) + fn from(value: i32) -> Self { + let value = Self { + value: Cell::new(INTEGER_TYPE | u64::from(value as u32)), + }; + debug_assert!(value.is_integer()); + debug_assert_eq!(value.tag(), ValueTag::Integer); + value } +} - pub fn as_symbol(&self) -> Option { - match self { - Self::Symbol(symbol) => Some(symbol.clone()), - _ => None, +impl From for JsValue { + #[inline] + fn from(value: f64) -> Self { + if value.is_nan() { + return Self { + value: Cell::new(CANONICALIZED_NAN), + }; } + + let value = Self { + value: Cell::new(value.to_bits()), + }; + debug_assert!(value.is_rational()); + debug_assert_eq!(value.tag(), ValueTag::Double); + value } +} - /// Returns true if the value is undefined. +impl From for JsValue { #[inline] - pub fn is_undefined(&self) -> bool { - matches!(self, Self::Undefined) + fn from(string: JsString) -> Self { + let string = ManuallyDrop::new(string); + let pointer = unsafe { JsString::into_void_ptr(string) } as u64; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(STRING_TYPE | pointer), + }; + debug_assert!(value.is_string()); + debug_assert_eq!(value.tag(), ValueTag::String); + value } +} - /// Returns true if the value is null. +impl From for JsValue { #[inline] - pub fn is_null(&self) -> bool { - matches!(self, Self::Null) + fn from(object: JsObject) -> Self { + let object = ManuallyDrop::new(object); + let pointer = unsafe { JsObject::into_void_ptr(object) } as u64; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); + let value = Self { + value: Cell::new(OBJECT_TYPE | pointer), + }; + debug_assert!(value.is_object()); + debug_assert_eq!(value.tag(), ValueTag::Object); + value } +} - /// Returns true if the value is null or undefined. +impl From for JsValue { #[inline] - pub fn is_null_or_undefined(&self) -> bool { - matches!(self, Self::Null | Self::Undefined) + fn from(symbol: JsSymbol) -> Self { + let symbol = ManuallyDrop::new(symbol); + let pointer = unsafe { JsSymbol::into_void_ptr(symbol) as u64 }; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(SYMBOL_TYPE | pointer), + }; + debug_assert!(value.is_symbol()); + debug_assert_eq!(value.tag(), ValueTag::Symbol); + value } +} - /// Returns true if the value is a 64-bit floating-point number. +impl From for JsValue { #[inline] - pub fn is_double(&self) -> bool { - matches!(self, Self::Rational(_)) + fn from(bigint: JsBigInt) -> Self { + let bigint = ManuallyDrop::new(bigint); + let pointer = unsafe { JsBigInt::into_void_ptr(bigint) as u64 }; + debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let value = Self { + value: Cell::new(BIGINT_TYPE | pointer), + }; + debug_assert!(value.is_bigint()); + debug_assert_eq!(value.tag(), ValueTag::BigInt); + value } +} - /// Returns true if the value is integer. +/// This abstracts over every pointer type boxed inside `NaN` values. +/// +/// # Safety +/// +/// Non-exhaustive list of situations that could cause undefined behaviour: +/// - Returning an invalid `*mut ()`. +/// - Returning a `ManuallyDrop` that doesn't correspond with the provided +/// `ptr`. +/// - Dropping `ty` before returning its pointer. +pub(crate) unsafe trait PointerType { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop; + + unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); +} + +impl Drop for JsValue { #[inline] - #[allow(clippy::float_cmp)] - pub fn is_integer(&self) -> bool { - // If it can fit in a i32 and the trucated version is - // equal to the original then it is an integer. - let is_racional_intiger = |n: f64| n == f64::from(n as i32); + fn drop(&mut self) { + unsafe { + match self.tag() { + ValueTag::Object => { + ManuallyDrop::into_inner(JsObject::from_void_ptr(self.as_pointer())); + } + ValueTag::String => { + ManuallyDrop::into_inner(JsString::from_void_ptr(self.as_pointer())); + } + ValueTag::Symbol => { + ManuallyDrop::into_inner(JsSymbol::from_void_ptr(self.as_pointer())); + } + ValueTag::BigInt => { + ManuallyDrop::into_inner(JsBigInt::from_void_ptr(self.as_pointer())); + } + _ => {} + } + } + } +} - match *self { - Self::Integer(_) => true, - Self::Rational(n) if is_racional_intiger(n) => true, - _ => false, +impl Clone for JsValue { + #[inline] + fn clone(&self) -> Self { + unsafe { + match self.tag() { + ValueTag::Object => Self::new(self.as_object_unchecked().clone()), + ValueTag::String => Self::new(self.as_string_unchecked().clone()), + ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), + ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), + _ => Self { + value: Cell::new(self.value.get()), + }, + } } } +} - /// Returns true if the value is a number. +impl Finalize for JsValue {} + +unsafe impl Trace for JsValue { #[inline] - pub fn is_number(&self) -> bool { - matches!(self, Self::Rational(_) | Self::Integer(_)) + unsafe fn trace(&self) { + if let Some(o) = self.as_object() { + // SAFETY: `self.as_object()` must always return a valid `JsObject + unsafe { o.trace() } + } } #[inline] - pub fn as_number(&self) -> Option { - match *self { - Self::Integer(integer) => Some(integer.into()), - Self::Rational(rational) => Some(rational), - _ => None, + unsafe fn root(&self) { + if let Some(o) = self.as_object() { + // SAFETY: `self.as_object()` must always return a valid `JsObject + unsafe { o.root() } } } - /// Returns true if the value is a string. #[inline] - pub fn is_string(&self) -> bool { - matches!(self, Self::String(_)) + unsafe fn unroot(&self) { + if let Some(o) = self.as_object() { + // SAFETY: `self.as_object()` must always return a valid `JsObject + unsafe { o.unroot() } + } } - /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn as_string(&self) -> Option<&JsString> { - match self { - Self::String(ref string) => Some(string), - _ => None, + fn finalize_glue(&self) { + if let Some(o) = self.as_object() { + o.finalize_glue(); } } +} +#[cfg(test)] +mod tests_nan_box { + use super::*; - /// Returns true if the value is a boolean. + #[test] + fn bigint() { + let value = JsValue::new(JsBigInt::new(12345)); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + assert!(!value.is_symbol()); + + assert!(value.is_bigint()); + + let bigint = value.as_bigint().unwrap(); + + assert_eq!(bigint, &JsBigInt::new(12345)); + } + + #[test] + fn symbol() { + let value = JsValue::new(JsSymbol::new(Some("description...".into()))); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + + assert!(value.is_symbol()); + + let symbol = value.as_symbol().unwrap(); + + assert_eq!(symbol.description(), Some("description...".into())); + } + + #[test] + fn string() { + let value = JsValue::new("I am a string :)"); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + + assert!(value.is_string()); + + let string = value.as_string().unwrap(); + + assert_eq!(JsString::refcount(string), Some(2)); + + assert_eq!(string, "I am a string :)"); + } + + #[test] + fn object() { + //let context = Context::default(); + + let o1 = JsObject::from_proto_and_data(None, ObjectData::ordinary()); + + // let value = JsValue::new(context.construct_object()); + let value = JsValue::new(o1.clone()); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + + assert!(value.is_object()); + + let o2 = value.as_object().unwrap(); + assert!(JsObject::equals(&o1, o2)); + } + + #[test] + fn boolean() { + let value = JsValue::new(true); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(true)); + + let value = JsValue::new(false); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(false)); + } + + #[test] + fn rational() { + let value = JsValue::new(1.3); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(1.3)); + + let value = JsValue::new(f64::MAX); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MAX)); + + let value = JsValue::new(f64::MIN); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MIN)); + + let value = JsValue::nan(); + assert!(value.is_rational()); + assert!(value.as_rational().unwrap().is_nan()); + + let value = JsValue::new(12345); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::undefined(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::null(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + } + + #[test] + fn undefined() { + let value = JsValue::undefined(); + + println!("{:?}", value); + println!("{:?}", UNDEFINED_TYPE); + + assert!(value.is_undefined()); + } + + #[test] + fn null() { + let value = JsValue::null(); + + assert!(value.is_null()); + assert!(value.is_null_or_undefined()); + assert!(!value.is_undefined()); + } + + #[test] + fn integer() { + let value = JsValue::new(-0xcafe); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + + assert!(value.is_i32()); + + assert_eq!(value.as_i32(), Some(-0xcafe)); + + let value = JsValue::null(); + assert_eq!(value.as_i32(), None); + } +} + +impl JsValue { + /// Creates a new number with `Infinity` value. #[inline] - pub fn is_boolean(&self) -> bool { - matches!(self, Self::Boolean(_)) + pub fn positive_infinity() -> Self { + Self::new(f64::INFINITY) } + /// Creates a new number with `-Infinity` value. #[inline] - pub fn as_boolean(&self) -> Option { - match self { - Self::Boolean(boolean) => Some(*boolean), - _ => None, + pub fn negative_infinity() -> Self { + Self::new(f64::NEG_INFINITY) + } + + /// 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 + #[inline] + pub fn is_callable(&self) -> bool { + self.as_object().map_or(false, JsObject::is_callable) + } + + #[inline] + pub fn as_callable(&self) -> Option<&JsObject> { + self.as_object().filter(|obj| obj.is_callable()) + } + + /// Returns true if the value is a constructor object + #[inline] + pub fn is_constructor(&self) -> bool { + self.as_object().map_or(false, JsObject::is_constructor) + } + + #[inline] + pub fn as_constructor(&self) -> Option<&JsObject> { + self.as_object().filter(|obj| obj.is_constructor()) + } + + /// Returns true if the value is a 64-bit floating-point number. + #[inline] + pub fn is_double(&self) -> bool { + self.is_rational() + } + + /// Returns true if the value is integer. + #[inline] + #[allow(clippy::float_cmp)] + pub fn is_integer(&self) -> bool { + // If it can fit in a i32 and the trucated version is + // equal to the original then it is an integer. + let is_racional_intiger = |n: f64| n == f64::from(n as i32); + + if self.is_i32() { + true + } else if self.is_rational() { + is_racional_intiger(self.as_rational_unchecked()) + } else { + false } } - /// Returns true if the value is a bigint. + /// Returns true if the value is a number. #[inline] - pub fn is_bigint(&self) -> bool { - matches!(self, Self::BigInt(_)) + pub fn is_number(&self) -> bool { + self.is_i32() || self.is_rational() } - /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. #[inline] - pub fn as_bigint(&self) -> Option<&JsBigInt> { - match self { - Self::BigInt(bigint) => Some(bigint), + pub fn as_number(&self) -> Option { + match self.variant() { + JsVariant::Integer(integer) => Some(integer.into()), + JsVariant::Rational(rational) => Some(rational), _ => None, } } @@ -277,13 +872,13 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-toboolean pub fn to_boolean(&self) -> bool { - match *self { - Self::Symbol(_) | Self::Object(_) => true, - Self::String(ref s) if !s.is_empty() => true, - Self::Rational(n) if n != 0.0 && !n.is_nan() => true, - Self::Integer(n) if n != 0 => true, - Self::BigInt(ref n) if !n.is_zero() => true, - Self::Boolean(v) => v, + match self.variant() { + JsVariant::Symbol(_) | JsVariant::Object(_) => true, + JsVariant::String(s) if !s.is_empty() => true, + JsVariant::Rational(n) if n != 0.0 && !n.is_nan() => true, + JsVariant::Integer(n) if n != 0 => true, + JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::Boolean(v) => v, _ => false, } } @@ -297,28 +892,27 @@ impl JsValue { { let key = key.into(); let _timer = Profiler::global().start_event("Value::get_property", "value"); - match self { - Self::Object(ref object) => { - // TODO: had to skip `__get_own_properties__` since we don't have context here - let property = object.borrow().properties().get(&key).cloned(); - if property.is_some() { - return property; - } - - object - .prototype() - .as_ref() - .map_or(Self::Null, |obj| obj.clone().into()) - .get_property(key) + if let Some(object) = self.as_object() { + // TODO: had to skip `__get_own_properties__` since we don't have context here + let property = object.borrow().properties().get(&key).cloned(); + if property.is_some() { + return property; } - _ => None, + + object + .prototype() + .as_ref() + .map_or(Self::null(), |obj| obj.clone().into()) + .get_property(key) + } else { + None } } /// Set the kind of an object. #[inline] pub fn set_data(&self, data: ObjectData) { - if let Self::Object(ref obj) = *self { + if let Some(obj) = self.as_object() { obj.borrow_mut().data = data; } } @@ -333,9 +927,9 @@ impl JsValue { ) -> JsResult { // 1. Assert: input is an ECMAScript language value. (always a value not need to check) // 2. If Type(input) is Object, then - if self.is_object() { + if let Some(object) = self.as_object() { // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). - let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?; + let exotic_to_prim = object.get_method(WellKnownSymbols::to_primitive(), context)?; // b. If exoticToPrim is not undefined, then if let Some(exotic_to_prim) = exotic_to_prim { @@ -369,9 +963,7 @@ impl JsValue { }; // d. Return ? OrdinaryToPrimitive(input, preferredType). - self.as_object() - .expect("self was not an object") - .ordinary_to_primitive(context, preferred_type) + object.ordinary_to_primitive(context, preferred_type) } else { // 3. Return input. Ok(self.clone()) @@ -385,10 +977,10 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-tobigint pub fn to_bigint(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => context.throw_type_error("cannot convert null to a BigInt"), - Self::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), - Self::String(ref string) => { + match self.variant() { + JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"), + JsVariant::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), + JsVariant::String(string) => { if let Some(value) = JsBigInt::from_string(string) { Ok(value) } else { @@ -397,17 +989,17 @@ impl JsValue { )) } } - Self::Boolean(true) => Ok(JsBigInt::one()), - Self::Boolean(false) => Ok(JsBigInt::zero()), - Self::Integer(_) | Self::Rational(_) => { + JsVariant::Boolean(true) => Ok(JsBigInt::one()), + JsVariant::Boolean(false) => Ok(JsBigInt::zero()), + JsVariant::Integer(_) | JsVariant::Rational(_) => { context.throw_type_error("cannot convert Number to a BigInt") } - Self::BigInt(b) => Ok(b.clone()), - Self::Object(_) => { + JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) } - Self::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), + JsVariant::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), } } @@ -437,16 +1029,16 @@ impl JsValue { /// /// This function is equivalent to `String(value)` in JavaScript. pub fn to_string(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok("null".into()), - Self::Undefined => Ok("undefined".into()), - Self::Boolean(boolean) => Ok(boolean.to_string().into()), - Self::Rational(rational) => Ok(Number::to_native_string(*rational).into()), - Self::Integer(integer) => Ok(integer.to_string().into()), - Self::String(string) => Ok(string.clone()), - Self::Symbol(_) => context.throw_type_error("can't convert symbol to string"), - Self::BigInt(ref bigint) => Ok(bigint.to_string().into()), - Self::Object(_) => { + match self.variant() { + JsVariant::Null => Ok("null".into()), + JsVariant::Undefined => Ok("undefined".into()), + JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()), + JsVariant::Rational(rational) => Ok(Number::to_native_string(rational).into()), + JsVariant::Integer(integer) => Ok(integer.to_string().into()), + JsVariant::String(string) => Ok(string.clone()), + JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"), + JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) } @@ -459,32 +1051,32 @@ impl JsValue { /// /// See: pub fn to_object(&self, context: &mut Context) -> JsResult { - match self { - Self::Undefined | Self::Null => { + match self.variant() { + JsVariant::Undefined | JsVariant::Null => { context.throw_type_error("cannot convert 'null' or 'undefined' to object") } - Self::Boolean(boolean) => { + JsVariant::Boolean(boolean) => { let prototype = context.intrinsics().constructors().boolean().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::boolean(*boolean), + ObjectData::boolean(boolean), )) } - Self::Integer(integer) => { + JsVariant::Integer(integer) => { let prototype = context.intrinsics().constructors().number().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::number(f64::from(*integer)), + ObjectData::number(f64::from(integer)), )) } - Self::Rational(rational) => { + JsVariant::Rational(rational) => { let prototype = context.intrinsics().constructors().number().prototype(); Ok(JsObject::from_proto_and_data( prototype, - ObjectData::number(*rational), + ObjectData::number(rational), )) } - Self::String(ref string) => { + JsVariant::String(string) => { let prototype = context.intrinsics().constructors().string().prototype(); let object = @@ -500,14 +1092,14 @@ impl JsValue { ); Ok(object) } - Self::Symbol(ref symbol) => { + JsVariant::Symbol(symbol) => { let prototype = context.intrinsics().constructors().symbol().prototype(); Ok(JsObject::from_proto_and_data( prototype, ObjectData::symbol(symbol.clone()), )) } - Self::BigInt(ref bigint) => { + JsVariant::BigInt(bigint) => { let prototype = context .intrinsics() .constructors() @@ -518,7 +1110,7 @@ impl JsValue { ObjectData::big_int(bigint.clone()), )) } - Self::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -526,16 +1118,19 @@ impl JsValue { /// /// See pub fn to_property_key(&self, context: &mut Context) -> JsResult { - Ok(match self { + Ok(match self.variant() { // Fast path: - Self::String(string) => string.clone().into(), - Self::Symbol(symbol) => symbol.clone().into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), // Slow path: - _ => match self.to_primitive(context, PreferredType::String)? { - Self::String(ref string) => string.clone().into(), - Self::Symbol(ref symbol) => symbol.clone().into(), - primitive => primitive.to_string(context)?.into(), - }, + _ => { + let primitive = self.to_primitive(context, PreferredType::String)?; + match primitive.variant() { + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + _ => primitive.to_string(context)?.into(), + } + }, }) } @@ -557,7 +1152,7 @@ impl JsValue { /// See: pub fn to_u32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let JsValue::Integer(number) = *self { + if let Some(number) = self.as_i32() { return Ok(number as u32); } let number = self.to_number(context)?; @@ -570,7 +1165,7 @@ impl JsValue { /// See: pub fn to_i32(&self, context: &mut Context) -> JsResult { // This is the fast path, if the value is Integer we can just return it. - if let JsValue::Integer(number) = *self { + if let Some(number) = self.as_i32() { return Ok(number); } let number = self.to_number(context)?; @@ -851,16 +1446,16 @@ impl JsValue { /// /// See: pub fn to_number(&self, context: &mut Context) -> JsResult { - match *self { - Self::Null => Ok(0.0), - Self::Undefined => Ok(f64::NAN), - Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - Self::String(ref string) => Ok(string.string_to_number()), - Self::Rational(number) => Ok(number), - Self::Integer(integer) => Ok(f64::from(integer)), - Self::Symbol(_) => context.throw_type_error("argument must not be a symbol"), - Self::BigInt(_) => context.throw_type_error("argument must not be a bigint"), - Self::Object(_) => { + match self.variant() { + JsVariant::Null => Ok(0.0), + JsVariant::Undefined => Ok(f64::NAN), + JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), + JsVariant::String(string) => Ok(string.string_to_number()), + JsVariant::Rational(number) => Ok(number), + JsVariant::Integer(integer) => Ok(f64::from(integer)), + JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"), + JsVariant::BigInt(_) => context.throw_type_error("argument must not be a bigint"), + JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_number(context) } @@ -920,15 +1515,15 @@ impl JsValue { /// /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator pub fn type_of(&self) -> JsString { - match *self { - Self::Rational(_) | Self::Integer(_) => "number", - Self::String(_) => "string", - Self::Boolean(_) => "boolean", - Self::Symbol(_) => "symbol", - Self::Null => "object", - Self::Undefined => "undefined", - Self::BigInt(_) => "bigint", - Self::Object(ref object) => { + match self.variant() { + JsVariant::Rational(_) | JsVariant::Integer(_) => "number", + JsVariant::String(_) => "string", + JsVariant::Boolean(_) => "boolean", + JsVariant::Symbol(_) => "symbol", + JsVariant::Null => "object", + JsVariant::Undefined => "undefined", + JsVariant::BigInt(_) => "bigint", + JsVariant::Object(object) => { if object.is_callable() { "function" } else { @@ -964,7 +1559,7 @@ impl JsValue { impl Default for JsValue { fn default() -> Self { - Self::Undefined + Self::undefined() } } diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index 4e45f6790e8..b3bca772469 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -1,5 +1,5 @@ use super::{ - Context, FromStr, JsBigInt, JsResult, JsString, JsValue, Numeric, PreferredType, + Context, FromStr, JsBigInt, JsResult, JsString, JsValue, JsVariant, Numeric, PreferredType, WellKnownSymbols, }; use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; @@ -7,60 +7,61 @@ use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; impl JsValue { #[inline] pub fn add(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: // Numeric add - (Self::Integer(x), Self::Integer(y)) => x - .checked_add(*y) - .map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x + y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), + (JsVariant::Integer(x), JsVariant::Integer(y)) => x + .checked_add(y) + .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x + y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) + y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x + f64::from(y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(x, y)), // String concat - (Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), - (Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)), - (x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)), + (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(x, y)), + (JsVariant::String(x), _) => Self::from(JsString::concat(x, other.to_string(context)?)), + (_, JsVariant::String(y)) => Self::from(JsString::concat(self.to_string(context)?, y)), // Slow path: - (_, _) => match ( - self.to_primitive(context, PreferredType::Default)?, - other.to_primitive(context, PreferredType::Default)?, - ) { - (Self::String(ref x), ref y) => { - Self::from(JsString::concat(x, y.to_string(context)?)) - } - (ref x, Self::String(ref y)) => { - Self::from(JsString::concat(x.to_string(context)?, y)) - } - (x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) { - (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), - (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { - Self::new(JsBigInt::add(x, y)) + (_, _) => { + let x = self.to_primitive(context, PreferredType::Default)?; + let y = other.to_primitive(context, PreferredType::Default)?; + match (x.variant(), y.variant()) { + (JsVariant::String(ref x), _) => { + Self::from(JsString::concat(x, y.to_string(context)?)) } - (_, _) => { - return context.throw_type_error( - "cannot mix BigInt and other types, use explicit conversions", - ) + (_, JsVariant::String(ref y)) => { + Self::from(JsString::concat(x.to_string(context)?, y)) } - }, - }, + (_, _) => match (x.to_numeric(context)?, y.to_numeric(context)?) { + (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), + (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => { + Self::new(JsBigInt::add(x, y)) + } + (_, _) => { + return context.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ) + } + }, + } + } }) } #[inline] pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_sub(*y) - .map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x - y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)), + (JsVariant::Integer(x), JsVariant::Integer(y)) => x + .checked_sub(y) + .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x - y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) - y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x - f64::from(y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::sub(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -77,16 +78,16 @@ impl JsValue { #[inline] pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_mul(*y) - .map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x * y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)), + (JsVariant::Integer(x), JsVariant::Integer(y)) => x + .checked_mul(y) + .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x * y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) * y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x * f64::from(y)), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::mul(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -103,17 +104,17 @@ impl JsValue { #[inline] pub fn div(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => x - .checked_div(*y) - .filter(|div| *y * div == *x) - .map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x / y), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y), - (Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)), - - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (JsVariant::Integer(x), JsVariant::Integer(y)) => x + .checked_div(y) + .filter(|div| y * div == x) + .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x / y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) / y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x / f64::from(y)), + + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } @@ -140,33 +141,32 @@ impl JsValue { #[inline] pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => { - if *y == 0 { + (JsVariant::Integer(x), JsVariant::Integer(y)) => { + if y == 0 { Self::nan() } else { - match x % *y { - rem if rem == 0 && *x < 0 => Self::new(-0.0), + match x % y { + rem if rem == 0 && x < 0 => Self::new(-0.0), rem => Self::new(rem), } } } - (Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)), - (Self::Integer(x), Self::Rational(y)) => { - let x = f64::from(*x); + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new((x % y).copysign(x)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + let x = f64::from(x); Self::new((x % y).copysign(x)) } - - (Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)), - - (Self::BigInt(ref x), Self::BigInt(ref y)) => { + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Self::new((x % f64::from(y)).copysign(x)) + } + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } Self::new(JsBigInt::rem(x, y)) } - // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b), @@ -187,17 +187,19 @@ impl JsValue { #[inline] pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => u32::try_from(*y) + (JsVariant::Integer(x), JsVariant::Integer(y)) => u32::try_from(y) .ok() .and_then(|y| x.checked_pow(y)) - .map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), - (Self::Rational(x), Self::Rational(y)) => Self::new(x.powf(*y)), - (Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)), + .map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x.powf(y)), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x).powf(y)), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x.powi(y)), - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b, context)?), + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { + Self::new(JsBigInt::pow(a, b, context)?) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -216,16 +218,16 @@ impl JsValue { #[inline] pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x & y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) & f64_to_int32(*y)) + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(x & y), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x) & f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x & f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) & y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -246,16 +248,16 @@ impl JsValue { #[inline] pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x | y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) | f64_to_int32(*y)) + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(x | y), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x) | f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x | f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) | y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -276,16 +278,16 @@ impl JsValue { #[inline] pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x) ^ f64_to_int32(*y)) + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(x ^ y), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x) ^ f64_to_int32(y)) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)), - (Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x ^ f64_to_int32(y)), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) ^ y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -306,18 +308,20 @@ impl JsValue { #[inline] pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y))) + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(x.wrapping_shl(y as u32)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) + } + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Self::new(x.wrapping_shl(f64_to_uint32(y))) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Self::new(f64_to_int32(x).wrapping_shl(y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { Self::new(JsBigInt::shift_left(a, b, context)?) } @@ -340,18 +344,20 @@ impl JsValue { #[inline] pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer(x), JsVariant::Integer(y)) => Self::new(x.wrapping_shr(y as u32)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) } - (Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))), - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Self::new(x.wrapping_shr(f64_to_uint32(y))) + } + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Self::new(f64_to_int32(x).wrapping_shr(y as u32)) } - (Self::BigInt(ref a), Self::BigInt(ref b)) => { + (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { Self::new(JsBigInt::shift_right(a, b, context)?) } @@ -374,17 +380,19 @@ impl JsValue { #[inline] pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path: - (Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)), - (Self::Rational(x), Self::Rational(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer(x), JsVariant::Integer(y)) => { + Self::new((x as u32).wrapping_shr(y as u32)) + } + (JsVariant::Rational(x), JsVariant::Rational(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) } - (Self::Integer(x), Self::Rational(y)) => { - Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) + (JsVariant::Integer(x), JsVariant::Rational(y)) => { + Self::new((x as u32).wrapping_shr(f64_to_uint32(y))) } - (Self::Rational(x), Self::Integer(y)) => { - Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) + (JsVariant::Rational(x), JsVariant::Integer(y)) => { + Self::new(f64_to_uint32(x).wrapping_shr(y as u32)) } // Slow path: @@ -443,22 +451,22 @@ impl JsValue { #[inline] pub fn neg(&self, context: &mut Context) -> JsResult { - Ok(match *self { - Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN), - Self::Object(_) => Self::new(match self.to_numeric_number(context) { + Ok(match self.variant() { + JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN), + JsVariant::Object(_) => Self::new(match self.to_numeric_number(context) { Ok(num) => -num, Err(_) => f64::NAN, }), - Self::String(ref str) => Self::new(match f64::from_str(str) { + JsVariant::String(str) => Self::new(match f64::from_str(str) { Ok(num) => -num, Err(_) => f64::NAN, }), - Self::Rational(num) => Self::new(-num), - Self::Integer(num) if num == 0 => Self::new(-f64::from(0)), - Self::Integer(num) => Self::new(-num), - Self::Boolean(true) => Self::new(1), - Self::Boolean(false) | Self::Null => Self::new(0), - Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)), + JsVariant::Rational(num) => Self::new(-num), + JsVariant::Integer(num) if num == 0 => Self::new(-f64::from(0)), + JsVariant::Integer(num) => Self::new(-num), + JsVariant::Boolean(true) => Self::new(1), + JsVariant::Boolean(false) | JsVariant::Null => Self::new(0), + JsVariant::BigInt(x) => Self::new(JsBigInt::neg(x)), }) } @@ -490,13 +498,13 @@ impl JsValue { left_first: bool, context: &mut Context, ) -> JsResult { - Ok(match (self, other) { + Ok(match (self.variant(), other.variant()) { // Fast path (for some common operations): - (Self::Integer(x), Self::Integer(y)) => (x < y).into(), - (Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y), - (Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)), - (Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y), - (Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(), + (JsVariant::Integer(x), JsVariant::Integer(y)) => (x < y).into(), + (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::less_than(f64::from(x), y), + (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::less_than(x, f64::from(y)), + (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::less_than(x, y), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -511,8 +519,8 @@ impl JsValue { (px, py) }; - match (px, py) { - (Self::String(ref x), Self::String(ref y)) => { + match (px.variant(), py.variant()) { + (JsVariant::String(x), JsVariant::String(y)) => { if x.starts_with(y.as_str()) { return Ok(AbstractRelation::False); } @@ -526,21 +534,21 @@ impl JsValue { } unreachable!() } - (Self::BigInt(ref x), Self::String(ref y)) => { + (JsVariant::BigInt(x), JsVariant::String(y)) => { if let Some(y) = JsBigInt::from_string(y) { (*x < y).into() } else { AbstractRelation::Undefined } } - (Self::String(ref x), Self::BigInt(ref y)) => { + (JsVariant::String(x), JsVariant::BigInt(y)) => { if let Some(x) = JsBigInt::from_string(x) { (x < *y).into() } else { AbstractRelation::Undefined } } - (px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) { + (_, _) => match (px.to_numeric(context)?, py.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(), (Numeric::BigInt(ref x), Numeric::Number(y)) => { diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index efa4422c963..73db0b811f8 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -1,6 +1,6 @@ //! This module implements the conversions from and into [`serde_json::Value`]. -use super::JsValue; +use super::{JsValue, JsVariant}; use crate::{ builtins::Array, property::{PropertyDescriptor, PropertyKey}, @@ -41,13 +41,13 @@ impl JsValue { const MIN_INT: i64 = i32::MIN as i64; match json { - Value::Null => Ok(Self::Null), - Value::Bool(b) => Ok(Self::Boolean(*b)), + Value::Null => Ok(Self::null()), + Value::Bool(b) => Ok(Self::from(*b)), Value::Number(num) => num .as_i64() .filter(|n| (MIN_INT..=MAX_INT).contains(n)) - .map(|i| Self::Integer(i as i32)) - .or_else(|| num.as_f64().map(Self::Rational)) + .map(|i| Self::from(i as i32)) + .or_else(|| num.as_f64().map(Self::from)) .ok_or_else(|| { context.construct_type_error(format!( "could not convert JSON number {num} to JsValue" @@ -104,15 +104,15 @@ impl JsValue { /// # assert_eq!(json, back_to_json); /// ``` pub fn to_json(&self, context: &mut Context) -> JsResult { - match self { - Self::Null => Ok(Value::Null), - Self::Undefined => todo!("undefined to JSON"), - &Self::Boolean(b) => Ok(b.into()), - Self::String(string) => Ok(string.as_str().into()), - &Self::Rational(rat) => Ok(rat.into()), - &Self::Integer(int) => Ok(int.into()), - Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), - Self::Object(obj) => { + match self.variant() { + JsVariant::Null => Ok(Value::Null), + JsVariant::Undefined => todo!("undefined to JSON"), + JsVariant::Boolean(b) => Ok(b.into()), + JsVariant::String(string) => Ok(string.as_str().into()), + JsVariant::Rational(rat) => Ok(rat.into()), + JsVariant::Integer(int) => Ok(int.into()), + JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), + JsVariant::Object(obj) => { if obj.is_array() { let len = obj.length_of_array_like(context)?; let mut arr = Vec::with_capacity(len); @@ -120,9 +120,12 @@ impl JsValue { let obj = obj.borrow(); for k in 0..len as u32 { - let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| { - desc.value().cloned().unwrap_or(Self::Null) - }); + let val = obj + .properties() + .get(&k.into()) + .map_or(Self::null(), |desc| { + desc.value().cloned().unwrap_or(Self::null()) + }); arr.push(val.to_json(context)?); } @@ -149,7 +152,7 @@ impl JsValue { Ok(Value::Object(map)) } } - Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), + JsVariant::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), } } } diff --git a/boa_engine/src/value/type.rs b/boa_engine/src/value/type.rs index 61e100bae6a..3504f2ca100 100644 --- a/boa_engine/src/value/type.rs +++ b/boa_engine/src/value/type.rs @@ -1,4 +1,4 @@ -use super::JsValue; +use super::{JsValue, JsVariant}; /// Possible types of values as defined at . #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -21,15 +21,15 @@ impl JsValue { /// /// Check [`JsValue::type_of`] if you need to call the `typeof` operator. pub fn get_type(&self) -> Type { - match *self { - Self::Rational(_) | Self::Integer(_) => Type::Number, - Self::String(_) => Type::String, - Self::Boolean(_) => Type::Boolean, - Self::Symbol(_) => Type::Symbol, - Self::Null => Type::Null, - Self::Undefined => Type::Undefined, - Self::BigInt(_) => Type::BigInt, - Self::Object(_) => Type::Object, + match self.variant() { + JsVariant::Rational(_) | JsVariant::Integer(_) => Type::Number, + JsVariant::String(_) => Type::String, + JsVariant::Boolean(_) => Type::Boolean, + JsVariant::Symbol(_) => Type::Symbol, + JsVariant::Null => Type::Null, + JsVariant::Undefined => Type::Undefined, + JsVariant::BigInt(_) => Type::BigInt, + JsVariant::Object(_) => Type::Object, } } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 80e3c34f108..7cfd1ddbb9e 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -681,7 +681,7 @@ impl JsObject { let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -789,7 +789,7 @@ impl JsObject { let mut args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v @@ -983,7 +983,7 @@ impl JsObject { let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); v.extend(vec![ - JsValue::Undefined; + JsValue::undefined(); code.params.parameters.len() - args.len() ]); v diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 62e09cc1759..9ea4fc7488f 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -6,7 +6,7 @@ use crate::{ builtins::{function::Function, iterable::IteratorRecord, Array, ForInIterator, Number}, object::PrivateElement, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, - value::Numeric, + value::{JsVariant, Numeric}, vm::{ call_frame::CatchAddresses, code_block::{create_function_object, create_generator_function_object, Readable}, @@ -191,7 +191,7 @@ impl Context { Opcode::PushClassPrototype => { let superclass = self.vm.pop(); if superclass.is_null() { - self.vm.push(JsValue::Null); + self.vm.push(JsValue::null()); } if let Some(superclass) = superclass.as_constructor() { let proto = superclass.get("prototype", self)?; @@ -384,7 +384,7 @@ impl Context { .into(); self.global_bindings_mut().entry(key).or_insert( PropertyDescriptor::builder() - .value(JsValue::Undefined) + .value(JsValue::undefined()) .writable(true) .enumerable(true) .configurable(true) @@ -394,7 +394,7 @@ impl Context { self.realm.environments.put_value_if_uninitialized( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + JsValue::undefined(), ); } } @@ -426,7 +426,7 @@ impl Context { self.realm.environments.put_value( binding_locator.environment_index(), binding_locator.binding_index(), - JsValue::Undefined, + JsValue::undefined(), ); } Opcode::DefInitLet | Opcode::DefInitConst | Opcode::DefInitArg => { @@ -1244,8 +1244,8 @@ impl Context { let func = self.vm.pop(); let mut this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1264,7 +1264,7 @@ impl Context { crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; self.vm.push(result); } else { - self.vm.push(JsValue::Undefined); + self.vm.push(JsValue::undefined()); } } else { let result = object.__call__(&this, &arguments, self)?; @@ -1292,8 +1292,8 @@ impl Context { } arguments.append(&mut rest_arguments); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1312,7 +1312,7 @@ impl Context { crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; self.vm.push(result); } else { - self.vm.push(JsValue::Undefined); + self.vm.push(JsValue::undefined()); } } else { let result = object.__call__(&this, &arguments, self)?; @@ -1333,8 +1333,8 @@ impl Context { let func = self.vm.pop(); let mut this = self.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object, _ => return self.throw_type_error("not a callable function"), }; @@ -1367,8 +1367,8 @@ impl Context { } arguments.append(&mut rest_arguments); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), + let object = match func.variant() { + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; @@ -1592,7 +1592,7 @@ impl Context { let iterator = self.vm.pop(); if !done.as_boolean().expect("not a boolean") { let iterator_record = IteratorRecord::new(iterator, next_function); - iterator_record.close(Ok(JsValue::Null), self)?; + iterator_record.close(Ok(JsValue::null()), self)?; } } Opcode::IteratorToArray => { @@ -1773,7 +1773,7 @@ impl Context { self.vm.frame_mut().pc = done_address as usize; let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); - iterator_record.close(Ok(JsValue::Undefined), self)?; + iterator_record.close(Ok(JsValue::undefined()), self)?; let error = self.construct_type_error("iterator does not have a throw method"); return Err(error); @@ -1896,7 +1896,7 @@ impl Context { } Ok(ShouldExit::False) => {} Ok(ShouldExit::Yield) => { - let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); + let result = self.vm.stack.pop().unwrap_or(JsValue::undefined()); return Ok((result, ReturnType::Yield)); } Err(e) => { diff --git a/boa_engine/src/vm/tests.rs b/boa_engine/src/vm/tests.rs index 8a5fda7be06..cbca50d489a 100644 --- a/boa_engine/src/vm/tests.rs +++ b/boa_engine/src/vm/tests.rs @@ -62,7 +62,7 @@ fn multiple_catches() { assert_eq!( Context::default().eval(source.as_bytes()), - Ok(JsValue::Undefined) + Ok(JsValue::undefined()) ); } diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index e482447e331..dc932028a9f 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -46,7 +46,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult Date: Thu, 3 Mar 2022 10:12:21 -0600 Subject: [PATCH 2/8] Return clones for pointer types instead of refs --- .../src/builtins/array/array_iterator.rs | 2 +- boa_engine/src/builtins/array/mod.rs | 10 +- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- boa_engine/src/builtins/bigint/mod.rs | 1 - boa_engine/src/builtins/boolean/tests.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 17 +- boa_engine/src/builtins/date/mod.rs | 4 +- boa_engine/src/builtins/function/mod.rs | 5 +- boa_engine/src/builtins/function/tests.rs | 2 +- boa_engine/src/builtins/generator/mod.rs | 2 +- boa_engine/src/builtins/iterable/mod.rs | 2 +- boa_engine/src/builtins/json/mod.rs | 14 +- boa_engine/src/builtins/map/map_iterator.rs | 3 +- boa_engine/src/builtins/map/mod.rs | 2 +- .../src/builtins/object/for_in_iterator.rs | 3 +- boa_engine/src/builtins/object/mod.rs | 14 +- boa_engine/src/builtins/proxy/mod.rs | 2 +- boa_engine/src/builtins/reflect/mod.rs | 11 +- boa_engine/src/builtins/regexp/mod.rs | 27 +-- .../builtins/regexp/regexp_string_iterator.rs | 3 +- boa_engine/src/builtins/set/set_iterator.rs | 6 +- boa_engine/src/builtins/string/mod.rs | 13 +- .../src/builtins/string/string_iterator.rs | 3 +- boa_engine/src/builtins/symbol/mod.rs | 3 +- boa_engine/src/builtins/typed_array/mod.rs | 28 +-- boa_engine/src/class.rs | 8 +- .../object/internal_methods/bound_function.rs | 4 +- boa_engine/src/object/internal_methods/mod.rs | 2 +- .../src/object/internal_methods/proxy.rs | 6 +- boa_engine/src/object/jsarray.rs | 5 - boa_engine/src/object/operations.rs | 8 +- boa_engine/src/tests.rs | 6 +- boa_engine/src/value/display.rs | 8 +- boa_engine/src/value/equality.rs | 40 ++-- boa_engine/src/value/mod.rs | 192 ++++++++---------- boa_engine/src/value/operations.rs | 46 +++-- boa_engine/src/value/serde_json.rs | 6 +- boa_engine/src/vm/code_block.rs | 2 +- boa_engine/src/vm/mod.rs | 14 +- 39 files changed, 262 insertions(+), 266 deletions(-) diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 8eb86230531..ba9e36fa30b 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -68,7 +68,7 @@ impl ArrayIterator { /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let array_iterator = this.as_object(); - let mut array_iterator = array_iterator.map(JsObject::borrow_mut); + let mut array_iterator = array_iterator.as_ref().map(JsObject::borrow_mut); let array_iterator = array_iterator .as_mut() .and_then(|obj| obj.as_array_iterator_mut()) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 5f01df391bb..8478bf234b3 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -369,8 +369,7 @@ impl Array { Ok( c.construct(&[JsValue::new(length)], &c.clone().into(), context)? .as_object() - .expect("constructing an object should always return an object") - .clone(), + .expect("constructing an object should always return an object"), ) } else { context.throw_type_error("Symbol.species must be a constructor") @@ -584,7 +583,6 @@ impl Array { Some(constructor) => constructor .construct(&[len.into()], this, context)? .as_object() - .cloned() .ok_or_else(|| { context.construct_type_error("object constructor didn't return an object") })?, @@ -1705,7 +1703,7 @@ impl Array { source_len as u64, 0, 1, - Some(mapper_function), + Some(&mapper_function), args.get_or_undefined(1), context, )?; @@ -1795,7 +1793,7 @@ impl Array { // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) target_index = Self::flatten_into_array( target, - element, + &element, element_len as u64, target_index, new_depth, @@ -2363,7 +2361,7 @@ impl Array { } // 4. If comparefn is not undefined, then - if let Some(cmp) = comparefn { + if let Some(ref cmp) = comparefn { let args = [x.clone(), y.clone()]; // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). let v = cmp diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index e3cb85f568f..fcaef794898 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -244,7 +244,7 @@ impl ArrayBuffer { let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = new.as_object().cloned().ok_or_else(|| { + let new_obj = new.as_object().ok_or_else(|| { context.construct_type_error("ArrayBuffer constructor returned non-object value") })?; diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index dc1ddd3485a..3fe1c0dfb24 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -133,7 +133,6 @@ impl BigInt { value // 1. If Type(value) is BigInt, return value. .as_bigint() - .cloned() // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. // b. Return value.[[BigIntData]]. diff --git a/boa_engine/src/builtins/boolean/tests.rs b/boa_engine/src/builtins/boolean/tests.rs index 3d6ae4a3dec..9b3547617d7 100644 --- a/boa_engine/src/builtins/boolean/tests.rs +++ b/boa_engine/src/builtins/boolean/tests.rs @@ -60,6 +60,6 @@ fn instances_have_correct_proto_set() { assert_eq!( &*bool_instance.as_object().unwrap().prototype(), - &bool_prototype.as_object().cloned() + &bool_prototype.as_object() ); } diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 0bb9b2497c7..59c8ae2bb63 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -164,7 +164,7 @@ impl DataView { prototype, ObjectData::data_view(Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. - viewed_array_buffer: buffer_obj.clone(), + viewed_array_buffer: buffer_obj, // 12. Set O.[[ByteLength]] to viewByteLength. byte_length: view_byte_length, // 13. Set O.[[ByteOffset]] to offset. @@ -194,7 +194,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_ref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -223,7 +224,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_ref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -262,7 +264,8 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); + let dataview = this.as_object(); + let dataview = dataview.as_ref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -302,7 +305,8 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); + let view = view.as_object(); + let view = view.as_ref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) @@ -661,7 +665,8 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); + let view = view.as_object(); + let view = view.as_ref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index 90a49164312..d7f04062b21 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -397,7 +397,7 @@ impl Date { dt.0 } else { let tv = value.to_primitive(context, PreferredType::Default)?; - if let JsVariant::String(str) = tv.variant() { + if let JsVariant::String(ref str) = tv.variant() { match chrono::DateTime::parse_from_rfc3339(str) { Ok(dt) => Some(dt.naive_utc()), _ => None, @@ -516,7 +516,7 @@ impl Date { let hint = args.get_or_undefined(0); - let try_first = match hint.as_string().map(JsString::as_str) { + let try_first = match hint.as_string().as_ref().map(JsString::as_str) { // 3. If hint is "string" or "default", then // a. Let tryFirst be string. Some("string" | "default") => PreferredType::String, diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 1f6cf9ee4cf..1caa0cf25e1 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -397,7 +397,7 @@ impl BuiltInFunctionObject { let target_name = target.get("name", context)?; // 9. If Type(targetName) is not String, set targetName to the empty String. - let target_name = target_name.as_string().cloned().unwrap_or_default(); + let target_name = target_name.as_string().unwrap_or_default(); // 10. Perform SetFunctionName(F, targetName, "bound"). set_function_name(&f, &target_name.into(), Some("bound"), context); @@ -433,7 +433,8 @@ impl BuiltInFunctionObject { #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let object = this.as_object().map(JsObject::borrow); + let object = this.as_object(); + let object = object.as_ref().map(JsObject::borrow); let function = object .as_deref() .and_then(Object::as_function) diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index f1833439739..9ad573af311 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -246,7 +246,7 @@ fn closure_capture_clone() { object .__get_own_property__(&"key".into(), context)? .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string().cloned()) + .and_then(|val| val.as_string()) .ok_or_else(|| context.construct_type_error("invalid `key` property"))?, ); Ok(hw.into()) diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 358890dac77..472ee5442b7 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -149,7 +149,7 @@ impl Generator { // 1. Return ? GeneratorResume(this value, value, empty). match this.as_object() { Some(obj) if obj.is_generator() => { - Self::generator_resume(obj, args.get_or_undefined(0), context) + Self::generator_resume(&obj, args.get_or_undefined(0), context) } _ => context.throw_type_error("Generator.prototype.next called on non generator"), } diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 2a1390cefc6..1c2208c00ca 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -292,7 +292,7 @@ impl IteratorRecord { // 3. If Type(result) is not Object, throw a TypeError exception. // 4. Return result. if let Some(o) = result.as_object() { - Ok(IteratorResult { object: o.clone() }) + Ok(IteratorResult { object: o }) } else { context.throw_type_error("next value should be an object") } diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index e51e10110cf..195337d8eab 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -105,7 +105,7 @@ impl Json { .expect("CreateDataPropertyOrThrow should never throw here"); // d. Return ? InternalizeJSONProperty(root, rootName, reviver). - Self::internalize_json_property(&root, "".into(), obj, context) + Self::internalize_json_property(&root, "".into(), &obj, context) } else { // 12. Else, // a. Return unfiltered. @@ -141,7 +141,7 @@ impl Json { // 1. Let prop be ! ToString(𝔽(I)). // 2. Let newElement be ? InternalizeJSONProperty(val, prop, reviver). let new_element = Self::internalize_json_property( - obj, + &obj, i.to_string().into(), reviver, context, @@ -173,7 +173,7 @@ impl Json { // 1. Let newElement be ? InternalizeJSONProperty(val, P, reviver). let new_element = - Self::internalize_json_property(obj, p.clone(), reviver, context)?; + Self::internalize_json_property(&obj, p.clone(), reviver, context)?; // 2. If newElement is undefined, then if new_element.is_undefined() { @@ -388,7 +388,7 @@ impl Json { } // 4. If Type(value) is Object, then - if let Some(obj) = value.as_object().cloned() { + if let Some(obj) = value.as_object() { // a. If value has a [[NumberData]] internal slot, then if obj.is_number() { // i. Set value to ? ToNumber(value). @@ -428,7 +428,7 @@ impl Json { // 8. If Type(value) is String, return QuoteJSONString(value). if let Some(s) = value.as_string() { - return Ok(Some(Self::quote_json_string(s))); + return Ok(Some(Self::quote_json_string(&s))); } // 9. If Type(value) is Number, then @@ -458,9 +458,9 @@ impl Json { // b. If isArray is true, return ? SerializeJSONArray(state, value). // c. Return ? SerializeJSONObject(state, value). return if obj.is_array_abstract(context)? { - Ok(Some(Self::serialize_json_array(state, obj, context)?)) + Ok(Some(Self::serialize_json_array(state, &obj, context)?)) } else { - Ok(Some(Self::serialize_json_object(state, obj, context)?)) + Ok(Some(Self::serialize_json_object(state, &obj, context)?)) }; } } diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index 7ed7c236c34..c0ebf14d240 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -71,7 +71,8 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut map_iterator = this.as_object().map(JsObject::borrow_mut); + let map_iterator = this.as_object(); + let mut map_iterator = map_iterator.as_ref().map(JsObject::borrow_mut); let map_iterator = map_iterator .as_mut() .and_then(|obj| obj.as_map_iterator_mut()) diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index cf06e73209c..8e29e718760 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -431,7 +431,7 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). let map = this .as_object() - .filter(|obj| obj.is_map()) + .filter(JsObject::is_map) .ok_or_else(|| context.construct_type_error("`this` is not a Map"))?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 612daa0b499..4e2175dbdfd 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -67,7 +67,8 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); + let iterator = this.as_object(); + let mut iterator = iterator.as_ref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_for_in_iterator_mut()) diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 5bac89d8250..c7a77a20c03 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -130,10 +130,9 @@ impl Object { let properties = args.get_or_undefined(1); let obj = match prototype.variant() { - JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data( - prototype.as_object().cloned(), - ObjectData::ordinary(), - ), + JsVariant::Object(_) | JsVariant::Null => { + JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary()) + } _ => { return context.throw_type_error(format!( "Object prototype may only be an Object or null: {}", @@ -344,7 +343,7 @@ impl Object { let proto = args.get_or_undefined(1); let proto = match proto.variant() { - JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Object(ref obj) => Some(obj.clone()), JsVariant::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. _ => { @@ -449,7 +448,7 @@ impl Object { let arg = args.get_or_undefined(0); if let Some(obj) = arg.as_object() { let props = args.get_or_undefined(1); - object_define_properties(obj, props, context)?; + object_define_properties(&obj, props, context)?; Ok(arg.clone()) } else { context.throw_type_error("Expected an object") @@ -525,7 +524,8 @@ impl Object { let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; // 16. If Type(tag) is not String, set tag to builtinTag. - let tag_str = tag.as_string().map_or(builtin_tag, JsString::as_str); + let tag_str = tag.as_string(); + let tag_str = tag_str.as_ref().map_or(builtin_tag, JsString::as_str); // 17. Return the string-concatenation of "[object ", tag, and "]". Ok(format!("[object {tag_str}]").into()) diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index e709300af62..5891549cad1 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -113,7 +113,7 @@ impl Proxy { let p = JsObject::from_proto_and_data( context.intrinsics().constructors().object().prototype(), ObjectData::proxy( - Self::new(target.clone(), handler.clone()), + Self::new(target.clone(), handler), target.is_callable(), target.is_constructor(), ), diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 82cdb08633a..948865eae07 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -114,7 +114,12 @@ impl Reflect { } let new_target = if let Some(new_target) = args.get(2) { - if new_target.as_object().map(JsObject::is_constructor) != Some(true) { + if new_target + .as_object() + .as_ref() + .map(JsObject::is_constructor) + != Some(true) + { return context.throw_type_error("newTarget must be constructor"); } new_target.clone() @@ -146,7 +151,7 @@ impl Reflect { let key = args.get_or_undefined(1).to_property_key(context)?; let prop_desc: JsValue = args .get(2) - .and_then(|v| v.as_object().cloned()) + .and_then(JsValue::as_object) .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .into(); @@ -383,7 +388,7 @@ impl Reflect { .and_then(JsValue::as_object) .ok_or_else(|| context.construct_type_error("target must be an object"))?; let proto = match args.get_or_undefined(1).variant() { - JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Object(ref obj) => Some(obj.clone()), JsVariant::Null => None, _ => return context.throw_type_error("proto must be an object or null"), }; diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index dd924457042..9cfc3f63a30 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -178,14 +178,14 @@ impl RegExp { let flags = args.get_or_undefined(1); // 1. Let patternIsRegExp be ? IsRegExp(pattern). - let pattern_is_regexp = pattern.as_object().filter(|obj| obj.is_regexp()); + let pattern_is_regexp = pattern.as_object().filter(JsObject::is_regexp); // 2. If NewTarget is undefined, then // 3. Else, let newTarget be NewTarget. if new_target.is_undefined() { // a. Let newTarget be the active function object. // b. If patternIsRegExp is true and flags is undefined, then - if let Some(pattern) = pattern_is_regexp { + if let Some(ref pattern) = pattern_is_regexp { if flags.is_undefined() { // i. Let patternConstructor be ? Get(pattern, "constructor"). let pattern_constructor = pattern.get("constructor", context)?; @@ -373,7 +373,7 @@ impl RegExp { } if JsObject::equals( - object, + &object, &context.intrinsics().constructors().regexp().prototype, ) { return Ok(JsValue::undefined()); @@ -701,7 +701,7 @@ impl RegExp { .to_string(context)?; // 4. Let match be ? RegExpExec(R, string). - let m = Self::abstract_exec(this, arg_str, context)?; + let m = Self::abstract_exec(&this, arg_str, context)?; // 5. If match is not null, return true; else return false. if m.is_some() { @@ -732,7 +732,7 @@ impl RegExp { // 2. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). let obj = this .as_object() - .filter(|obj| obj.is_regexp()) + .filter(JsObject::is_regexp) .ok_or_else(|| { context.construct_type_error("RegExp.prototype.exec called with invalid value") })?; @@ -741,7 +741,7 @@ impl RegExp { let arg_str = args.get_or_undefined(0).to_string(context)?; // 4. Return ? RegExpBuiltinExec(R, S). - if let Some(v) = Self::abstract_builtin_exec(obj, &arg_str, context)? { + if let Some(v) = Self::abstract_builtin_exec(&obj, &arg_str, context)? { Ok(v.into()) } else { Ok(JsValue::null()) @@ -776,7 +776,7 @@ impl RegExp { } // c. Return result. - return Ok(result.as_object().cloned()); + return Ok(result.as_object()); } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). @@ -1053,7 +1053,7 @@ impl RegExp { #[allow(clippy::if_not_else)] if !global { // a. Return ? RegExpExec(rx, S). - if let Some(v) = Self::abstract_exec(rx, arg_str, context)? { + if let Some(v) = Self::abstract_exec(&rx, arg_str, context)? { Ok(v.into()) } else { Ok(JsValue::null()) @@ -1078,7 +1078,7 @@ impl RegExp { // f. Repeat, loop { // i. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, arg_str.clone(), context)?; + let result = Self::abstract_exec(&rx, arg_str.clone(), context)?; // ii. If result is null, then // iii. Else, @@ -1257,6 +1257,7 @@ impl RegExp { let mut replace_value = args.get_or_undefined(1).clone(); let functional_replace = replace_value .as_object() + .as_ref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1286,7 +1287,7 @@ impl RegExp { // 11. Repeat, while done is false, loop { // a. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, arg_str.clone(), context)?; + let result = Self::abstract_exec(&rx, arg_str.clone(), context)?; // b. If result is null, set done to true. // c. Else, @@ -1508,7 +1509,7 @@ impl RegExp { } // 6. Let result be ? RegExpExec(rx, S). - let result = Self::abstract_exec(rx, arg_str, context)?; + let result = Self::abstract_exec(&rx, arg_str, context)?; // 7. Let currentLastIndex be ? Get(rx, "lastIndex"). let current_last_index = rx.get("lastIndex", context)?; @@ -1614,7 +1615,7 @@ impl RegExp { // 16. If size is 0, then if size == 0 { // a. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // b. If z is not null, return A. if result.is_some() { @@ -1640,7 +1641,7 @@ impl RegExp { splitter.set("lastIndex", JsValue::new(q), true, context)?; // b. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching). // d. Else, diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 620d26640b8..6467356a0a9 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -81,7 +81,8 @@ impl RegExpStringIterator { } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); + let iterator = this.as_object(); + let mut iterator = iterator.as_ref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_regexp_string_iterator_mut()) diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 07b2f5e2c72..7bea6722cca 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -66,7 +66,8 @@ impl SetIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut set_iterator = this.as_object().map(JsObject::borrow_mut); + let set_iterator = this.as_object(); + let mut set_iterator = set_iterator.as_ref().map(JsObject::borrow_mut); let set_iterator = set_iterator .as_mut() @@ -85,7 +86,8 @@ impl SetIterator { )); } - let entries = m.as_object().map(JsObject::borrow); + let entries = m.as_object(); + let entries = entries.as_ref().map(JsObject::borrow); let entries = entries .as_ref() .and_then(|obj| obj.as_set_ref()) diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 1728827314d..f78e6fe1f5e 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -248,7 +248,6 @@ impl String { fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(value) is String, return value. this.as_string() - .cloned() // 2. If Type(value) is Object and value has a [[StringData]] internal slot, then // a. Let s be value.[[StringData]]. // b. Assert: Type(s) is String. @@ -1001,6 +1000,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() + .as_ref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1096,7 +1096,7 @@ impl String { // a. Let isRegExp be ? IsRegExp(searchValue). if let Some(obj) = search_value.as_object() { // b. If isRegExp is true, then - if is_reg_exp_object(obj, context)? { + if is_reg_exp_object(&obj, context)? { // i. Let flags be ? Get(searchValue, "flags"). let flags = obj.get("flags", context)?; @@ -1131,6 +1131,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() + .as_ref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1975,7 +1976,7 @@ impl String { if !regexp.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(regexp). // b. If isRegExp is true, then - if let Some(regexp_obj) = regexp.as_object().filter(|obj| obj.is_regexp()) { + if let Some(regexp_obj) = regexp.as_object().filter(JsObject::is_regexp) { // i. Let flags be ? Get(regexp, "flags"). let flags = regexp_obj.get("flags", context)?; @@ -2203,7 +2204,7 @@ pub(crate) fn get_substitution( result.push(second); result.push(*third); } else if let Some(capture) = captures.get(nn - 1) { - if let Some(s) = capture.as_string() { + if let Some(ref s) = capture.as_string() { result.push_str(s); } } @@ -2224,7 +2225,7 @@ pub(crate) fn get_substitution( result.push('$'); result.push(second); } else if let Some(capture) = captures.get(n - 1) { - if let Some(s) = capture.as_string() { + if let Some(ref s) = capture.as_string() { result.push_str(s); } } @@ -2340,7 +2341,7 @@ fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult { return Ok(false); }; - is_reg_exp_object(argument, context) + is_reg_exp_object(&argument, context) } fn is_reg_exp_object(argument: &JsObject, context: &mut Context) -> JsResult { // 2. Let matcher be ? Get(argument, @@match). diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 31fd9c00f99..d98e12851d6 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -37,7 +37,8 @@ impl StringIterator { } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut string_iterator = this.as_object().map(JsObject::borrow_mut); + let string_iterator = this.as_object(); + let mut string_iterator = string_iterator.as_ref().map(JsObject::borrow_mut); let string_iterator = string_iterator .as_mut() .and_then(|obj| obj.as_string_iterator_mut()) diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index e2707bbf12e..ddc3213c054 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -192,7 +192,6 @@ impl Symbol { fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { value .as_symbol() - .cloned() .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) .ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) } @@ -315,7 +314,7 @@ impl Symbol { // 4. Return undefined. let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { let registry = registry.borrow(); - registry.get_symbol(sym) + registry.get_symbol(&sym) }); Ok(symbol.map(JsValue::from).unwrap_or_default()) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 56eb4880358..f6b8130c100 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -436,7 +436,7 @@ impl TypedArray { // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[values.len().into()], context)?; + let target_obj = Self::create(&constructor, &[values.len().into()], context)?; // d. Let k be 0. // e. Repeat, while k < len, @@ -472,7 +472,7 @@ impl TypedArray { let len = array_like.length_of_array_like(context)?; // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let target_obj = Self::create(constructor, &[len.into()], context)?; + let target_obj = Self::create(&constructor, &[len.into()], context)?; // 11. Let k be 0. // 12. Repeat, while k < len, @@ -518,7 +518,7 @@ impl TypedArray { }; // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). - let new_obj = Self::create(constructor, &[args.len().into()], context)?; + let new_obj = Self::create(&constructor, &[args.len().into()], context)?; // 5. Let k be 0. // 6. Repeat, while k < len, @@ -875,7 +875,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - o.clone(), + o, PropertyNameKind::KeyAndValue, context, )) @@ -1098,7 +1098,7 @@ impl TypedArray { } // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). - let a = Self::species_create(obj, o.typed_array_name(), &[captured.into()], context)?; + let a = Self::species_create(&obj, o.typed_array_name(), &[captured.into()], context)?; // 10. Let n be 0. // 11. For each element e of kept, do @@ -1538,7 +1538,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - o.clone(), + o, PropertyNameKind::Key, context, )) @@ -1693,7 +1693,7 @@ impl TypedArray { }; // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). - let a = Self::species_create(obj, o.typed_array_name(), &[len.into()], context)?; + let a = Self::species_create(&obj, o.typed_array_name(), &[len.into()], context)?; // 6. Let k be 0. // 7. Repeat, while k < len, @@ -1979,14 +1979,14 @@ impl TypedArray { let source = args.get_or_undefined(0); match source.variant() { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsVariant::Object(source) if source.is_typed_array() => { + JsVariant::Object(ref source) if source.is_typed_array() => { // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). - Self::set_typed_array_from_typed_array(target, target_offset, source, context)?; + Self::set_typed_array_from_typed_array(&target, target_offset, source, context)?; } // 7. Else, _ => { // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). - Self::set_typed_array_from_array_like(target, target_offset, source, context)?; + Self::set_typed_array_from_array_like(&target, target_offset, source, context)?; } } @@ -2383,7 +2383,7 @@ impl TypedArray { let count = std::cmp::max(r#final - k, 0) as usize; // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). - let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?; + let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?; let a_borrow = a.borrow(); let a_array = a_borrow .as_typed_array() @@ -2737,7 +2737,7 @@ impl TypedArray { let mut sort_err = Ok(()); items.sort_by(|x, y| { if sort_err.is_ok() { - sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { + sort_compare(x, y, compare_fn.as_ref(), context).unwrap_or_else(|err| { sort_err = Err(err); Ordering::Equal }) @@ -2841,7 +2841,7 @@ impl TypedArray { // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». // 20. Return ? TypedArraySpeciesCreate(O, argumentsList). Ok(Self::species_create( - obj, + &obj, o.typed_array_name(), &[ buffer.clone().into(), @@ -2877,7 +2877,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - o.clone(), + o, PropertyNameKind::Value, context, )) diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index ed9986f010a..71f11f41cfc 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -121,7 +121,7 @@ impl ClassConstructor for T { }; let class_prototype = if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() { - object.clone() + object } else { return context.throw_type_error(format!( "invalid default prototype for native class `{}`", @@ -131,11 +131,7 @@ impl ClassConstructor for T { let prototype = this .as_object() - .cloned() - .map(|obj| { - obj.get(PROTOTYPE, context) - .map(|val| val.as_object().cloned()) - }) + .map(|obj| obj.get(PROTOTYPE, context).map(|val| val.as_object())) .transpose()? .flatten() .unwrap_or(class_prototype); diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 2cf3309bc8e..8bfd9c6cb03 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -91,7 +91,9 @@ fn bound_function_exotic_construct( // 5. If SameValue(F, newTarget) is true, set newTarget to target. let new_target = match new_target.variant() { - JsVariant::Object(new_target) if JsObject::equals(obj, new_target) => target.clone().into(), + JsVariant::Object(ref new_target) if JsObject::equals(obj, new_target) => { + target.clone().into() + } _ => new_target.clone(), }; diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index d628f6d1164..c92cbe807b8 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -961,7 +961,7 @@ where // 2. Let proto be ? Get(constructor, "prototype"). if let Some(object) = constructor.as_object() { if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() { - return Ok(proto.clone()); + return Ok(proto); } } // 3. If Type(proto) is not Object, then diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 4db7a601d23..f357799a30e 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -98,7 +98,7 @@ pub(crate) fn proxy_exotic_get_prototype_of( // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. let handler_proto = match handler_proto.variant() { - JsVariant::Object(obj) => Some(obj.clone()), + JsVariant::Object(ref obj) => Some(obj.clone()), JsVariant::Null => None, _ => return context.throw_type_error("Proxy trap result is neither object nor null"), }; @@ -826,7 +826,7 @@ pub(crate) fn proxy_exotic_own_property_keys( let mut trap_result = Vec::new(); for value in &trap_result_raw { match value.variant() { - JsVariant::String(s) => { + JsVariant::String(ref s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate string property keys", @@ -834,7 +834,7 @@ pub(crate) fn proxy_exotic_own_property_keys( } trap_result.push(s.clone().into()); } - JsVariant::Symbol(s) => { + JsVariant::Symbol(ref s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate symbol property keys", diff --git a/boa_engine/src/object/jsarray.rs b/boa_engine/src/object/jsarray.rs index 4f1d0715bc7..efaae839964 100644 --- a/boa_engine/src/object/jsarray.rs +++ b/boa_engine/src/object/jsarray.rs @@ -109,7 +109,6 @@ impl JsArray { pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult { let object = Array::concat(&self.inner.clone().into(), items, context)? .as_object() - .cloned() .expect("Array.prototype.filter should always return object"); Self::from_object(object, context) @@ -124,7 +123,6 @@ impl JsArray { ) .map(|x| { x.as_string() - .cloned() .expect("Array.prototype.join always returns string") }) } @@ -231,7 +229,6 @@ impl JsArray { context, )? .as_object() - .cloned() .expect("Array.prototype.filter should always return object"); Self::from_object(object, context) @@ -250,7 +247,6 @@ impl JsArray { context, )? .as_object() - .cloned() .expect("Array.prototype.map should always return object"); Self::from_object(object, context) @@ -316,7 +312,6 @@ impl JsArray { context, )? .as_object() - .cloned() .expect("Array.prototype.slice should always return object"); Self::from_object(object, context) diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 01177612401..2aa9e92e76f 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -503,7 +503,7 @@ impl JsObject { // 7. If IsConstructor(S) is true, return S. // 8. Throw a TypeError exception. match s.as_object() { - Some(obj) if obj.is_constructor() => Ok(obj.clone()), + Some(obj) if obj.is_constructor() => Ok(obj), _ => context.throw_type_error("property 'constructor' is not a constructor"), } } @@ -594,7 +594,7 @@ impl JsObject { // 3. If func is either undefined or null, return undefined. JsVariant::Undefined | JsVariant::Null => Ok(None), // 5. Return func. - JsVariant::Object(object) if object.is_callable() => Ok(Some(object.clone())), + JsVariant::Object(ref object) if object.is_callable() => Ok(Some(object.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. _ => { context.throw_type_error("value returned for property of object is not a function") @@ -802,7 +802,7 @@ impl JsValue { } let mut object = if let Some(obj) = object.as_object() { - obj.clone() + obj } else { // 3. If Type(O) is not Object, return false. return Ok(false); @@ -829,7 +829,7 @@ impl JsValue { }; // c. If SameValue(P, O) is true, return true. - if JsObject::equals(&object, prototype) { + if JsObject::equals(&object, &prototype) { return Ok(true); } } diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index e5e8d9dcbec..bc5d5ecca6d 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -870,11 +870,9 @@ mod in_operator { let foo_val = forward_val(&mut context, "Foo").unwrap(); assert_eq!( *bar_obj.prototype(), - foo_val.as_object().and_then(|obj| obj - .get("prototype", &mut context) - .unwrap() + foo_val .as_object() - .cloned()) + .and_then(|obj| obj.get("prototype", &mut context).unwrap().as_object()) ); } } diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index abb31537c94..7ac1ae9ba79 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -98,7 +98,7 @@ macro_rules! print_obj_value { pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { match x.variant() { // We don't want to print private (compiler) or prototype properties - JsVariant::Object(v) => { + JsVariant::Object(ref v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing match v.borrow().kind() { @@ -283,17 +283,17 @@ impl Display for ValueDisplay<'_> { JsVariant::Null => write!(f, "null"), JsVariant::Undefined => write!(f, "undefined"), JsVariant::Boolean(v) => write!(f, "{v}"), - JsVariant::Symbol(symbol) => match symbol.description() { + JsVariant::Symbol(ref symbol) => match symbol.description() { Some(description) => write!(f, "Symbol({description})"), None => write!(f, "Symbol()"), }, - JsVariant::String(v) => write!(f, "\"{v}\""), + JsVariant::String(ref v) => write!(f, "\"{v}\""), JsVariant::Rational(v) => format_rational(v, f), JsVariant::Object(_) => { write!(f, "{}", log_string_from(self.value, self.internals, true)) } JsVariant::Integer(v) => write!(f, "{v}"), - JsVariant::BigInt(num) => write!(f, "{num}n"), + JsVariant::BigInt(ref num) => write!(f, "{num}n"), } } } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index e8e5a236f21..ae90fb7b394 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -15,7 +15,7 @@ impl JsValue { match (self.variant(), other.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::equal(x, y). - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::equal(x, y), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::equal(x, y), (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::equal(x, y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::equal(x, f64::from(y)), (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::equal(f64::from(x), y), @@ -69,16 +69,20 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (JsVariant::BigInt(a), JsVariant::String(b)) => match JsBigInt::from_string(b) { - Some(ref b) => a == b, - None => false, - }, + (JsVariant::BigInt(ref a), JsVariant::String(ref b)) => { + match JsBigInt::from_string(b) { + Some(ref b) => a == b, + None => false, + } + } // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (JsVariant::String(a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) { - Some(ref a) => a == b, - None => false, - }, + (JsVariant::String(ref a), JsVariant::BigInt(ref b)) => { + match JsBigInt::from_string(a) { + Some(ref a) => a == b, + None => false, + } + } // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. (JsVariant::Boolean(x), _) => return other.equals(&Self::new(i32::from(x)), context), @@ -121,10 +125,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (JsVariant::BigInt(a), JsVariant::Rational(ref b)) => a == b, - (JsVariant::Rational(ref a), JsVariant::BigInt(b)) => a == b, - (JsVariant::BigInt(a), JsVariant::Integer(ref b)) => a == b, - (JsVariant::Integer(ref a), JsVariant::BigInt(b)) => a == b, + (JsVariant::BigInt(ref a), JsVariant::Rational(ref b)) => a == b, + (JsVariant::Rational(ref a), JsVariant::BigInt(ref b)) => a == b, + (JsVariant::BigInt(ref a), JsVariant::Integer(ref b)) => a == b, + (JsVariant::Integer(ref a), JsVariant::BigInt(ref b)) => a == b, // 13. Return false. _ => false, @@ -147,7 +151,7 @@ impl JsValue { match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValue(x, y). - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value(x, y), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value(x, y), (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::same_value(x, y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::same_value(x, f64::from(y)), (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::same_value(f64::from(x), y), @@ -175,7 +179,7 @@ impl JsValue { match (x.variant(), y.variant()) { // 2. If Type(x) is Number or BigInt, then // a. Return ! Type(x)::SameValueZero(x, y). - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value_zero(x, y), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value_zero(x, y), (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::same_value_zero(x, y), (JsVariant::Rational(x), JsVariant::Integer(y)) => { @@ -195,10 +199,10 @@ impl JsValue { debug_assert!(x.get_type() == y.get_type()); match (x.variant(), y.variant()) { (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => true, - (JsVariant::String(x), JsVariant::String(y)) => x == y, + (JsVariant::String(ref x), JsVariant::String(ref y)) => x == y, (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, - (JsVariant::Object(x), JsVariant::Object(y)) => JsObject::equals(x, y), - (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, + (JsVariant::Object(ref x), JsVariant::Object(ref y)) => JsObject::equals(x, y), + (JsVariant::Symbol(ref x), JsVariant::Symbol(ref y)) => x == y, _ => false, } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 4347c14adc0..958f1399d01 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -253,21 +253,12 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsObject`] is undefined behaviour. - pub unsafe fn as_object_unchecked(&self) -> &JsObject { + pub unsafe fn as_object_unchecked(&self) -> JsObject { // SAFETY: The safety contract must be upheld by the caller - let object = unsafe { - JsObject::from_void_ptr(self.as_pointer()); - }; - - // SAFETY: Assuming the above is safe, this is safe because - // we currently are manipulating a [`ManuallyDrop`] that is - // manually dropped until this [`JsValue`] is dropped, - // which implies that the lifetime of `&object` can be extended - // to the lifetime of `&self`. - unsafe { std::mem::transmute(&object) } + unsafe { JsObject::clone(&JsObject::from_void_ptr(self.as_pointer())) } } - pub fn as_object(&self) -> Option<&JsObject> { + pub fn as_object(&self) -> Option { if self.is_object() { return unsafe { Some(self.as_object_unchecked()) }; } @@ -288,23 +279,14 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsString`] is undefined behaviour. - pub unsafe fn as_string_unchecked(&self) -> &JsString { + pub unsafe fn as_string_unchecked(&self) -> JsString { // SAFETY: The safety contract must be upheld by the caller - let string = unsafe { - JsString::from_void_ptr(self.as_pointer()); - }; - - // SAFETY: Assuming the above is safe, this is safe because - // we currently are manipulating a [`ManuallyDrop`] that is - // manually dropped until this [`JsValue`] is dropped, - // which implies that the lifetime of `&string` can be extended - // to the lifetime of `&self`. - unsafe { std::mem::transmute(&string) } + unsafe { JsString::clone(&JsString::from_void_ptr(self.as_pointer())) } } /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn as_string(&self) -> Option<&JsString> { + pub fn as_string(&self) -> Option { if self.is_string() { return unsafe { Some(self.as_string_unchecked()) }; } @@ -323,19 +305,12 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsSymbol`] is undefined behaviour. - pub unsafe fn as_symbol_unchecked(&self) -> &JsSymbol { + pub unsafe fn as_symbol_unchecked(&self) -> JsSymbol { // SAFETY: The safety contract must be upheld by the caller - let symbol = unsafe { JsSymbol::from_void_ptr(self.as_pointer()) }; - - // SAFETY: Assuming the above is safe, this is safe because - // we currently are manipulating a [`ManuallyDrop`] that is - // manually dropped until this [`JsValue`] is dropped, - // which implies that the lifetime of `&symbol` can be extended - // to the lifetime of `&self`. - unsafe { std::mem::transmute(&symbol) } + unsafe { JsSymbol::clone(&JsSymbol::from_void_ptr(self.as_pointer())) } } - pub fn as_symbol(&self) -> Option<&JsSymbol> { + pub fn as_symbol(&self) -> Option { if self.is_symbol() { return unsafe { Some(self.as_symbol_unchecked()) }; } @@ -357,21 +332,12 @@ impl JsValue { /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsBigInt`] is undefined behaviour. #[inline] - pub unsafe fn as_bigint_unchecked(&self) -> &JsBigInt { + pub unsafe fn as_bigint_unchecked(&self) -> JsBigInt { // SAFETY: The safety contract must be upheld by the caller - let bigint = unsafe { - JsBigInt::from_void_ptr(self.as_pointer()); - }; - - // SAFETY: Assuming the above is safe, this is safe because - // we currently are manipulating a [`ManuallyDrop`] that is - // manually dropped until this [`JsValue`] is dropped, - // which implies that the lifetime of `&bigint` can be extended - // to the lifetime of `&self`. - unsafe { std::mem::transmute(&bigint) } + unsafe { JsBigInt::clone(&JsBigInt::from_void_ptr(self.as_pointer())) } } - pub fn as_bigint(&self) -> Option<&JsBigInt> { + pub fn as_bigint(&self) -> Option { if self.is_bigint() { return unsafe { Some(self.as_bigint_unchecked()) }; } @@ -379,7 +345,7 @@ impl JsValue { None } - pub fn variant(&self) -> JsVariant<'_> { + pub fn variant(&self) -> JsVariant { unsafe { match self.tag() { ValueTag::Null => JsVariant::Null, @@ -397,16 +363,16 @@ impl JsValue { } #[derive(Debug, Clone)] -pub enum JsVariant<'a> { +pub enum JsVariant { Null, Undefined, Rational(f64), Integer(i32), Boolean(bool), - String(&'a JsString), - Symbol(&'a JsSymbol), - BigInt(&'a JsBigInt), - Object(&'a JsObject), + String(JsString), + Symbol(JsSymbol), + BigInt(JsBigInt), + Object(JsObject), } impl From for JsValue { @@ -527,8 +493,24 @@ pub(crate) unsafe trait PointerType { unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); } -impl Drop for JsValue { +impl Clone for JsValue { #[inline] + fn clone(&self) -> Self { + unsafe { + match self.tag() { + ValueTag::Object => Self::new(self.as_object_unchecked()), + ValueTag::String => Self::new(self.as_string_unchecked()), + ValueTag::Symbol => Self::new(self.as_symbol_unchecked()), + ValueTag::BigInt => Self::new(self.as_bigint_unchecked()), + _ => Self { + value: Cell::new(self.value.get()), + }, + } + } + } +} + +impl Drop for JsValue { fn drop(&mut self) { unsafe { match self.tag() { @@ -550,47 +532,41 @@ impl Drop for JsValue { } } -impl Clone for JsValue { - #[inline] - fn clone(&self) -> Self { - unsafe { - match self.tag() { - ValueTag::Object => Self::new(self.as_object_unchecked().clone()), - ValueTag::String => Self::new(self.as_string_unchecked().clone()), - ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), - ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), - _ => Self { - value: Cell::new(self.value.get()), - }, - } - } - } -} - impl Finalize for JsValue {} unsafe impl Trace for JsValue { - #[inline] unsafe fn trace(&self) { if let Some(o) = self.as_object() { // SAFETY: `self.as_object()` must always return a valid `JsObject - unsafe { o.trace() } + unsafe { + o.trace(); + } } } - #[inline] unsafe fn root(&self) { - if let Some(o) = self.as_object() { - // SAFETY: `self.as_object()` must always return a valid `JsObject - unsafe { o.root() } + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.root(); + self.value + .set(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + } } } - #[inline] unsafe fn unroot(&self) { - if let Some(o) = self.as_object() { - // SAFETY: `self.as_object()` must always return a valid `JsObject - unsafe { o.unroot() } + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.unroot(); + self.value + .set(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + } } } @@ -623,7 +599,7 @@ mod tests_nan_box { let bigint = value.as_bigint().unwrap(); - assert_eq!(bigint, &JsBigInt::new(12345)); + assert_eq!(&bigint, &JsBigInt::new(12345)); } #[test] @@ -662,7 +638,7 @@ mod tests_nan_box { let string = value.as_string().unwrap(); - assert_eq!(JsString::refcount(string), Some(2)); + assert_eq!(JsString::refcount(&string), Some(2)); assert_eq!(string, "I am a string :)"); } @@ -686,7 +662,7 @@ mod tests_nan_box { assert!(value.is_object()); let o2 = value.as_object().unwrap(); - assert!(JsObject::equals(&o1, o2)); + assert!(JsObject::equals(&o1, &o2)); } #[test] @@ -808,23 +784,27 @@ impl JsValue { /// [spec]: https://tc39.es/ecma262/#sec-iscallable #[inline] pub fn is_callable(&self) -> bool { - self.as_object().map_or(false, JsObject::is_callable) + self.as_object() + .as_ref() + .map_or(false, JsObject::is_callable) } #[inline] - pub fn as_callable(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_callable()) + pub fn as_callable(&self) -> Option { + self.as_object().filter(JsObject::is_callable) } /// Returns true if the value is a constructor object #[inline] pub fn is_constructor(&self) -> bool { - self.as_object().map_or(false, JsObject::is_constructor) + self.as_object() + .as_ref() + .map_or(false, JsObject::is_constructor) } #[inline] - pub fn as_constructor(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_constructor()) + pub fn as_constructor(&self) -> Option { + self.as_object().filter(JsObject::is_constructor) } /// Returns true if the value is a 64-bit floating-point number. @@ -874,10 +854,10 @@ impl JsValue { pub fn to_boolean(&self) -> bool { match self.variant() { JsVariant::Symbol(_) | JsVariant::Object(_) => true, - JsVariant::String(s) if !s.is_empty() => true, + JsVariant::String(ref s) if !s.is_empty() => true, JsVariant::Rational(n) if n != 0.0 && !n.is_nan() => true, JsVariant::Integer(n) if n != 0 => true, - JsVariant::BigInt(n) if !n.is_zero() => true, + JsVariant::BigInt(ref n) if !n.is_zero() => true, JsVariant::Boolean(v) => v, _ => false, } @@ -980,7 +960,7 @@ impl JsValue { match self.variant() { JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"), JsVariant::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), - JsVariant::String(string) => { + JsVariant::String(ref string) => { if let Some(value) = JsBigInt::from_string(string) { Ok(value) } else { @@ -994,7 +974,7 @@ impl JsValue { JsVariant::Integer(_) | JsVariant::Rational(_) => { context.throw_type_error("cannot convert Number to a BigInt") } - JsVariant::BigInt(b) => Ok(b.clone()), + JsVariant::BigInt(ref b) => Ok(b.clone()), JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) @@ -1035,9 +1015,9 @@ impl JsValue { JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()), JsVariant::Rational(rational) => Ok(Number::to_native_string(rational).into()), JsVariant::Integer(integer) => Ok(integer.to_string().into()), - JsVariant::String(string) => Ok(string.clone()), + JsVariant::String(ref string) => Ok(string.clone()), JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"), - JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), + JsVariant::BigInt(ref bigint) => Ok(bigint.to_string().into()), JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) @@ -1076,7 +1056,7 @@ impl JsValue { ObjectData::number(rational), )) } - JsVariant::String(string) => { + JsVariant::String(ref string) => { let prototype = context.intrinsics().constructors().string().prototype(); let object = @@ -1092,14 +1072,14 @@ impl JsValue { ); Ok(object) } - JsVariant::Symbol(symbol) => { + JsVariant::Symbol(ref symbol) => { let prototype = context.intrinsics().constructors().symbol().prototype(); Ok(JsObject::from_proto_and_data( prototype, ObjectData::symbol(symbol.clone()), )) } - JsVariant::BigInt(bigint) => { + JsVariant::BigInt(ref bigint) => { let prototype = context .intrinsics() .constructors() @@ -1110,7 +1090,7 @@ impl JsValue { ObjectData::big_int(bigint.clone()), )) } - JsVariant::Object(jsobject) => Ok(jsobject.clone()), + JsVariant::Object(ref jsobject) => Ok(jsobject.clone()), } } @@ -1120,14 +1100,14 @@ impl JsValue { pub fn to_property_key(&self, context: &mut Context) -> JsResult { Ok(match self.variant() { // Fast path: - JsVariant::String(string) => string.clone().into(), - JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::String(ref string) => string.clone().into(), + JsVariant::Symbol(ref symbol) => symbol.clone().into(), // Slow path: _ => { let primitive = self.to_primitive(context, PreferredType::String)?; match primitive.variant() { - JsVariant::String(string) => string.clone().into(), - JsVariant::Symbol(symbol) => symbol.clone().into(), + JsVariant::String(ref string) => string.clone().into(), + JsVariant::Symbol(ref symbol) => symbol.clone().into(), _ => primitive.to_string(context)?.into(), } }, @@ -1140,7 +1120,7 @@ impl JsValue { pub fn to_numeric(&self, context: &mut Context) -> JsResult { let primitive = self.to_primitive(context, PreferredType::Number)?; if let Some(bigint) = primitive.as_bigint() { - return Ok(bigint.clone().into()); + return Ok(bigint.into()); } Ok(self.to_number(context)?.into()) } @@ -1450,7 +1430,7 @@ impl JsValue { JsVariant::Null => Ok(0.0), JsVariant::Undefined => Ok(f64::NAN), JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - JsVariant::String(string) => Ok(string.string_to_number()), + JsVariant::String(ref string) => Ok(string.string_to_number()), JsVariant::Rational(number) => Ok(number), JsVariant::Integer(integer) => Ok(f64::from(integer)), JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"), @@ -1523,7 +1503,7 @@ impl JsValue { JsVariant::Null => "object", JsVariant::Undefined => "undefined", JsVariant::BigInt(_) => "bigint", - JsVariant::Object(object) => { + JsVariant::Object(ref object) => { if object.is_callable() { "function" } else { diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index b3bca772469..75f1a1ad0ab 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -16,12 +16,12 @@ impl JsValue { (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x + y), (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) + y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x + f64::from(y)), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), // String concat - (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(x, y)), - (JsVariant::String(x), _) => Self::from(JsString::concat(x, other.to_string(context)?)), - (_, JsVariant::String(y)) => Self::from(JsString::concat(self.to_string(context)?, y)), + (JsVariant::String(ref x), JsVariant::String(ref y)) => Self::from(JsString::concat(x, y)), + (JsVariant::String(ref x), _) => Self::from(JsString::concat(x, other.to_string(context)?)), + (_, JsVariant::String(ref y)) => Self::from(JsString::concat(self.to_string(context)?, y)), // Slow path: (_, _) => { @@ -61,7 +61,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) - y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x - f64::from(y)), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::sub(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -87,7 +87,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) * y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x * f64::from(y)), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::mul(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -114,7 +114,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) / y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x / f64::from(y)), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } @@ -161,7 +161,7 @@ impl JsValue { (JsVariant::Rational(x), JsVariant::Integer(y)) => { Self::new((x % f64::from(y)).copysign(x)) } - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => { + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { if y.is_zero() { return context.throw_range_error("BigInt division by zero"); } @@ -197,7 +197,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x).powf(y)), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x.powi(y)), - (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { Self::new(JsBigInt::pow(a, b, context)?) } @@ -227,7 +227,9 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x & f64_to_int32(y)), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) & y), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitand(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitand(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -257,7 +259,9 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x | f64_to_int32(y)), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) | y), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitor(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitor(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -287,7 +291,9 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(x ^ f64_to_int32(y)), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(f64_to_int32(x) ^ y), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitxor(x, y)), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => { + Self::new(JsBigInt::bitxor(x, y)) + } // Slow path: (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { @@ -321,7 +327,7 @@ impl JsValue { Self::new(f64_to_int32(x).wrapping_shl(y as u32)) } - (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { Self::new(JsBigInt::shift_left(a, b, context)?) } @@ -357,7 +363,7 @@ impl JsValue { Self::new(f64_to_int32(x).wrapping_shr(y as u32)) } - (JsVariant::BigInt(a), JsVariant::BigInt(b)) => { + (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => { Self::new(JsBigInt::shift_right(a, b, context)?) } @@ -457,7 +463,7 @@ impl JsValue { Ok(num) => -num, Err(_) => f64::NAN, }), - JsVariant::String(str) => Self::new(match f64::from_str(str) { + JsVariant::String(ref str) => Self::new(match f64::from_str(str) { Ok(num) => -num, Err(_) => f64::NAN, }), @@ -466,7 +472,7 @@ impl JsValue { JsVariant::Integer(num) => Self::new(-num), JsVariant::Boolean(true) => Self::new(1), JsVariant::Boolean(false) | JsVariant::Null => Self::new(0), - JsVariant::BigInt(x) => Self::new(JsBigInt::neg(x)), + JsVariant::BigInt(ref x) => Self::new(JsBigInt::neg(x)), }) } @@ -504,7 +510,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::less_than(f64::from(x), y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::less_than(x, f64::from(y)), (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::less_than(x, y), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), + (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => (x < y).into(), // Slow path: (_, _) => { @@ -520,7 +526,7 @@ impl JsValue { }; match (px.variant(), py.variant()) { - (JsVariant::String(x), JsVariant::String(y)) => { + (JsVariant::String(ref x), JsVariant::String(ref y)) => { if x.starts_with(y.as_str()) { return Ok(AbstractRelation::False); } @@ -534,14 +540,14 @@ impl JsValue { } unreachable!() } - (JsVariant::BigInt(x), JsVariant::String(y)) => { + (JsVariant::BigInt(ref x), JsVariant::String(ref y)) => { if let Some(y) = JsBigInt::from_string(y) { (*x < y).into() } else { AbstractRelation::Undefined } } - (JsVariant::String(x), JsVariant::BigInt(y)) => { + (JsVariant::String(ref x), JsVariant::BigInt(ref y)) => { if let Some(x) = JsBigInt::from_string(x) { (x < *y).into() } else { diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 73db0b811f8..45775d6eced 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -108,11 +108,11 @@ impl JsValue { JsVariant::Null => Ok(Value::Null), JsVariant::Undefined => todo!("undefined to JSON"), JsVariant::Boolean(b) => Ok(b.into()), - JsVariant::String(string) => Ok(string.as_str().into()), + JsVariant::String(ref string) => Ok(string.as_str().into()), JsVariant::Rational(rat) => Ok(rat.into()), JsVariant::Integer(int) => Ok(int.into()), JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), - JsVariant::Object(obj) => { + JsVariant::Object(ref obj) => { if obj.is_array() { let len = obj.length_of_array_like(context)?; let mut arr = Vec::with_capacity(len); @@ -201,7 +201,7 @@ mod tests { let phones = obj.get("phones", &mut context).unwrap(); let phones = phones.as_object().unwrap(); - let arr = JsArray::from_object(phones.clone(), &mut context).unwrap(); + let arr = JsArray::from_object(phones, &mut context).unwrap(); assert_eq!(arr.at(0, &mut context).unwrap(), "+44 1234567".into()); assert_eq!(arr.at(1, &mut context).unwrap(), JsValue::from(-45_i32)); assert!(arr.at(2, &mut context).unwrap().is_object()); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 7cfd1ddbb9e..63694000eb0 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -834,7 +834,7 @@ impl JsObject { .expect("GeneratorFunction must have a prototype property") .as_object() { - prototype.clone() + prototype } else { context.intrinsics().constructors().generator().prototype() }; diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 9ea4fc7488f..45653b7d584 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -635,7 +635,7 @@ impl Context { let value = self.vm.pop(); let object = if let Some(object) = value.as_object() { - object.clone() + object } else { value.to_object(self)? }; @@ -650,7 +650,7 @@ impl Context { let object = self.vm.pop(); let key = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object.clone() + object } else { object.to_object(self)? }; @@ -666,7 +666,7 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object.clone() + object } else { object.to_object(self)? }; @@ -686,7 +686,7 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object.clone() + object } else { object.to_object(self)? }; @@ -730,7 +730,7 @@ impl Context { let key = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object.clone() + object } else { object.to_object(self)? }; @@ -748,7 +748,7 @@ impl Context { let key = self.vm.pop(); let object = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object.clone() + object } else { object.to_object(self)? }; @@ -1368,7 +1368,7 @@ impl Context { arguments.append(&mut rest_arguments); let object = match func.variant() { - JsVariant::Object(object) if object.is_callable() => object.clone(), + JsVariant::Object(ref object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; From f8259d0593e5360d409b7e97b7daeb32d5187cb6 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 4 Mar 2022 08:23:40 -0600 Subject: [PATCH 3/8] Avoid returning clones of pointer types on `JsValue` methods (#1891) Co-authored-by: HalidOdat --- .../src/builtins/array/array_iterator.rs | 2 +- boa_engine/src/builtins/array/mod.rs | 5 +- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- boa_engine/src/builtins/bigint/mod.rs | 2 + boa_engine/src/builtins/boolean/tests.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 12 +- boa_engine/src/builtins/date/mod.rs | 2 +- boa_engine/src/builtins/function/mod.rs | 8 +- boa_engine/src/builtins/function/tests.rs | 4 +- boa_engine/src/builtins/generator/mod.rs | 4 +- boa_engine/src/builtins/iterable/mod.rs | 2 +- boa_engine/src/builtins/json/mod.rs | 22 +-- boa_engine/src/builtins/map/map_iterator.rs | 2 +- boa_engine/src/builtins/map/mod.rs | 2 +- .../src/builtins/object/for_in_iterator.rs | 2 +- boa_engine/src/builtins/object/mod.rs | 15 +- boa_engine/src/builtins/proxy/mod.rs | 2 +- boa_engine/src/builtins/reflect/mod.rs | 6 +- boa_engine/src/builtins/regexp/mod.rs | 14 +- .../builtins/regexp/regexp_string_iterator.rs | 2 +- boa_engine/src/builtins/set/set_iterator.rs | 4 +- boa_engine/src/builtins/string/mod.rs | 12 +- .../src/builtins/string/string_iterator.rs | 2 +- boa_engine/src/builtins/symbol/mod.rs | 6 +- boa_engine/src/builtins/typed_array/mod.rs | 12 +- boa_engine/src/class.rs | 8 +- boa_engine/src/object/internal_methods/mod.rs | 2 +- .../src/object/internal_methods/proxy.rs | 6 +- boa_engine/src/object/jsarray.rs | 10 + boa_engine/src/object/operations.rs | 6 +- boa_engine/src/tests.rs | 7 +- boa_engine/src/value/display.rs | 8 +- boa_engine/src/value/equality.rs | 32 ++-- boa_engine/src/value/hash.rs | 8 +- boa_engine/src/value/mod.rs | 174 ++++++++++++------ boa_engine/src/value/operations.rs | 30 +-- boa_engine/src/value/serde_json.rs | 6 +- boa_engine/src/value/tests.rs | 4 +- boa_engine/src/vm/code_block.rs | 2 +- boa_engine/src/vm/mod.rs | 14 +- 40 files changed, 277 insertions(+), 188 deletions(-) diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index ba9e36fa30b..552531527b5 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -68,7 +68,7 @@ impl ArrayIterator { /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let array_iterator = this.as_object(); - let mut array_iterator = array_iterator.as_ref().map(JsObject::borrow_mut); + let mut array_iterator = array_iterator.as_deref().map(JsObject::borrow_mut); let array_iterator = array_iterator .as_mut() .and_then(|obj| obj.as_array_iterator_mut()) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 8478bf234b3..6ed7b148ba4 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -369,7 +369,8 @@ impl Array { Ok( c.construct(&[JsValue::new(length)], &c.clone().into(), context)? .as_object() - .expect("constructing an object should always return an object"), + .expect("constructing an object should always return an object") + .clone(), ) } else { context.throw_type_error("Symbol.species must be a constructor") @@ -583,6 +584,8 @@ impl Array { Some(constructor) => constructor .construct(&[len.into()], this, context)? .as_object() + .as_deref() + .cloned() .ok_or_else(|| { context.construct_type_error("object constructor didn't return an object") })?, diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index fcaef794898..8afe51372be 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -244,7 +244,7 @@ impl ArrayBuffer { let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?; // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = new.as_object().ok_or_else(|| { + let new_obj = new.as_object().as_deref().cloned().ok_or_else(|| { context.construct_type_error("ArrayBuffer constructor returned non-object value") })?; diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index 3fe1c0dfb24..9225dd5325f 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -133,6 +133,8 @@ impl BigInt { value // 1. If Type(value) is BigInt, return value. .as_bigint() + .as_deref() + .cloned() // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. // b. Return value.[[BigIntData]]. diff --git a/boa_engine/src/builtins/boolean/tests.rs b/boa_engine/src/builtins/boolean/tests.rs index 9b3547617d7..cc0e18ba0e3 100644 --- a/boa_engine/src/builtins/boolean/tests.rs +++ b/boa_engine/src/builtins/boolean/tests.rs @@ -60,6 +60,6 @@ fn instances_have_correct_proto_set() { assert_eq!( &*bool_instance.as_object().unwrap().prototype(), - &bool_prototype.as_object() + &bool_prototype.as_object().as_deref().cloned() ); } diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 59c8ae2bb63..478b9d647db 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -164,7 +164,7 @@ impl DataView { prototype, ObjectData::data_view(Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. - viewed_array_buffer: buffer_obj, + viewed_array_buffer: buffer_obj.clone(), // 12. Set O.[[ByteLength]] to viewByteLength. byte_length: view_byte_length, // 13. Set O.[[ByteOffset]] to offset. @@ -195,7 +195,7 @@ impl DataView { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let dataview = this.as_object(); - let dataview = dataview.as_ref().map(JsObject::borrow); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -225,7 +225,7 @@ impl DataView { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let dataview = this.as_object(); - let dataview = dataview.as_ref().map(JsObject::borrow); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -265,7 +265,7 @@ impl DataView { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let dataview = this.as_object(); - let dataview = dataview.as_ref().map(JsObject::borrow); + let dataview = dataview.as_deref().map(JsObject::borrow); let dataview = dataview .as_ref() .and_then(|obj| obj.as_data_view()) @@ -306,7 +306,7 @@ impl DataView { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let view = view.as_object(); - let view = view.as_ref().map(JsObject::borrow); + let view = view.as_deref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) @@ -666,7 +666,7 @@ impl DataView { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let view = view.as_object(); - let view = view.as_ref().map(JsObject::borrow); + let view = view.as_deref().map(JsObject::borrow); let view = view .as_ref() .and_then(|obj| obj.as_data_view()) diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index d7f04062b21..3feed3ef128 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -516,7 +516,7 @@ impl Date { let hint = args.get_or_undefined(0); - let try_first = match hint.as_string().as_ref().map(JsString::as_str) { + let try_first = match hint.as_string().as_deref().map(JsString::as_str) { // 3. If hint is "string" or "default", then // a. Let tryFirst be string. Some("string" | "default") => PreferredType::String, diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 1caa0cf25e1..d23f4950592 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -397,7 +397,11 @@ impl BuiltInFunctionObject { let target_name = target.get("name", context)?; // 9. If Type(targetName) is not String, set targetName to the empty String. - let target_name = target_name.as_string().unwrap_or_default(); + let target_name = target_name + .as_string() + .as_deref() + .cloned() + .unwrap_or_default(); // 10. Perform SetFunctionName(F, targetName, "bound"). set_function_name(&f, &target_name.into(), Some("bound"), context); @@ -434,7 +438,7 @@ impl BuiltInFunctionObject { #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let object = this.as_object(); - let object = object.as_ref().map(JsObject::borrow); + let object = object.as_deref().map(JsObject::borrow); let function = object .as_deref() .and_then(Object::as_function) diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 9ad573af311..eaa9d6cc5e8 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -129,7 +129,7 @@ fn function_prototype_call() { "#; let value = forward_val(&mut context, func).unwrap(); assert!(value.is_string()); - assert_eq!(value.as_string().unwrap(), "[object Error]"); + assert_eq!(*value.as_string().unwrap(), "[object Error]"); } #[test] @@ -246,7 +246,7 @@ fn closure_capture_clone() { object .__get_own_property__(&"key".into(), context)? .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string()) + .and_then(|val| val.as_string().as_deref().cloned()) .ok_or_else(|| context.construct_type_error("invalid `key` property"))?, ); Ok(hw.into()) diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 472ee5442b7..9fdd045cc9c 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -148,8 +148,8 @@ impl Generator { ) -> JsResult { // 1. Return ? GeneratorResume(this value, value, empty). match this.as_object() { - Some(obj) if obj.is_generator() => { - Self::generator_resume(&obj, args.get_or_undefined(0), context) + Some(ref obj) if obj.is_generator() => { + Self::generator_resume(obj, args.get_or_undefined(0), context) } _ => context.throw_type_error("Generator.prototype.next called on non generator"), } diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 1c2208c00ca..2a1390cefc6 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -292,7 +292,7 @@ impl IteratorRecord { // 3. If Type(result) is not Object, throw a TypeError exception. // 4. Return result. if let Some(o) = result.as_object() { - Ok(IteratorResult { object: o }) + Ok(IteratorResult { object: o.clone() }) } else { context.throw_type_error("next value should be an object") } diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 195337d8eab..172a1a99f33 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -95,7 +95,7 @@ impl Json { let unfiltered = context.eval(script_string.as_bytes())?; // 11. If IsCallable(reviver) is true, then - if let Some(obj) = args.get_or_undefined(1).as_callable() { + if let Some(ref obj) = args.get_or_undefined(1).as_callable() { // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). let root = context.construct_object(); @@ -105,7 +105,7 @@ impl Json { .expect("CreateDataPropertyOrThrow should never throw here"); // d. Return ? InternalizeJSONProperty(root, rootName, reviver). - Self::internalize_json_property(&root, "".into(), &obj, context) + Self::internalize_json_property(&root, "".into(), obj, context) } else { // 12. Else, // a. Return unfiltered. @@ -129,7 +129,7 @@ impl Json { let val = holder.get(name.clone(), context)?; // 2. If Type(val) is Object, then - if let Some(obj) = val.as_object() { + if let Some(ref obj) = val.as_object() { // a. Let isArray be ? IsArray(val). // b. If isArray is true, then if obj.is_array_abstract(context)? { @@ -141,7 +141,7 @@ impl Json { // 1. Let prop be ! ToString(𝔽(I)). // 2. Let newElement be ? InternalizeJSONProperty(val, prop, reviver). let new_element = Self::internalize_json_property( - &obj, + obj, i.to_string().into(), reviver, context, @@ -173,7 +173,7 @@ impl Json { // 1. Let newElement be ? InternalizeJSONProperty(val, P, reviver). let new_element = - Self::internalize_json_property(&obj, p.clone(), reviver, context)?; + Self::internalize_json_property(obj, p.clone(), reviver, context)?; // 2. If newElement is undefined, then if new_element.is_undefined() { @@ -388,7 +388,7 @@ impl Json { } // 4. If Type(value) is Object, then - if let Some(obj) = value.as_object() { + if let Some(obj) = value.as_object().as_deref().cloned() { // a. If value has a [[NumberData]] internal slot, then if obj.is_number() { // i. Set value to ? ToNumber(value). @@ -427,8 +427,8 @@ impl Json { } // 8. If Type(value) is String, return QuoteJSONString(value). - if let Some(s) = value.as_string() { - return Ok(Some(Self::quote_json_string(&s))); + if let Some(ref s) = value.as_string() { + return Ok(Some(Self::quote_json_string(s))); } // 9. If Type(value) is Number, then @@ -452,15 +452,15 @@ impl Json { } // 11. If Type(value) is Object and IsCallable(value) is false, then - if let Some(obj) = value.as_object() { + if let Some(ref obj) = value.as_object() { if !obj.is_callable() { // a. Let isArray be ? IsArray(value). // b. If isArray is true, return ? SerializeJSONArray(state, value). // c. Return ? SerializeJSONObject(state, value). return if obj.is_array_abstract(context)? { - Ok(Some(Self::serialize_json_array(state, &obj, context)?)) + Ok(Some(Self::serialize_json_array(state, obj, context)?)) } else { - Ok(Some(Self::serialize_json_object(state, &obj, context)?)) + Ok(Some(Self::serialize_json_object(state, obj, context)?)) }; } } diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index c0ebf14d240..05176e932e2 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -72,7 +72,7 @@ impl MapIterator { /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let map_iterator = this.as_object(); - let mut map_iterator = map_iterator.as_ref().map(JsObject::borrow_mut); + let mut map_iterator = map_iterator.as_deref().map(JsObject::borrow_mut); let map_iterator = map_iterator .as_mut() .and_then(|obj| obj.as_map_iterator_mut()) diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 8e29e718760..cf06e73209c 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -431,7 +431,7 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). let map = this .as_object() - .filter(JsObject::is_map) + .filter(|obj| obj.is_map()) .ok_or_else(|| context.construct_type_error("`this` is not a Map"))?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 4e2175dbdfd..4b1d366a615 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -68,7 +68,7 @@ impl ForInIterator { /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let iterator = this.as_object(); - let mut iterator = iterator.as_ref().map(JsObject::borrow_mut); + let mut iterator = iterator.as_deref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_for_in_iterator_mut()) diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index c7a77a20c03..a9944b8df0b 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -130,9 +130,10 @@ impl Object { let properties = args.get_or_undefined(1); let obj = match prototype.variant() { - JsVariant::Object(_) | JsVariant::Null => { - JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary()) - } + JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data( + prototype.as_object().as_deref().cloned(), + ObjectData::ordinary(), + ), _ => { return context.throw_type_error(format!( "Object prototype may only be an Object or null: {}", @@ -343,7 +344,7 @@ impl Object { let proto = args.get_or_undefined(1); let proto = match proto.variant() { - JsVariant::Object(ref obj) => Some(obj.clone()), + JsVariant::Object(obj) => Some(obj.clone()), JsVariant::Null => None, // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. _ => { @@ -446,9 +447,9 @@ impl Object { context: &mut Context, ) -> JsResult { let arg = args.get_or_undefined(0); - if let Some(obj) = arg.as_object() { + if let Some(ref obj) = arg.as_object() { let props = args.get_or_undefined(1); - object_define_properties(&obj, props, context)?; + object_define_properties(obj, props, context)?; Ok(arg.clone()) } else { context.throw_type_error("Expected an object") @@ -525,7 +526,7 @@ impl Object { // 16. If Type(tag) is not String, set tag to builtinTag. let tag_str = tag.as_string(); - let tag_str = tag_str.as_ref().map_or(builtin_tag, JsString::as_str); + let tag_str = tag_str.as_deref().map_or(builtin_tag, JsString::as_str); // 17. Return the string-concatenation of "[object ", tag, and "]". Ok(format!("[object {tag_str}]").into()) diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 5891549cad1..e709300af62 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -113,7 +113,7 @@ impl Proxy { let p = JsObject::from_proto_and_data( context.intrinsics().constructors().object().prototype(), ObjectData::proxy( - Self::new(target.clone(), handler), + Self::new(target.clone(), handler.clone()), target.is_callable(), target.is_constructor(), ), diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 948865eae07..fee7bd354e3 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -116,7 +116,7 @@ impl Reflect { let new_target = if let Some(new_target) = args.get(2) { if new_target .as_object() - .as_ref() + .as_deref() .map(JsObject::is_constructor) != Some(true) { @@ -151,7 +151,7 @@ impl Reflect { let key = args.get_or_undefined(1).to_property_key(context)?; let prop_desc: JsValue = args .get(2) - .and_then(JsValue::as_object) + .and_then(|v| v.as_object().as_deref().cloned()) .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .into(); @@ -388,7 +388,7 @@ impl Reflect { .and_then(JsValue::as_object) .ok_or_else(|| context.construct_type_error("target must be an object"))?; let proto = match args.get_or_undefined(1).variant() { - JsVariant::Object(ref obj) => Some(obj.clone()), + JsVariant::Object(obj) => Some(obj.clone()), JsVariant::Null => None, _ => return context.throw_type_error("proto must be an object or null"), }; diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 9cfc3f63a30..c52399cdde7 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -178,7 +178,7 @@ impl RegExp { let flags = args.get_or_undefined(1); // 1. Let patternIsRegExp be ? IsRegExp(pattern). - let pattern_is_regexp = pattern.as_object().filter(JsObject::is_regexp); + let pattern_is_regexp = pattern.as_object().filter(|obj| obj.is_regexp()); // 2. If NewTarget is undefined, then // 3. Else, let newTarget be NewTarget. @@ -191,7 +191,7 @@ impl RegExp { let pattern_constructor = pattern.get("constructor", context)?; // ii. If SameValue(newTarget, patternConstructor) is true, return pattern. if JsValue::same_value(new_target, &pattern_constructor) { - return Ok(pattern.clone().into()); + return Ok((&**pattern).clone().into()); } } } @@ -358,7 +358,7 @@ impl RegExp { #[inline] fn regexp_has_flag(this: &JsValue, flag: u8, context: &mut Context) -> JsResult { - if let Some(object) = this.as_object() { + if let Some(ref object) = this.as_object() { if let Some(regexp) = object.borrow().as_regexp() { return Ok(JsValue::new(match flag { b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES), @@ -373,7 +373,7 @@ impl RegExp { } if JsObject::equals( - &object, + object, &context.intrinsics().constructors().regexp().prototype, ) { return Ok(JsValue::undefined()); @@ -732,7 +732,7 @@ impl RegExp { // 2. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). let obj = this .as_object() - .filter(JsObject::is_regexp) + .filter(|obj| obj.is_regexp()) .ok_or_else(|| { context.construct_type_error("RegExp.prototype.exec called with invalid value") })?; @@ -776,7 +776,7 @@ impl RegExp { } // c. Return result. - return Ok(result.as_object()); + return Ok(result.as_object().as_deref().cloned()); } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). @@ -1257,7 +1257,7 @@ impl RegExp { let mut replace_value = args.get_or_undefined(1).clone(); let functional_replace = replace_value .as_object() - .as_ref() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 6467356a0a9..49c065b8b6e 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -82,7 +82,7 @@ impl RegExpStringIterator { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let iterator = this.as_object(); - let mut iterator = iterator.as_ref().map(JsObject::borrow_mut); + let mut iterator = iterator.as_deref().map(JsObject::borrow_mut); let iterator = iterator .as_mut() .and_then(|obj| obj.as_regexp_string_iterator_mut()) diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 7bea6722cca..21d476abb0a 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -67,7 +67,7 @@ impl SetIterator { /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let set_iterator = this.as_object(); - let mut set_iterator = set_iterator.as_ref().map(JsObject::borrow_mut); + let mut set_iterator = set_iterator.as_deref().map(JsObject::borrow_mut); let set_iterator = set_iterator .as_mut() @@ -87,7 +87,7 @@ impl SetIterator { } let entries = m.as_object(); - let entries = entries.as_ref().map(JsObject::borrow); + let entries = entries.as_deref().map(JsObject::borrow); let entries = entries .as_ref() .and_then(|obj| obj.as_set_ref()) diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index f78e6fe1f5e..0fcbac4c7c2 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -248,6 +248,8 @@ impl String { fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult { // 1. If Type(value) is String, return value. this.as_string() + .as_deref() + .cloned() // 2. If Type(value) is Object and value has a [[StringData]] internal slot, then // a. Let s be value.[[StringData]]. // b. Assert: Type(s) is String. @@ -1000,7 +1002,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() - .as_ref() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1094,9 +1096,9 @@ impl String { // 2. If searchValue is neither undefined nor null, then if !search_value.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(searchValue). - if let Some(obj) = search_value.as_object() { + if let Some(ref obj) = search_value.as_object() { // b. If isRegExp is true, then - if is_reg_exp_object(&obj, context)? { + if is_reg_exp_object(obj, context)? { // i. Let flags be ? Get(searchValue, "flags"). let flags = obj.get("flags", context)?; @@ -1131,7 +1133,7 @@ impl String { // 5. Let functionalReplace be IsCallable(replaceValue). let functional_replace = replace_value .as_object() - .as_ref() + .as_deref() .map(JsObject::is_callable) .unwrap_or_default(); @@ -1976,7 +1978,7 @@ impl String { if !regexp.is_null_or_undefined() { // a. Let isRegExp be ? IsRegExp(regexp). // b. If isRegExp is true, then - if let Some(regexp_obj) = regexp.as_object().filter(JsObject::is_regexp) { + if let Some(regexp_obj) = regexp.as_object().filter(|obj| obj.is_regexp()) { // i. Let flags be ? Get(regexp, "flags"). let flags = regexp_obj.get("flags", context)?; diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index d98e12851d6..3ddd6cb6a9f 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -38,7 +38,7 @@ impl StringIterator { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let string_iterator = this.as_object(); - let mut string_iterator = string_iterator.as_ref().map(JsObject::borrow_mut); + let mut string_iterator = string_iterator.as_deref().map(JsObject::borrow_mut); let string_iterator = string_iterator .as_mut() .and_then(|obj| obj.as_string_iterator_mut()) diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index ddc3213c054..f1a85f4760c 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -192,6 +192,8 @@ impl Symbol { fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult { value .as_symbol() + .as_deref() + .cloned() .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) .ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) } @@ -307,14 +309,14 @@ impl Symbol { ) -> JsResult { let sym = args.get_or_undefined(0); // 1. If Type(sym) is not Symbol, throw a TypeError exception. - if let Some(sym) = sym.as_symbol() { + if let Some(ref sym) = sym.as_symbol() { // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym. // 4. Return undefined. let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { let registry = registry.borrow(); - registry.get_symbol(&sym) + registry.get_symbol(sym) }); Ok(symbol.map(JsValue::from).unwrap_or_default()) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index f6b8130c100..9299b00b47b 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -875,7 +875,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key+value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::KeyAndValue, context, )) @@ -1538,7 +1538,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, key). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Key, context, )) @@ -1979,9 +1979,9 @@ impl TypedArray { let source = args.get_or_undefined(0); match source.variant() { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsVariant::Object(ref source) if source.is_typed_array() => { + JsVariant::Object(source) if source.is_typed_array() => { // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). - Self::set_typed_array_from_typed_array(&target, target_offset, source, context)?; + Self::set_typed_array_from_typed_array(&target, target_offset, &source, context)?; } // 7. Else, _ => { @@ -2737,7 +2737,7 @@ impl TypedArray { let mut sort_err = Ok(()); items.sort_by(|x, y| { if sort_err.is_ok() { - sort_compare(x, y, compare_fn.as_ref(), context).unwrap_or_else(|err| { + sort_compare(x, y, compare_fn.as_deref(), context).unwrap_or_else(|err| { sort_err = Err(err); Ordering::Equal }) @@ -2877,7 +2877,7 @@ impl TypedArray { // 3. Return CreateArrayIterator(O, value). Ok(ArrayIterator::create_array_iterator( - o, + o.clone(), PropertyNameKind::Value, context, )) diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 71f11f41cfc..5c2c59b397b 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -121,7 +121,7 @@ impl ClassConstructor for T { }; let class_prototype = if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() { - object + object.clone() } else { return context.throw_type_error(format!( "invalid default prototype for native class `{}`", @@ -131,7 +131,11 @@ impl ClassConstructor for T { let prototype = this .as_object() - .map(|obj| obj.get(PROTOTYPE, context).map(|val| val.as_object())) + .as_deref() + .map(|obj| { + obj.get(PROTOTYPE, context) + .map(|val| val.as_object().as_deref().cloned()) + }) .transpose()? .flatten() .unwrap_or(class_prototype); diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index c92cbe807b8..d628f6d1164 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -961,7 +961,7 @@ where // 2. Let proto be ? Get(constructor, "prototype"). if let Some(object) = constructor.as_object() { if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() { - return Ok(proto); + return Ok(proto.clone()); } } // 3. If Type(proto) is not Object, then diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index f357799a30e..4db7a601d23 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -98,7 +98,7 @@ pub(crate) fn proxy_exotic_get_prototype_of( // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. let handler_proto = match handler_proto.variant() { - JsVariant::Object(ref obj) => Some(obj.clone()), + JsVariant::Object(obj) => Some(obj.clone()), JsVariant::Null => None, _ => return context.throw_type_error("Proxy trap result is neither object nor null"), }; @@ -826,7 +826,7 @@ pub(crate) fn proxy_exotic_own_property_keys( let mut trap_result = Vec::new(); for value in &trap_result_raw { match value.variant() { - JsVariant::String(ref s) => { + JsVariant::String(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate string property keys", @@ -834,7 +834,7 @@ pub(crate) fn proxy_exotic_own_property_keys( } trap_result.push(s.clone().into()); } - JsVariant::Symbol(ref s) => { + JsVariant::Symbol(s) => { if !unchecked_result_keys.insert(s.clone().into()) { return context.throw_type_error( "Proxy trap result contains duplicate symbol property keys", diff --git a/boa_engine/src/object/jsarray.rs b/boa_engine/src/object/jsarray.rs index efaae839964..fd5a2d544e5 100644 --- a/boa_engine/src/object/jsarray.rs +++ b/boa_engine/src/object/jsarray.rs @@ -109,6 +109,8 @@ impl JsArray { pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult { let object = Array::concat(&self.inner.clone().into(), items, context)? .as_object() + .as_deref() + .cloned() .expect("Array.prototype.filter should always return object"); Self::from_object(object, context) @@ -123,6 +125,8 @@ impl JsArray { ) .map(|x| { x.as_string() + .as_deref() + .cloned() .expect("Array.prototype.join always returns string") }) } @@ -229,6 +233,8 @@ impl JsArray { context, )? .as_object() + .as_deref() + .cloned() .expect("Array.prototype.filter should always return object"); Self::from_object(object, context) @@ -247,6 +253,8 @@ impl JsArray { context, )? .as_object() + .as_deref() + .cloned() .expect("Array.prototype.map should always return object"); Self::from_object(object, context) @@ -312,6 +320,8 @@ impl JsArray { context, )? .as_object() + .as_deref() + .cloned() .expect("Array.prototype.slice should always return object"); Self::from_object(object, context) diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 2aa9e92e76f..1b2fd013940 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -503,7 +503,7 @@ impl JsObject { // 7. If IsConstructor(S) is true, return S. // 8. Throw a TypeError exception. match s.as_object() { - Some(obj) if obj.is_constructor() => Ok(obj), + Some(obj) if obj.is_constructor() => Ok(obj.clone()), _ => context.throw_type_error("property 'constructor' is not a constructor"), } } @@ -594,7 +594,7 @@ impl JsObject { // 3. If func is either undefined or null, return undefined. JsVariant::Undefined | JsVariant::Null => Ok(None), // 5. Return func. - JsVariant::Object(ref object) if object.is_callable() => Ok(Some(object.clone())), + JsVariant::Object(object) if object.is_callable() => Ok(Some(object.clone())), // 4. If IsCallable(func) is false, throw a TypeError exception. _ => { context.throw_type_error("value returned for property of object is not a function") @@ -802,7 +802,7 @@ impl JsValue { } let mut object = if let Some(obj) = object.as_object() { - obj + obj.clone() } else { // 3. If Type(O) is not Object, return false. return Ok(false); diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index bc5d5ecca6d..389bd4b1568 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -870,9 +870,12 @@ mod in_operator { let foo_val = forward_val(&mut context, "Foo").unwrap(); assert_eq!( *bar_obj.prototype(), - foo_val + foo_val.as_object().and_then(|obj| obj + .get("prototype", &mut context) + .unwrap() .as_object() - .and_then(|obj| obj.get("prototype", &mut context).unwrap().as_object()) + .as_deref() + .cloned()) ); } } diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 7ac1ae9ba79..5344c973289 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -98,7 +98,7 @@ macro_rules! print_obj_value { pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { match x.variant() { // We don't want to print private (compiler) or prototype properties - JsVariant::Object(ref v) => { + JsVariant::Object(v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing match v.borrow().kind() { @@ -283,17 +283,17 @@ impl Display for ValueDisplay<'_> { JsVariant::Null => write!(f, "null"), JsVariant::Undefined => write!(f, "undefined"), JsVariant::Boolean(v) => write!(f, "{v}"), - JsVariant::Symbol(ref symbol) => match symbol.description() { + JsVariant::Symbol(symbol) => match symbol.description() { Some(description) => write!(f, "Symbol({description})"), None => write!(f, "Symbol()"), }, - JsVariant::String(ref v) => write!(f, "\"{v}\""), + JsVariant::String(v) => write!(f, "\"{}\"", *v), JsVariant::Rational(v) => format_rational(v, f), JsVariant::Object(_) => { write!(f, "{}", log_string_from(self.value, self.internals, true)) } JsVariant::Integer(v) => write!(f, "{v}"), - JsVariant::BigInt(ref num) => write!(f, "{num}n"), + JsVariant::BigInt(num) => write!(f, "{}n", *num), } } } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index ae90fb7b394..1ea11f848d2 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -69,20 +69,16 @@ impl JsValue { // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. - (JsVariant::BigInt(ref a), JsVariant::String(ref b)) => { - match JsBigInt::from_string(b) { - Some(ref b) => a == b, - None => false, - } - } + (JsVariant::BigInt(a), JsVariant::String(ref b)) => match JsBigInt::from_string(b) { + Some(b) => a == b, + None => false, + }, // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. - (JsVariant::String(ref a), JsVariant::BigInt(ref b)) => { - match JsBigInt::from_string(a) { - Some(ref a) => a == b, - None => false, - } - } + (JsVariant::String(ref a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) { + Some(a) => a == *b, + None => false, + }, // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. (JsVariant::Boolean(x), _) => return other.equals(&Self::new(i32::from(x)), context), @@ -125,10 +121,10 @@ impl JsValue { // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // a. If x or y are any of NaN, +∞, or -∞, return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. - (JsVariant::BigInt(ref a), JsVariant::Rational(ref b)) => a == b, - (JsVariant::Rational(ref a), JsVariant::BigInt(ref b)) => a == b, - (JsVariant::BigInt(ref a), JsVariant::Integer(ref b)) => a == b, - (JsVariant::Integer(ref a), JsVariant::BigInt(ref b)) => a == b, + (JsVariant::BigInt(a), JsVariant::Rational(b)) => *a == b, + (JsVariant::Rational(a), JsVariant::BigInt(b)) => a == *b, + (JsVariant::BigInt(a), JsVariant::Integer(b)) => *a == b, + (JsVariant::Integer(a), JsVariant::BigInt(b)) => a == *b, // 13. Return false. _ => false, @@ -199,10 +195,10 @@ impl JsValue { debug_assert!(x.get_type() == y.get_type()); match (x.variant(), y.variant()) { (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => true, - (JsVariant::String(ref x), JsVariant::String(ref y)) => x == y, + (JsVariant::String(x), JsVariant::String(y)) => x == y, (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, (JsVariant::Object(ref x), JsVariant::Object(ref y)) => JsObject::equals(x, y), - (JsVariant::Symbol(ref x), JsVariant::Symbol(ref y)) => x == y, + (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y, _ => false, } } diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 50b2e67eb9a..0d17fd5d8b1 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -40,13 +40,13 @@ impl Hash for JsValue { match self.variant() { JsVariant::Undefined => UndefinedHashable.hash(state), JsVariant::Null => NullHashable.hash(state), - JsVariant::String(ref string) => string.hash(state), + JsVariant::String(string) => string.hash(state), JsVariant::Boolean(boolean) => boolean.hash(state), JsVariant::Integer(integer) => RationalHashable(f64::from(integer)).hash(state), - JsVariant::BigInt(ref bigint) => bigint.hash(state), + JsVariant::BigInt(bigint) => bigint.hash(state), JsVariant::Rational(rational) => RationalHashable(rational).hash(state), - JsVariant::Symbol(ref symbol) => Hash::hash(symbol, state), - JsVariant::Object(ref object) => std::ptr::hash(object.as_ref(), state), + JsVariant::Symbol(symbol) => Hash::hash(symbol.as_ref(), state), + JsVariant::Object(object) => std::ptr::hash(object.as_ref(), state), } } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 958f1399d01..3399accda47 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -26,6 +26,7 @@ use std::{ cell::Cell, collections::HashSet, fmt::{self, Display}, + marker::PhantomData, mem::ManuallyDrop, ops::Sub, str::FromStr, @@ -253,12 +254,11 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsObject`] is undefined behaviour. - pub unsafe fn as_object_unchecked(&self) -> JsObject { - // SAFETY: The safety contract must be upheld by the caller - unsafe { JsObject::clone(&JsObject::from_void_ptr(self.as_pointer())) } + pub unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> { + unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) } } - pub fn as_object(&self) -> Option { + pub fn as_object(&self) -> Option> { if self.is_object() { return unsafe { Some(self.as_object_unchecked()) }; } @@ -279,14 +279,13 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsString`] is undefined behaviour. - pub unsafe fn as_string_unchecked(&self) -> JsString { - // SAFETY: The safety contract must be upheld by the caller - unsafe { JsString::clone(&JsString::from_void_ptr(self.as_pointer())) } + pub unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> { + unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) } } /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn as_string(&self) -> Option { + pub fn as_string(&self) -> Option> { if self.is_string() { return unsafe { Some(self.as_string_unchecked()) }; } @@ -305,12 +304,11 @@ impl JsValue { /// /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsSymbol`] is undefined behaviour. - pub unsafe fn as_symbol_unchecked(&self) -> JsSymbol { - // SAFETY: The safety contract must be upheld by the caller - unsafe { JsSymbol::clone(&JsSymbol::from_void_ptr(self.as_pointer())) } + pub unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> { + unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) } } - pub fn as_symbol(&self) -> Option { + pub fn as_symbol(&self) -> Option> { if self.is_symbol() { return unsafe { Some(self.as_symbol_unchecked()) }; } @@ -332,12 +330,12 @@ impl JsValue { /// Calling this method with a [`JsValue`] that doesn't box /// a [`JsBigInt`] is undefined behaviour. #[inline] - pub unsafe fn as_bigint_unchecked(&self) -> JsBigInt { + pub unsafe fn as_bigint_unchecked(&self) -> Ref<'_, JsBigInt> { // SAFETY: The safety contract must be upheld by the caller - unsafe { JsBigInt::clone(&JsBigInt::from_void_ptr(self.as_pointer())) } + unsafe { Ref::new(JsBigInt::from_void_ptr(self.as_pointer())) } } - pub fn as_bigint(&self) -> Option { + pub fn as_bigint(&self) -> Option> { if self.is_bigint() { return unsafe { Some(self.as_bigint_unchecked()) }; } @@ -345,7 +343,7 @@ impl JsValue { None } - pub fn variant(&self) -> JsVariant { + pub fn variant(&self) -> JsVariant<'_> { unsafe { match self.tag() { ValueTag::Null => JsVariant::Null, @@ -362,17 +360,17 @@ impl JsValue { } } -#[derive(Debug, Clone)] -pub enum JsVariant { +#[derive(Debug)] +pub enum JsVariant<'a> { Null, Undefined, Rational(f64), Integer(i32), Boolean(bool), - String(JsString), - Symbol(JsSymbol), - BigInt(JsBigInt), - Object(JsObject), + String(Ref<'a, JsString>), + Symbol(Ref<'a, JsSymbol>), + BigInt(Ref<'a, JsBigInt>), + Object(Ref<'a, JsObject>), } impl From for JsValue { @@ -493,24 +491,64 @@ pub(crate) unsafe trait PointerType { unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); } -impl Clone for JsValue { +/// Represents a reference to a boxed pointer type inside a [`JsValue`] +/// +/// This is exclusively used to return references to [`JsString`], [`JsObject`], +/// [`JsSymbol`] and [`JsBigInt`], since `NaN` boxing makes returning proper references +/// difficult. It is mainly returned by the [`JsValue::variant`] method and the +/// `as_` methods for checked casts to pointer types. +/// +/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion +/// to a proper [`reference`] by using the `ref` keyword or the +/// [`Option::as_deref`][`std::option::Option::as_deref`] method. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ref<'a, T> { + inner: ManuallyDrop, + _marker: PhantomData<&'a T>, +} + +impl Ref<'_, T> { #[inline] - fn clone(&self) -> Self { - unsafe { - match self.tag() { - ValueTag::Object => Self::new(self.as_object_unchecked()), - ValueTag::String => Self::new(self.as_string_unchecked()), - ValueTag::Symbol => Self::new(self.as_symbol_unchecked()), - ValueTag::BigInt => Self::new(self.as_bigint_unchecked()), - _ => Self { - value: Cell::new(self.value.get()), - }, - } + fn new(inner: ManuallyDrop) -> Self { + Self { + inner, + _marker: PhantomData, } } } +impl std::borrow::Borrow for Ref<'_, T> { + #[inline] + fn borrow(&self) -> &T { + &**self + } +} + +impl AsRef for Ref<'_, T> { + #[inline] + fn as_ref(&self) -> &T { + &**self + } +} + +impl std::ops::Deref for Ref<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl PartialEq for Ref<'_, T> { + #[inline] + fn eq(&self, other: &T) -> bool { + &**self == other + } +} + impl Drop for JsValue { + #[inline] fn drop(&mut self) { unsafe { match self.tag() { @@ -532,6 +570,23 @@ impl Drop for JsValue { } } +impl Clone for JsValue { + #[inline] + fn clone(&self) -> Self { + unsafe { + match self.tag() { + ValueTag::Object => Self::new(self.as_object_unchecked().clone()), + ValueTag::String => Self::new(self.as_string_unchecked().clone()), + ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), + ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), + _ => Self { + value: Cell::new(self.value.get()), + }, + } + } + } +} + impl Finalize for JsValue {} unsafe impl Trace for JsValue { @@ -638,9 +693,9 @@ mod tests_nan_box { let string = value.as_string().unwrap(); - assert_eq!(JsString::refcount(&string), Some(2)); + assert_eq!(JsString::refcount(&string), Some(1)); - assert_eq!(string, "I am a string :)"); + assert_eq!(*string, "I am a string :)"); } #[test] @@ -785,26 +840,26 @@ impl JsValue { #[inline] pub fn is_callable(&self) -> bool { self.as_object() - .as_ref() + .as_deref() .map_or(false, JsObject::is_callable) } #[inline] - pub fn as_callable(&self) -> Option { - self.as_object().filter(JsObject::is_callable) + pub fn as_callable(&self) -> Option> { + self.as_object().filter(|obj| obj.is_callable()) } /// Returns true if the value is a constructor object #[inline] pub fn is_constructor(&self) -> bool { self.as_object() - .as_ref() + .as_deref() .map_or(false, JsObject::is_constructor) } #[inline] - pub fn as_constructor(&self) -> Option { - self.as_object().filter(JsObject::is_constructor) + pub fn as_constructor(&self) -> Option> { + self.as_object().filter(|obj| obj.is_constructor()) } /// Returns true if the value is a 64-bit floating-point number. @@ -854,10 +909,10 @@ impl JsValue { pub fn to_boolean(&self) -> bool { match self.variant() { JsVariant::Symbol(_) | JsVariant::Object(_) => true, - JsVariant::String(ref s) if !s.is_empty() => true, + JsVariant::String(s) if !s.is_empty() => true, JsVariant::Rational(n) if n != 0.0 && !n.is_nan() => true, JsVariant::Integer(n) if n != 0 => true, - JsVariant::BigInt(ref n) if !n.is_zero() => true, + JsVariant::BigInt(n) if !n.is_zero() => true, JsVariant::Boolean(v) => v, _ => false, } @@ -960,7 +1015,8 @@ impl JsValue { match self.variant() { JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"), JsVariant::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), - JsVariant::String(ref string) => { + JsVariant::String(string) => { + let string = &*string; if let Some(value) = JsBigInt::from_string(string) { Ok(value) } else { @@ -974,7 +1030,7 @@ impl JsValue { JsVariant::Integer(_) | JsVariant::Rational(_) => { context.throw_type_error("cannot convert Number to a BigInt") } - JsVariant::BigInt(ref b) => Ok(b.clone()), + JsVariant::BigInt(b) => Ok(b.clone()), JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::Number)?; primitive.to_bigint(context) @@ -1015,9 +1071,9 @@ impl JsValue { JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()), JsVariant::Rational(rational) => Ok(Number::to_native_string(rational).into()), JsVariant::Integer(integer) => Ok(integer.to_string().into()), - JsVariant::String(ref string) => Ok(string.clone()), + JsVariant::String(string) => Ok(string.clone()), JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"), - JsVariant::BigInt(ref bigint) => Ok(bigint.to_string().into()), + JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()), JsVariant::Object(_) => { let primitive = self.to_primitive(context, PreferredType::String)?; primitive.to_string(context) @@ -1056,7 +1112,7 @@ impl JsValue { ObjectData::number(rational), )) } - JsVariant::String(ref string) => { + JsVariant::String(string) => { let prototype = context.intrinsics().constructors().string().prototype(); let object = @@ -1072,14 +1128,14 @@ impl JsValue { ); Ok(object) } - JsVariant::Symbol(ref symbol) => { + JsVariant::Symbol(symbol) => { let prototype = context.intrinsics().constructors().symbol().prototype(); Ok(JsObject::from_proto_and_data( prototype, ObjectData::symbol(symbol.clone()), )) } - JsVariant::BigInt(ref bigint) => { + JsVariant::BigInt(bigint) => { let prototype = context .intrinsics() .constructors() @@ -1090,7 +1146,7 @@ impl JsValue { ObjectData::big_int(bigint.clone()), )) } - JsVariant::Object(ref jsobject) => Ok(jsobject.clone()), + JsVariant::Object(jsobject) => Ok(jsobject.clone()), } } @@ -1100,14 +1156,14 @@ impl JsValue { pub fn to_property_key(&self, context: &mut Context) -> JsResult { Ok(match self.variant() { // Fast path: - JsVariant::String(ref string) => string.clone().into(), - JsVariant::Symbol(ref symbol) => symbol.clone().into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), // Slow path: _ => { let primitive = self.to_primitive(context, PreferredType::String)?; match primitive.variant() { - JsVariant::String(ref string) => string.clone().into(), - JsVariant::Symbol(ref symbol) => symbol.clone().into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), _ => primitive.to_string(context)?.into(), } }, @@ -1120,7 +1176,7 @@ impl JsValue { pub fn to_numeric(&self, context: &mut Context) -> JsResult { let primitive = self.to_primitive(context, PreferredType::Number)?; if let Some(bigint) = primitive.as_bigint() { - return Ok(bigint.into()); + return Ok(bigint.clone().into()); } Ok(self.to_number(context)?.into()) } @@ -1430,7 +1486,7 @@ impl JsValue { JsVariant::Null => Ok(0.0), JsVariant::Undefined => Ok(f64::NAN), JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - JsVariant::String(ref string) => Ok(string.string_to_number()), + JsVariant::String(string) => Ok(string.string_to_number()), JsVariant::Rational(number) => Ok(number), JsVariant::Integer(integer) => Ok(f64::from(integer)), JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"), @@ -1503,7 +1559,7 @@ impl JsValue { JsVariant::Null => "object", JsVariant::Undefined => "undefined", JsVariant::BigInt(_) => "bigint", - JsVariant::Object(ref object) => { + JsVariant::Object(object) => { if object.is_callable() { "function" } else { diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index 75f1a1ad0ab..45bb7cbd6dd 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -16,23 +16,29 @@ impl JsValue { (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x + y), (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) + y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x + f64::from(y)), - (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), + + (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)), + (JsVariant::String(x), _) => { + Self::new(JsString::concat(&*x, other.to_string(context)?)) + } + (_, JsVariant::String(y)) => Self::new(JsString::concat(self.to_string(context)?, &*y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)), // String concat - (JsVariant::String(ref x), JsVariant::String(ref y)) => Self::from(JsString::concat(x, y)), - (JsVariant::String(ref x), _) => Self::from(JsString::concat(x, other.to_string(context)?)), - (_, JsVariant::String(ref y)) => Self::from(JsString::concat(self.to_string(context)?, y)), + (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)), + (JsVariant::String(x), _) => Self::from(JsString::concat(&*x, other.to_string(context)?)), + (_, JsVariant::String(y)) => Self::from(JsString::concat(self.to_string(context)?, &*y)), // Slow path: (_, _) => { let x = self.to_primitive(context, PreferredType::Default)?; let y = other.to_primitive(context, PreferredType::Default)?; match (x.variant(), y.variant()) { - (JsVariant::String(ref x), _) => { - Self::from(JsString::concat(x, y.to_string(context)?)) + (JsVariant::String(x), _) => { + Self::from(JsString::concat(&*x, y.to_string(context)?)) } - (_, JsVariant::String(ref y)) => { - Self::from(JsString::concat(x.to_string(context)?, y)) + (_, JsVariant::String(y)) => { + Self::from(JsString::concat(x.to_string(context)?, &*y)) } (_, _) => match (x.to_numeric(context)?, y.to_numeric(context)?) { (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y), @@ -510,7 +516,7 @@ impl JsValue { (JsVariant::Integer(x), JsVariant::Rational(y)) => Number::less_than(f64::from(x), y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Number::less_than(x, f64::from(y)), (JsVariant::Rational(x), JsVariant::Rational(y)) => Number::less_than(x, y), - (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => (x < y).into(), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(), // Slow path: (_, _) => { @@ -526,7 +532,7 @@ impl JsValue { }; match (px.variant(), py.variant()) { - (JsVariant::String(ref x), JsVariant::String(ref y)) => { + (JsVariant::String(x), JsVariant::String(y)) => { if x.starts_with(y.as_str()) { return Ok(AbstractRelation::False); } @@ -540,14 +546,14 @@ impl JsValue { } unreachable!() } - (JsVariant::BigInt(ref x), JsVariant::String(ref y)) => { + (JsVariant::BigInt(x), JsVariant::String(ref y)) => { if let Some(y) = JsBigInt::from_string(y) { (*x < y).into() } else { AbstractRelation::Undefined } } - (JsVariant::String(ref x), JsVariant::BigInt(ref y)) => { + (JsVariant::String(ref x), JsVariant::BigInt(y)) => { if let Some(x) = JsBigInt::from_string(x) { (x < *y).into() } else { diff --git a/boa_engine/src/value/serde_json.rs b/boa_engine/src/value/serde_json.rs index 45775d6eced..73db0b811f8 100644 --- a/boa_engine/src/value/serde_json.rs +++ b/boa_engine/src/value/serde_json.rs @@ -108,11 +108,11 @@ impl JsValue { JsVariant::Null => Ok(Value::Null), JsVariant::Undefined => todo!("undefined to JSON"), JsVariant::Boolean(b) => Ok(b.into()), - JsVariant::String(ref string) => Ok(string.as_str().into()), + JsVariant::String(string) => Ok(string.as_str().into()), JsVariant::Rational(rat) => Ok(rat.into()), JsVariant::Integer(int) => Ok(int.into()), JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), - JsVariant::Object(ref obj) => { + JsVariant::Object(obj) => { if obj.is_array() { let len = obj.length_of_array_like(context)?; let mut arr = Vec::with_capacity(len); @@ -201,7 +201,7 @@ mod tests { let phones = obj.get("phones", &mut context).unwrap(); let phones = phones.as_object().unwrap(); - let arr = JsArray::from_object(phones, &mut context).unwrap(); + let arr = JsArray::from_object(phones.clone(), &mut context).unwrap(); assert_eq!(arr.at(0, &mut context).unwrap(), "+44 1234567".into()); assert_eq!(arr.at(1, &mut context).unwrap(), JsValue::from(-45_i32)); assert!(arr.at(2, &mut context).unwrap().is_object()); diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index 1b880c40cff..68e86dfd65c 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -643,7 +643,7 @@ mod cyclic_conversions { let value = forward_val(&mut context, src).unwrap(); let result = value.as_string().unwrap(); - assert_eq!(result, "[[],[]]",); + assert_eq!(*result, "[[],[]]",); } // These tests don't throw errors. Instead we mirror Chrome / Firefox behavior for these @@ -660,7 +660,7 @@ mod cyclic_conversions { let value = forward_val(&mut context, src).unwrap(); let result = value.as_string().unwrap(); - assert_eq!(result, ""); + assert_eq!(*result, ""); } #[test] diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 63694000eb0..7cfd1ddbb9e 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -834,7 +834,7 @@ impl JsObject { .expect("GeneratorFunction must have a prototype property") .as_object() { - prototype + prototype.clone() } else { context.intrinsics().constructors().generator().prototype() }; diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 45653b7d584..9ea4fc7488f 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -635,7 +635,7 @@ impl Context { let value = self.vm.pop(); let object = if let Some(object) = value.as_object() { - object + object.clone() } else { value.to_object(self)? }; @@ -650,7 +650,7 @@ impl Context { let object = self.vm.pop(); let key = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -666,7 +666,7 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -686,7 +686,7 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -730,7 +730,7 @@ impl Context { let key = self.vm.pop(); let value = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -748,7 +748,7 @@ impl Context { let key = self.vm.pop(); let object = self.vm.pop(); let object = if let Some(object) = object.as_object() { - object + object.clone() } else { object.to_object(self)? }; @@ -1368,7 +1368,7 @@ impl Context { arguments.append(&mut rest_arguments); let object = match func.variant() { - JsVariant::Object(ref object) if object.is_callable() => object.clone(), + JsVariant::Object(object) if object.is_callable() => object.clone(), _ => return self.throw_type_error("not a callable function"), }; From ba02ded5e146906343600a79b8cbc568e35f244d Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 7 Mar 2022 13:55:49 -0600 Subject: [PATCH 4/8] Fix object hash --- boa_cli/src/main.rs | 4 ++-- boa_engine/src/value/hash.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 93eae402576..41b983b6029 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -93,8 +93,8 @@ struct Opt { #[clap(long, short = 'a', value_name = "FORMAT", ignore_case = true, arg_enum)] dump_ast: Option>, - /// Dump the AST to stdout with the given format. - #[clap(long = "trace", short = 't')] + /// Dump the compiled bytecode and trace the execution stack + #[clap(long = "trace", short = "t")] trace: bool, /// Use vi mode in the REPL diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 0d17fd5d8b1..0f27cbdb57a 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -46,7 +46,7 @@ impl Hash for JsValue { JsVariant::BigInt(bigint) => bigint.hash(state), JsVariant::Rational(rational) => RationalHashable(rational).hash(state), JsVariant::Symbol(symbol) => Hash::hash(symbol.as_ref(), state), - JsVariant::Object(object) => std::ptr::hash(object.as_ref(), state), + JsVariant::Object(object) => std::ptr::hash((*object).as_ref(), state), } } } From cbaaa82e83ccb1484cea63c644b2b44150248a51 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 7 Mar 2022 17:46:20 -0600 Subject: [PATCH 5/8] Lift `Ref` over `AsRef` --- boa_engine/src/value/hash.rs | 4 ++-- boa_engine/src/value/mod.rs | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/boa_engine/src/value/hash.rs b/boa_engine/src/value/hash.rs index 0f27cbdb57a..87a0d96172c 100644 --- a/boa_engine/src/value/hash.rs +++ b/boa_engine/src/value/hash.rs @@ -45,8 +45,8 @@ impl Hash for JsValue { JsVariant::Integer(integer) => RationalHashable(f64::from(integer)).hash(state), JsVariant::BigInt(bigint) => bigint.hash(state), JsVariant::Rational(rational) => RationalHashable(rational).hash(state), - JsVariant::Symbol(symbol) => Hash::hash(symbol.as_ref(), state), - JsVariant::Object(object) => std::ptr::hash((*object).as_ref(), state), + JsVariant::Symbol(symbol) => Hash::hash(&*symbol, state), + JsVariant::Object(object) => std::ptr::hash(object.as_ref(), state), } } } diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 3399accda47..c5996066107 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -524,10 +524,15 @@ impl std::borrow::Borrow for Ref<'_, T> { } } -impl AsRef for Ref<'_, T> { +// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the +// `as_ref` implementations of `T`. +impl AsRef for Ref<'_, T> +where + T: AsRef, +{ #[inline] - fn as_ref(&self) -> &T { - &**self + fn as_ref(&self) -> &U { + >::as_ref(&*self) } } From 6353a7705c0e25842e35a72421243660c5b3c2e0 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 20 May 2022 05:07:22 -0500 Subject: [PATCH 6/8] Preserve provenance of `JsValue` --- Cargo.lock | 7 ++ boa_cli/src/main.rs | 2 +- boa_engine/Cargo.toml | 1 + boa_engine/src/builtins/array/mod.rs | 73 +++++++++------ boa_engine/src/builtins/console/mod.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 104 ++++++++++----------- boa_engine/src/builtins/error/aggregate.rs | 4 +- boa_engine/src/builtins/error/eval.rs | 2 +- boa_engine/src/builtins/error/mod.rs | 2 +- boa_engine/src/builtins/error/range.rs | 2 +- boa_engine/src/builtins/error/reference.rs | 2 +- boa_engine/src/builtins/error/syntax.rs | 2 +- boa_engine/src/builtins/error/type.rs | 2 +- boa_engine/src/builtins/error/uri.rs | 2 +- boa_engine/src/builtins/eval/mod.rs | 2 +- boa_engine/src/builtins/function/mod.rs | 8 +- boa_engine/src/builtins/generator/mod.rs | 2 +- boa_engine/src/builtins/map/mod.rs | 6 +- boa_engine/src/builtins/mod.rs | 11 +-- boa_engine/src/builtins/object/mod.rs | 13 +-- boa_engine/src/builtins/proxy/mod.rs | 4 +- boa_engine/src/builtins/reflect/mod.rs | 2 +- boa_engine/src/builtins/set/mod.rs | 4 +- boa_engine/src/builtins/string/mod.rs | 32 +++---- boa_engine/src/builtins/typed_array/mod.rs | 55 ++++++----- boa_engine/src/object/jstypedarray.rs | 83 +++++++++------- boa_engine/src/string.rs | 1 - boa_engine/src/value/conversions.rs | 2 - boa_engine/src/value/mod.rs | 99 ++++++++++++-------- boa_engine/src/value/operations.rs | 8 +- boa_tester/src/exec/js262.rs | 2 +- 31 files changed, 297 insertions(+), 244 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 107b9a15a8f..c0fa6c540db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,7 @@ dependencies = [ "ryu-js", "serde", "serde_json", + "sptr", "tap", "unicode-normalization", ] @@ -1549,6 +1550,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sptr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4365121b91e8522958da77ce29c004333daeaf0711225c4727c8c5f49941" + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 41b983b6029..bc89b06501d 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -94,7 +94,7 @@ struct Opt { dump_ast: Option>, /// Dump the compiled bytecode and trace the execution stack - #[clap(long = "trace", short = "t")] + #[clap(long = "trace", short = 't')] trace: bool, /// Use vi mode in the REPL diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index d38fe08e15b..23ff832f53b 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -42,6 +42,7 @@ dyn-clone = "1.0.5" once_cell = "1.10.0" tap = "1.0.1" icu = "0.5.0" +sptr = "0.3.1" [dev-dependencies] criterion = "0.3.5" diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 6ed7b148ba4..88f2c64385f 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -423,6 +423,7 @@ impl Array { Some(constructor) => constructor .construct(&[], this, context)? .as_object() + .as_deref() .cloned() .ok_or_else(|| { context.construct_type_error("Object constructor didn't return an object") @@ -458,9 +459,9 @@ impl Array { let next_value = next.value(context)?; // vi. If mapping is true, then - let mapped_value = if let Some(mapfn) = mapping { + let mapped_value = if let Some(ref mapfn) = mapping { // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). - let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); + let mapped_value = mapfn.call(&this_arg, &[next_value, k.into()], context); // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). if_abrupt_close_iterator!(mapped_value, iterator_record, context) @@ -502,6 +503,7 @@ impl Array { Some(constructor) => constructor .construct(&[len.into()], this, context)? .as_object() + .as_deref() .cloned() .ok_or_else(|| { context.construct_type_error("Object constructor didn't return an object") @@ -518,10 +520,10 @@ impl Array { // b. Let kValue be ? Get(arrayLike, Pk). let k_value = array_like.get(k, context)?; - let mapped_value = if let Some(mapfn) = mapping { + let mapped_value = if let Some(ref mapfn) = mapping { // c. If mapping is true, then // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - mapfn.call(this_arg, &[k_value, k.into()], context)? + mapfn.call(&this_arg, &[k_value, k.into()], context)? } else { // d. Else, let mappedValue be kValue. k_value @@ -826,7 +828,8 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.forEach: invalid callback function") })?; // 4. Let k be 0. @@ -842,7 +845,7 @@ impl Array { let k_value = o.get(pk, context)?; // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let this_arg = args.get_or_undefined(1); - callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; + callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?; } // d. Set k to k + 1. } @@ -1167,7 +1170,8 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.every: callback is not callable") })?; @@ -1185,7 +1189,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is false, return false. if !test_result { @@ -1219,7 +1223,8 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.map: Callbackfn is not callable") })?; @@ -1240,7 +1245,7 @@ impl Array { let k_value = o.get(k, context)?; // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = - callback.call(this_arg, &[k_value, k.into(), this.into()], context)?; + callback.call(&this_arg, &[k_value, k.into(), this.into()], context)?; // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). a.create_data_property_or_throw(k, mapped_value, context)?; } @@ -1392,7 +1397,7 @@ impl Array { let element_k = o.get(k, context)?; // ii. Let same be IsStrictlyEqual(searchElement, elementK). // iii. If same is true, return 𝔽(k). - if JsValue::strict_equals(search_element, &element_k) { + if JsValue::strict_equals(&search_element, &element_k) { return Ok(JsValue::new(k)); } } @@ -1427,7 +1432,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.find: predicate is not callable") })?; @@ -1444,7 +1450,7 @@ impl Array { // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate .call( - this_arg, + &this_arg, &[k_value.clone(), k.into(), o.clone().into()], context, )? @@ -1484,7 +1490,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findIndex: predicate is not callable") })?; @@ -1500,7 +1507,7 @@ impl Array { let k_value = o.get(pk, context)?; // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // d. If testResult is true, return 𝔽(k). if test_result { @@ -1535,7 +1542,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findLast: predicate is not callable") })?; @@ -1550,7 +1558,7 @@ impl Array { // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate .call( - this_arg, + &this_arg, &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1587,7 +1595,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let predicate = args.get_or_undefined(0); + let predicate = predicate.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.findLastIndex: predicate is not callable") })?; @@ -1601,7 +1610,7 @@ impl Array { let k_value = o.get(k, context)?; // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). let test_result = predicate - .call(this_arg, &[k_value, k.into(), this.clone()], context)? + .call(&this_arg, &[k_value, k.into(), this.clone()], context)? .to_boolean(); // d. If testResult is true, return 𝔽(k). if test_result { @@ -1692,7 +1701,8 @@ impl Array { let source_len = o.length_of_array_like(context)?; // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. - let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let mapper_function = args.get_or_undefined(0); + let mapper_function = mapper_function.as_callable().ok_or_else(|| { context.construct_type_error("flatMap mapper function is not callable") })?; @@ -1707,7 +1717,7 @@ impl Array { 0, 1, Some(&mapper_function), - args.get_or_undefined(1), + &args.get_or_undefined(1), context, )?; @@ -1940,7 +1950,7 @@ impl Array { // a. Let elementK be ? Get(O, ! ToString(𝔽(k))). let element_k = o.get(k, context)?; // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(search_element, &element_k) { + if JsValue::same_value_zero(&search_element, &element_k) { return Ok(JsValue::new(true)); } // c. Set k to k + 1. @@ -2223,7 +2233,8 @@ impl Array { let length = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.filter: `callback` must be callable") })?; let this_arg = args.get_or_undefined(1); @@ -2246,7 +2257,7 @@ impl Array { let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - let selected = callback.call(this_arg, &args, context)?.to_boolean(); + let selected = callback.call(&this_arg, &args, context)?.to_boolean(); // iii. If selected is true, then if selected { @@ -2287,7 +2298,8 @@ impl Array { // 2. Let len be ? LengthOfArrayLike(O). let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error("Array.prototype.some: callback is not callable") })?; @@ -2304,7 +2316,7 @@ impl Array { // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let this_arg = args.get_or_undefined(1); let test_result = callback - .call(this_arg, &[k_value, k.into(), o.clone().into()], context)? + .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)? .to_boolean(); // iii. If testResult is true, return true. if test_result { @@ -2335,7 +2347,8 @@ impl Array { context: &mut Context, ) -> JsResult { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - let comparefn = match args.get_or_undefined(0).variant() { + let comparefn = args.get_or_undefined(0); + let comparefn = match comparefn.variant() { JsVariant::Object(obj) if obj.is_callable() => Some(obj), JsVariant::Undefined => None, _ => { @@ -2472,7 +2485,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context .construct_type_error("Array.prototype.reduce: callback function is not callable") })?; @@ -2567,7 +2581,8 @@ impl Array { let len = o.length_of_array_like(context)?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { + let callback = args.get_or_undefined(0); + let callback = callback.as_callable().ok_or_else(|| { context.construct_type_error( "Array.prototype.reduceRight: callback function is not callable", ) diff --git a/boa_engine/src/builtins/console/mod.rs b/boa_engine/src/builtins/console/mod.rs index 2aef07cb3e2..5d1de4aa515 100644 --- a/boa_engine/src/builtins/console/mod.rs +++ b/boa_engine/src/builtins/console/mod.rs @@ -571,7 +571,7 @@ impl Console { #[allow(clippy::unnecessary_wraps)] pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { logger( - LogMessage::Info(display_obj(args.get_or_undefined(0), true)), + LogMessage::Info(display_obj(&args.get_or_undefined(0), true)), context.console(), ); Ok(JsValue::undefined()) diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 478b9d647db..c8181b02ee8 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -101,8 +101,8 @@ impl DataView { let byte_length = args.get_or_undefined(2); let buffer_obj = args - .get_or_undefined(0) - .as_object() + .get_or_undefined(0); + let buffer_obj = buffer_obj.as_object() .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; // 1. If NewTarget is undefined, throw a TypeError exception. @@ -377,8 +377,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigInt64, context, ) @@ -406,8 +406,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigUint64, context, ) @@ -435,8 +435,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float32, context, ) @@ -464,8 +464,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float64, context, ) @@ -493,8 +493,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int8, context, ) @@ -522,8 +522,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int16, context, ) @@ -551,8 +551,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int32, context, ) @@ -580,8 +580,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint8, context, ) @@ -609,8 +609,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint16, context, ) @@ -638,8 +638,8 @@ impl DataView { // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). Self::get_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint32, context, ) @@ -747,10 +747,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigInt64, - value, + &value, context, ) } @@ -778,10 +778,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::BigUint64, - value, + &value, context, ) } @@ -809,10 +809,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float32, - value, + &value, context, ) } @@ -840,10 +840,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Float64, - value, + &value, context, ) } @@ -871,10 +871,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int8, - value, + &value, context, ) } @@ -902,10 +902,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int16, - value, + &value, context, ) } @@ -933,10 +933,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Int32, - value, + &value, context, ) } @@ -964,10 +964,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint8, - value, + &value, context, ) } @@ -995,10 +995,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint16, - value, + &value, context, ) } @@ -1026,10 +1026,10 @@ impl DataView { // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value). Self::set_view_value( this, - byte_offset, - is_little_endian, + &byte_offset, + &is_little_endian, TypedArrayKind::Uint32, - value, + &value, context, ) } diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 5c384177c1d..42ccb98c94f 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -86,11 +86,11 @@ impl AggregateError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(2), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(2), context)?; // 5. Let errorsList be ? IterableToList(errors). let errors = args.get_or_undefined(0); - let errors_list = iterable_to_list(context, errors, None)?; + let errors_list = iterable_to_list(context, &errors, None)?; // 6. Perform ! DefinePropertyOrThrow(O, "errors", // PropertyDescriptor { // [[Configurable]]: true, diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index 4cade0f1856..f86f3c5f785 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -83,7 +83,7 @@ impl EvalError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index c68d1854446..30312aa9a55 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -120,7 +120,7 @@ impl Error { } // 4. Perform ? InstallErrorCause(O, options). - Self::install_error_cause(&o, args.get_or_undefined(1), context)?; + Self::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index 1fa1258b0a8..675fffe34d2 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -81,7 +81,7 @@ impl RangeError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index e130678b955..8353d23cb69 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -87,7 +87,7 @@ impl ReferenceError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index d0d2a33bcda..d0bd5c98cc6 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -86,7 +86,7 @@ impl SyntaxError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 0d1faad759c..d6eb51e01f5 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -87,7 +87,7 @@ impl TypeError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index f00866d2a7b..3982f542699 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -82,7 +82,7 @@ impl UriError { } // 4. Perform ? InstallErrorCause(O, options). - Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + Error::install_error_cause(&o, &args.get_or_undefined(1), context)?; // 5. Return O. Ok(o.into()) diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index f52108f5335..04ca184a561 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -52,7 +52,7 @@ impl Eval { /// [spec]: https://tc39.es/ecma262/#sec-eval-x fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { // 1. Return ? PerformEval(x, false, false). - Self::perform_eval(args.get_or_undefined(0), false, false, context) + Self::perform_eval(&args.get_or_undefined(0), false, false, context) } /// `19.2.1.1 PerformEval ( x, strictCaller, direct )` diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index d23f4950592..1c53518069c 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -312,7 +312,7 @@ impl BuiltInFunctionObject { // TODO?: 3.a. PrepareForTailCall // b. Return ? Call(func, thisArg). - return func.call(this_arg, &[], context); + return func.call(&this_arg, &[], context); } // 4. Let argList be ? CreateListFromArrayLike(argArray). @@ -322,7 +322,7 @@ impl BuiltInFunctionObject { // TODO?: 5. PrepareForTailCall // 6. Return ? Call(func, thisArg, argList). - func.call(this_arg, &arg_list, context) + func.call(&this_arg, &arg_list, context) } /// `Function.prototype.bind ( thisArg, ...args )` @@ -432,7 +432,7 @@ impl BuiltInFunctionObject { // TODO?: 3. Perform PrepareForTailCall // 4. Return ? Call(func, thisArg, args). - func.call(this_arg, args.get(1..).unwrap_or(&[]), context) + func.call(&this_arg, args.get(1..).unwrap_or(&[]), context) } #[allow(clippy::wrong_self_convention)] @@ -488,7 +488,7 @@ impl BuiltInFunctionObject { fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let F be the this value. // 2. Return ? OrdinaryHasInstance(F, V). - Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) + Ok(JsValue::ordinary_has_instance(this, &args.get_or_undefined(0), context)?.into()) } #[allow(clippy::unnecessary_wraps)] diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 9fdd045cc9c..c3f3280ae45 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -149,7 +149,7 @@ impl Generator { // 1. Return ? GeneratorResume(this value, value, empty). match this.as_object() { Some(ref obj) if obj.is_generator() => { - Self::generator_resume(obj, args.get_or_undefined(0), context) + Self::generator_resume(obj, &args.get_or_undefined(0), context) } _ => context.throw_type_error("Generator.prototype.next called on non generator"), } diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index cf06e73209c..1d5877ab394 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -144,7 +144,7 @@ impl Map { let adder = map.get("set", context)?; // 6. Return ? AddEntriesFromIterable(map, iterable, adder). - add_entries_from_iterable(&map, iterable, &adder, context) + add_entries_from_iterable(&map, &iterable, &adder, context) } /// `get Map [ @@species ]` @@ -306,7 +306,7 @@ impl Map { // ii. Set p.[[Value]] to empty. // iii. Return true. // 5. Return false. - return Ok(map.remove(key).is_some().into()); + return Ok(map.remove(&key).is_some().into()); } } context.throw_type_error("'this' is not a Map") @@ -476,7 +476,7 @@ impl Map { // a. If e.[[Key]] is not empty, then if let Some(arguments) = arguments { // i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »). - callback.call(this_arg, &arguments, context)?; + callback.call(&this_arg, &arguments, context)?; } index += 1; diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 7a54e32566f..cff50f47169 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -196,16 +196,11 @@ pub trait JsArgs { /// Call this if you are thinking of calling something similar to /// `args.get(n).cloned().unwrap_or_default()` or /// `args.get(n).unwrap_or(&undefined)`. - /// - /// This returns a reference for efficiency, in case - /// you only need to call methods of `JsValue`, so - /// try to minimize calling `clone`. - fn get_or_undefined(&self, index: usize) -> &JsValue; + fn get_or_undefined(&self, index: usize) -> JsValue; } impl JsArgs for [JsValue] { - fn get_or_undefined(&self, index: usize) -> &JsValue { - static UNDEFINED: JsValue = JsValue::undefined(); - self.get(index).unwrap_or(&UNDEFINED) + fn get_or_undefined(&self, index: usize) -> JsValue { + self.get(index).cloned().unwrap_or(JsValue::undefined()) } } diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index a9944b8df0b..7cacdeca2da 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -143,7 +143,7 @@ impl Object { }; if !properties.is_undefined() { - object_define_properties(&obj, properties, context)?; + object_define_properties(&obj, &properties, context)?; return Ok(obj.into()); } @@ -293,7 +293,7 @@ impl Object { let x = args.get_or_undefined(0); let y = args.get_or_undefined(1); - Ok(JsValue::same_value(x, y).into()) + Ok(JsValue::same_value(&x, &y).into()) } /// Get the `prototype` of an object. @@ -449,7 +449,7 @@ impl Object { let arg = args.get_or_undefined(0); if let Some(ref obj) = arg.as_object() { let props = args.get_or_undefined(1); - object_define_properties(obj, props, context)?; + object_define_properties(obj, &props, context)?; Ok(arg.clone()) } else { context.throw_type_error("Expected an object") @@ -900,7 +900,7 @@ impl Object { ) -> JsResult { // 1. Return ? GetOwnPropertyKeys(O, string). let o = args.get_or_undefined(0); - get_own_property_keys(o, PropertyKeyType::String, context) + get_own_property_keys(&o, PropertyKeyType::String, context) } /// `Object.getOwnPropertySymbols( object )` @@ -918,7 +918,7 @@ impl Object { ) -> JsResult { // 1. Return ? GetOwnPropertyKeys(O, symbol). let o = args.get_or_undefined(0); - get_own_property_keys(o, PropertyKeyType::Symbol, context) + get_own_property_keys(&o, PropertyKeyType::Symbol, context) } /// `Object.hasOwn( object, property )` @@ -950,7 +950,8 @@ impl Object { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Perform ? RequireObjectCoercible(iterable). - let iterable = args.get_or_undefined(0).require_object_coercible(context)?; + let iterable = args.get_or_undefined(0); + let iterable = iterable.require_object_coercible(context)?; // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 3. Assert: obj is an extensible ordinary object with no own properties. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index e709300af62..5739c209041 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -82,7 +82,7 @@ impl Proxy { } // 2. Return ? ProxyCreate(target, handler). - Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context) + Self::create(&args.get_or_undefined(0), &args.get_or_undefined(1), context) } // `10.5.14 ProxyCreate ( target, handler )` @@ -131,7 +131,7 @@ impl Proxy { /// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let p be ? ProxyCreate(target, handler). - let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?; + let p = Self::create(&args.get_or_undefined(0),& args.get_or_undefined(1), context)?; // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 4. Set revoker.[[RevocableProxy]] to p. diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index fee7bd354e3..2a020805bc2 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -87,7 +87,7 @@ impl Reflect { return context.throw_type_error("target must be a function"); } let args = args_list.create_list_from_array_like(&[], context)?; - target.call(this_arg, &args, context) + target.call(&this_arg, &args, context) } /// Calls a target function as a constructor with arguments. diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 53c5501805a..4ef710fdf34 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -257,7 +257,7 @@ impl Set { let res = if let Some(object) = this.as_object() { if let Some(set) = object.borrow_mut().as_set_mut() { - set.delete(value) + set.delete(&value) } else { return context.throw_type_error("'this' is not a Set"); } @@ -374,7 +374,7 @@ impl Set { .and_then(|obj| { obj.borrow() .as_set_ref() - .map(|set| set.contains(value).into()) + .map(|set| set.contains(&value).into()) }) .ok_or_else(|| context.construct_type_error("'this' is not a Set")) } diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 0fcbac4c7c2..4ef80a55121 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -785,7 +785,7 @@ impl String { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. - if is_reg_exp(search_string, context)? { + if is_reg_exp(&search_string, context)? { context.throw_type_error( "First argument to String.prototype.startsWith must not be a regular expression", )?; @@ -857,7 +857,7 @@ impl String { let search_str = match args.get_or_undefined(0) { // 3. Let isRegExp be ? IsRegExp(searchString). // 4. If isRegExp is true, throw a TypeError exception. - search_string if is_reg_exp(search_string, context)? => { + search_string if is_reg_exp(&search_string, context)? => { return context.throw_type_error( "First argument to String.prototype.endsWith must not be a regular expression", ); @@ -926,7 +926,7 @@ impl String { let search_str = match args.get_or_undefined(0) { // 3. Let isRegExp be ? IsRegExp(searchString). - search_string if is_reg_exp(search_string, context)? => { + search_string if is_reg_exp(&search_string, context)? => { return context.throw_type_error( // 4. If isRegExp is true, throw a TypeError exception. "First argument to String.prototype.includes must not be a regular expression", @@ -986,7 +986,7 @@ impl String { if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). return replacer.call( - search_value, + &search_value, &[this.clone(), replace_value.clone()], context, ); @@ -1031,7 +1031,7 @@ impl String { // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). context .call( - replace_value, + &replace_value, &JsValue::undefined(), &[search_str.into(), position.into(), this_str.clone().into()], )? @@ -1120,7 +1120,7 @@ impl String { // d. If replacer is not undefined, then if let Some(replacer) = replacer { // i. Return ? Call(replacer, searchValue, « O, replaceValue »). - return replacer.call(search_value, &[o.into(), replace_value.clone()], context); + return replacer.call(&search_value, &[o.into(), replace_value.clone()], context); } } @@ -1205,7 +1205,7 @@ impl String { // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). context .call( - replace_value, + &replace_value, &JsValue::undefined(), &[ search_string.clone().into(), @@ -1381,7 +1381,7 @@ impl String { // b. If matcher is not undefined, then if let Some(matcher) = matcher { // i. Return ? Call(matcher, regexp, « O »). - return matcher.call(regexp, &[o.clone()], context); + return matcher.call(®exp, &[o.clone()], context); } } @@ -1389,7 +1389,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; + let rx = RegExp::create(®exp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@match, « S »). rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) @@ -1494,7 +1494,7 @@ impl String { let fill_string = args.get_or_undefined(1); // 2. Return ? StringPad(O, maxLength, fillString, end). - Self::string_pad(this, max_length, fill_string, Placement::End, context) + Self::string_pad(this, &max_length, &fill_string, Placement::End, context) } /// `String.prototype.padStart( targetLength [, padString] )` @@ -1521,7 +1521,7 @@ impl String { let fill_string = args.get_or_undefined(1); // 2. Return ? StringPad(O, maxLength, fillString, start). - Self::string_pad(this, max_length, fill_string, Placement::Start, context) + Self::string_pad(this, &max_length,& fill_string, Placement::Start, context) } /// String.prototype.trim() @@ -1811,7 +1811,7 @@ impl String { // b. If splitter is not undefined, then if let Some(splitter) = splitter { // i. Return ? Call(splitter, separator, « O, limit »). - return splitter.call(separator, &[this.clone(), limit.clone()], context); + return splitter.call(&separator, &[this.clone(), limit.clone()], context); } } @@ -1996,7 +1996,7 @@ impl String { let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; // d. If matcher is not undefined, then if let Some(matcher) = matcher { - return matcher.call(regexp, &[o.clone()], context); + return matcher.call(®exp, &[o.clone()], context); } } @@ -2004,7 +2004,7 @@ impl String { let s = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, "g"). - let rx = RegExp::create(regexp, &JsValue::new("g"), context)?; + let rx = RegExp::create(®exp, &JsValue::new("g"), context)?; // 5. Return ? Invoke(rx, @@matchAll, « S »). rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context) @@ -2085,7 +2085,7 @@ impl String { // b. If searcher is not undefined, then if let Some(searcher) = searcher { // i. Return ? Call(searcher, regexp, « O »). - return searcher.call(regexp, &[o.clone()], context); + return searcher.call(®exp, &[o.clone()], context); } } @@ -2093,7 +2093,7 @@ impl String { let string = o.to_string(context)?; // 4. Let rx be ? RegExpCreate(regexp, undefined). - let rx = RegExp::create(regexp, &JsValue::undefined(), context)?; + let rx = RegExp::create(®exp, &JsValue::undefined(), context)?; // 5. Return ? Invoke(rx, @@search, « string »). rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 9299b00b47b..22d3b6fde56 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -176,8 +176,8 @@ macro_rules! typed_array { TypedArray::initialize_from_array_buffer( &o, first_argument.clone(), - byte_offset, - length, + &byte_offset, + &length, context, )?; } else { @@ -432,7 +432,7 @@ impl TypedArray { // 6. If usingIterator is not undefined, then if let Some(using_iterator) = using_iterator { // a. Let values be ? IterableToList(source, usingIterator). - let values = iterable_to_list(context, source, Some(using_iterator.into()))?; + let values = iterable_to_list(context, &source, Some(using_iterator.into()))?; // b. Let len be the number of elements in values. // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). @@ -446,7 +446,7 @@ impl TypedArray { // iii. If mapping is true, then let mapped_value = if let Some(map_fn) = &mapping { // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? + map_fn.call(&this_arg, &[k_value.clone(), k.into()], context)? } // iv. Else, let mappedValue be kValue. else { @@ -484,7 +484,7 @@ impl TypedArray { // c. If mapping is true, then let mapped_value = if let Some(map_fn) = &mapping { // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - map_fn.call(this_arg, &[k_value, k.into()], context)? + map_fn.call(&this_arg, &[k_value, k.into()], context)? } // d. Else, let mappedValue be kValue. else { @@ -909,7 +909,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -928,7 +929,7 @@ impl TypedArray { // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). let test_result = callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )? @@ -1056,7 +1057,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1081,7 +1083,7 @@ impl TypedArray { // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# let selected = callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1141,7 +1143,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(predicate) is false, throw a TypeError exception. - let predicate = match args.get_or_undefined(0).as_object() { + let predicate = args.get_or_undefined(0); + let predicate = match predicate.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1161,7 +1164,7 @@ impl TypedArray { // d. If testResult is true, return kValue. if predicate .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1203,8 +1206,9 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(predicate) is false, throw a TypeError exception. + let predicate = args.get_or_undefined(0); let predicate = - match args.get_or_undefined(0).as_object() { + match predicate.as_object() { Some(obj) if obj.is_callable() => obj, _ => return context.throw_type_error( "TypedArray.prototype.findindex called with non-callable predicate function", @@ -1222,7 +1226,7 @@ impl TypedArray { // d. If testResult is true, return 𝔽(k). if predicate .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value.clone(), k.into(), this.clone()], context, )? @@ -1264,7 +1268,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1282,7 +1287,7 @@ impl TypedArray { // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). callback_fn.call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )?; @@ -1357,7 +1362,7 @@ impl TypedArray { let element_k = obj.get(k, context).expect("Get cannot fail here"); // b. If SameValueZero(searchElement, elementK) is true, return true. - if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { + if JsValue::same_value_zero(&args.get_or_undefined(0), &element_k) { return Ok(true.into()); } @@ -1683,7 +1688,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1704,7 +1710,7 @@ impl TypedArray { // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). let mapped_value = callback_fn.call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )?; @@ -1745,7 +1751,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -1826,8 +1833,9 @@ impl TypedArray { let len = o.array_length() as i64; // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + let callback_fn = args.get_or_undefined(0); let callback_fn = - match args.get_or_undefined(0).as_object() { + match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => return context.throw_type_error( "TypedArray.prototype.reduceright called with non-callable callback function", @@ -1986,7 +1994,7 @@ impl TypedArray { // 7. Else, _ => { // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). - Self::set_typed_array_from_array_like(&target, target_offset, source, context)?; + Self::set_typed_array_from_array_like(&target, target_offset, &source, context)?; } } @@ -2520,7 +2528,8 @@ impl TypedArray { let len = o.array_length(); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - let callback_fn = match args.get_or_undefined(0).as_object() { + let callback_fn = args.get_or_undefined(0); + let callback_fn = match callback_fn.as_object() { Some(obj) if obj.is_callable() => obj, _ => { return context.throw_type_error( @@ -2540,7 +2549,7 @@ impl TypedArray { // d. If testResult is true, return true. if callback_fn .call( - args.get_or_undefined(1), + &args.get_or_undefined(1), &[k_value, k.into(), this.clone()], context, )? diff --git a/boa_engine/src/object/jstypedarray.rs b/boa_engine/src/object/jstypedarray.rs index c23bd04de40..389d6d77475 100644 --- a/boa_engine/src/object/jstypedarray.rs +++ b/boa_engine/src/object/jstypedarray.rs @@ -10,7 +10,7 @@ use std::ops::Deref; /// JavaScript `TypedArray` rust object. #[derive(Debug, Clone, Trace, Finalize)] pub struct JsTypedArray { - inner: JsValue, + inner: JsObject, } impl JsTypedArray { @@ -33,7 +33,7 @@ impl JsTypedArray { /// Same a `array.length` in JavaScript. #[inline] pub fn length(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::length(&self.inner, &[], context)? + Ok(TypedArray::length(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("length should return a number")) @@ -50,12 +50,12 @@ impl JsTypedArray { where T: Into, { - TypedArray::at(&self.inner, &[index.into().into()], context) + TypedArray::at(&self.clone().into(), &[index.into().into()], context) } #[inline] pub fn byte_length(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::byte_length(&self.inner, &[], context)? + Ok(TypedArray::byte_length(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -63,7 +63,7 @@ impl JsTypedArray { #[inline] pub fn byte_offset(&self, context: &mut Context) -> JsResult { - Ok(TypedArray::byte_offset(&self.inner, &[], context)? + Ok(TypedArray::byte_offset(&self.clone().into(), &[], context)? .as_number() .map(|x| x as usize) .expect("byteLength should return a number")) @@ -81,7 +81,7 @@ impl JsTypedArray { T: Into, { TypedArray::fill( - &self.inner, + &self.clone().into(), &[ value.into(), start.into_or_undefined(), @@ -99,7 +99,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let result = TypedArray::every( - &self.inner, + &self.clone().into(), &[predicate.into(), this_arg.into_or_undefined()], context, )? @@ -117,7 +117,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let result = TypedArray::some( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, )? @@ -129,7 +129,11 @@ impl JsTypedArray { #[inline] pub fn sort(&self, compare_fn: Option, context: &mut Context) -> JsResult { - TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; + TypedArray::sort( + &self.clone().into(), + &[compare_fn.into_or_undefined()], + context, + )?; Ok(self.clone()) } @@ -142,10 +146,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::filter( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -158,10 +166,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::map( - &self.inner, + &self.clone().into(), &[callback.into(), this_arg.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -174,7 +186,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::reduce( - &self.inner, + &self.clone().into(), &[callback.into(), initial_value.into_or_undefined()], context, ) @@ -188,7 +200,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::reduceright( - &self.inner, + &self.clone().into(), &[callback.into(), initial_value.into_or_undefined()], context, ) @@ -196,7 +208,7 @@ impl JsTypedArray { #[inline] pub fn reverse(&self, context: &mut Context) -> JsResult { - TypedArray::reverse(&self.inner, &[], context)?; + TypedArray::reverse(&self.clone().into(), &[], context)?; Ok(self.clone()) } @@ -208,10 +220,14 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { let object = TypedArray::slice( - &self.inner, + &self.clone().into(), &[start.into_or_undefined(), end.into_or_undefined()], context, - )?; + )? + .as_object() + .as_deref() + .cloned() + .expect("Filter must always return an array object on success"); Ok(Self { inner: object }) } @@ -224,7 +240,7 @@ impl JsTypedArray { context: &mut Context, ) -> JsResult { TypedArray::find( - &self.inner, + &self.clone().into(), &[predicate.into(), this_arg.into_or_undefined()], context, ) @@ -241,7 +257,7 @@ impl JsTypedArray { T: Into, { let index = TypedArray::index_of( - &self.inner, + &self.clone().into(), &[search_element.into(), from_index.into_or_undefined()], context, )? @@ -267,7 +283,7 @@ impl JsTypedArray { T: Into, { let index = TypedArray::last_index_of( - &self.inner, + &self.clone().into(), &[search_element.into(), from_index.into_or_undefined()], context, )? @@ -284,8 +300,14 @@ impl JsTypedArray { #[inline] pub fn join(&self, separator: Option, context: &mut Context) -> JsResult { - TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { + TypedArray::join( + &self.clone().into(), + &[separator.into_or_undefined()], + context, + ) + .map(|x| { x.as_string() + .as_deref() .cloned() .expect("TypedArray.prototype.join always returns string") }) @@ -295,17 +317,14 @@ impl JsTypedArray { impl From for JsObject { #[inline] fn from(o: JsTypedArray) -> Self { - o.inner - .as_object() - .expect("should always be an object") - .clone() + o.inner.clone() } } impl From for JsValue { #[inline] fn from(o: JsTypedArray) -> Self { - o.inner.clone() + o.inner.clone().into() } } @@ -314,7 +333,7 @@ impl Deref for JsTypedArray { #[inline] fn deref(&self) -> &Self::Target { - self.inner.as_object().expect("should always be an object") + &self.inner } } @@ -361,18 +380,14 @@ macro_rules! JsTypedArrayType { impl From<$name> for JsObject { #[inline] fn from(o: $name) -> Self { - o.inner - .inner - .as_object() - .expect("should always be an object") - .clone() + o.inner.inner.clone() } } impl From<$name> for JsValue { #[inline] fn from(o: $name) -> Self { - o.inner.inner.clone() + o.inner.inner.clone().into() } } diff --git a/boa_engine/src/string.rs b/boa_engine/src/string.rs index e4f7ea326c9..6ca0bbac43e 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -723,7 +723,6 @@ impl TaggedInner { } } - impl Default for JsString { #[inline] fn default() -> Self { diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index bf9bf06e7c8..e582158e842 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -170,5 +170,3 @@ where self.map_or_else(JsValue::undefined, Into::into) } } - - diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index c5996066107..367e88d332b 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -1,4 +1,5 @@ #![warn(unsafe_op_in_unsafe_fn)] +#![allow(unstable_name_collisions)] //! This module implements the JavaScript Value. //! //! Javascript values, utility methods and conversion between Javascript values and Rust values. @@ -22,6 +23,7 @@ use num_bigint::BigInt; use num_integer::Integer; use num_traits::Zero; use once_cell::sync::Lazy; +use sptr::Strict; use std::{ cell::Cell, collections::HashSet, @@ -69,7 +71,7 @@ pub const CANONICALIZED_NAN: u64 = QNAN; // const PAYLOAD: u64 = 0x00007FFFFFFFFFFF; // const TYPE: u64 = !PAYLOAD; -pub const TAG_MASK: u64 = 0xFFFF000000000000; +pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000; pub const DOUBLE_TYPE: u64 = QNAN; pub const INTEGER_TYPE: u64 = QNAN | (0b001 << 48); @@ -117,12 +119,9 @@ pub enum ValueTag { #[derive(Debug)] #[repr(transparent)] pub struct JsValue { - value: Cell, + value: Cell<*mut ()>, } -// TODO: Remove this !!!! -unsafe impl Sync for JsValue {} - impl JsValue { /// Create a new [`JsValue`]. #[inline] @@ -137,47 +136,47 @@ impl JsValue { if self.is_rational() { return ValueTag::Double; } - unsafe { std::mem::transmute(((self.value.get() & TAG_MASK) >> 48) as u16) } + unsafe { std::mem::transmute(((self.value.get().addr() & TAG_MASK as usize) >> 48) as u16) } } /// Creates a new number with `NaN` value. #[inline] pub const fn nan() -> Self { Self { - value: Cell::new(CANONICALIZED_NAN), + value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN as usize)), } } pub fn is_nan(&self) -> bool { - self.value.get() == CANONICALIZED_NAN + self.value.get().addr() as u64 == CANONICALIZED_NAN } /// Creates a new `undefined` value. #[inline] pub const fn undefined() -> Self { Self { - value: Cell::new(UNDEFINED_TYPE), + value: Cell::new(sptr::invalid_mut(UNDEFINED_TYPE as usize)), } } /// Returns true if the value is undefined. #[inline] pub fn is_undefined(&self) -> bool { - self.value.get() == UNDEFINED_TYPE + self.value.get().addr() as u64 == UNDEFINED_TYPE } /// Creates a new `null` value. #[inline] pub const fn null() -> Self { Self { - value: Cell::new(NULL_TYPE), + value: Cell::new(sptr::invalid_mut(NULL_TYPE as usize)), } } /// Returns true if the value is null. #[inline] pub fn is_null(&self) -> bool { - self.value.get() == NULL_TYPE + self.value.get().addr() as u64 == NULL_TYPE } /// Returns true if the value is null or undefined. @@ -187,11 +186,11 @@ impl JsValue { } pub fn is_rational(&self) -> bool { - (self.value.get() & !SIGN_BIT) <= QNAN + (self.value.get().addr() as u64 & !SIGN_BIT) <= QNAN } fn as_rational_unchecked(&self) -> f64 { - f64::from_bits(self.value.get()) + f64::from_bits(self.value.get().addr() as u64) } pub fn as_rational(&self) -> Option { @@ -203,11 +202,11 @@ impl JsValue { } pub fn is_i32(&self) -> bool { - self.value.get() & TAG_MASK == INTEGER_TYPE + self.value.get().addr() as u64 & TAG_MASK == INTEGER_TYPE } pub fn as_i32_uncheched(&self) -> i32 { - (self.value.get() & MASK_INT_PAYLOAD) as u32 as i32 + (self.value.get().addr() as u64 & MASK_INT_PAYLOAD) as u32 as i32 } pub fn as_i32(&self) -> Option { @@ -221,11 +220,11 @@ impl JsValue { /// Returns true if the value is a boolean. #[inline] pub fn is_boolean(&self) -> bool { - self.value.get() & TAG_MASK == BOOLEAN_TYPE + self.value.get().addr() as u64 & TAG_MASK == BOOLEAN_TYPE } pub fn as_boolean_uncheched(&self) -> bool { - (self.value.get() & 0xFF) != 0 + (self.value.get().addr() as u64 & 0xFF) != 0 } #[inline] @@ -238,13 +237,15 @@ impl JsValue { } pub fn as_pointer(&self) -> *mut () { - (self.value.get() & MASK_POINTER_PAYLOAD) as *mut () + self.value + .get() + .map_addr(|addr| addr & MASK_POINTER_PAYLOAD as usize) } /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { - self.value.get() & TAG_MASK == OBJECT_TYPE + self.value.get().addr() as u64 & TAG_MASK == OBJECT_TYPE } /// Returns a reference to the boxed [`JsObject`] without checking @@ -269,7 +270,7 @@ impl JsValue { /// Returns true if the value is a string. #[inline] pub fn is_string(&self) -> bool { - self.value.get() & TAG_MASK == STRING_TYPE + self.value.get().addr() as u64 & TAG_MASK == STRING_TYPE } /// Returns a reference to the boxed [`JsString`] without checking @@ -294,7 +295,7 @@ impl JsValue { } pub fn is_symbol(&self) -> bool { - self.value.get() & TAG_MASK == SYMBOL_TYPE + self.value.get().addr() as u64 & TAG_MASK == SYMBOL_TYPE } /// Returns a reference to the boxed [`JsSymbol`] without checking @@ -319,7 +320,7 @@ impl JsValue { /// Returns true if the value is a bigint. #[inline] pub fn is_bigint(&self) -> bool { - self.value.get() & TAG_MASK == BIGINT_TYPE + self.value.get().addr() as u64 & TAG_MASK == BIGINT_TYPE } /// Returns a reference to the boxed [`JsBigInt`] without checking @@ -377,7 +378,9 @@ impl From for JsValue { #[inline] fn from(value: bool) -> Self { let value = Self { - value: Cell::new(BOOLEAN_TYPE | u64::from(value)), + value: Cell::new(sptr::invalid_mut( + usize::from(value) | BOOLEAN_TYPE as usize, + )), }; debug_assert!(value.is_boolean()); debug_assert_eq!(value.tag(), ValueTag::Boolean); @@ -389,7 +392,9 @@ impl From for JsValue { #[inline] fn from(value: i32) -> Self { let value = Self { - value: Cell::new(INTEGER_TYPE | u64::from(value as u32)), + value: Cell::new(sptr::invalid_mut( + value as u32 as usize | INTEGER_TYPE as usize, + )), }; debug_assert!(value.is_integer()); debug_assert_eq!(value.tag(), ValueTag::Integer); @@ -402,12 +407,12 @@ impl From for JsValue { fn from(value: f64) -> Self { if value.is_nan() { return Self { - value: Cell::new(CANONICALIZED_NAN), + value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN as usize)), }; } let value = Self { - value: Cell::new(value.to_bits()), + value: Cell::new(sptr::invalid_mut(value.to_bits() as usize)), }; debug_assert!(value.is_rational()); debug_assert_eq!(value.tag(), ValueTag::Double); @@ -419,10 +424,13 @@ impl From for JsValue { #[inline] fn from(string: JsString) -> Self { let string = ManuallyDrop::new(string); - let pointer = unsafe { JsString::into_void_ptr(string) } as u64; - debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let pointer = unsafe { JsString::into_void_ptr(string) }; + debug_assert_eq!( + pointer.addr() & MASK_POINTER_PAYLOAD as usize, + pointer.addr() + ); let value = Self { - value: Cell::new(STRING_TYPE | pointer), + value: Cell::new(pointer.map_addr(|addr| addr | STRING_TYPE as usize)), }; debug_assert!(value.is_string()); debug_assert_eq!(value.tag(), ValueTag::String); @@ -434,11 +442,14 @@ impl From for JsValue { #[inline] fn from(object: JsObject) -> Self { let object = ManuallyDrop::new(object); - let pointer = unsafe { JsObject::into_void_ptr(object) } as u64; - debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let pointer = unsafe { JsObject::into_void_ptr(object) }; + debug_assert_eq!( + pointer.addr() & MASK_POINTER_PAYLOAD as usize, + pointer.addr() + ); debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); let value = Self { - value: Cell::new(OBJECT_TYPE | pointer), + value: Cell::new(pointer.map_addr(|addr| addr | OBJECT_TYPE as usize)), }; debug_assert!(value.is_object()); debug_assert_eq!(value.tag(), ValueTag::Object); @@ -450,10 +461,13 @@ impl From for JsValue { #[inline] fn from(symbol: JsSymbol) -> Self { let symbol = ManuallyDrop::new(symbol); - let pointer = unsafe { JsSymbol::into_void_ptr(symbol) as u64 }; - debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let pointer = unsafe { JsSymbol::into_void_ptr(symbol) }; + debug_assert_eq!( + pointer.addr() & MASK_POINTER_PAYLOAD as usize, + pointer.addr() + ); let value = Self { - value: Cell::new(SYMBOL_TYPE | pointer), + value: Cell::new(pointer.map_addr(|addr| addr | SYMBOL_TYPE as usize)), }; debug_assert!(value.is_symbol()); debug_assert_eq!(value.tag(), ValueTag::Symbol); @@ -465,10 +479,13 @@ impl From for JsValue { #[inline] fn from(bigint: JsBigInt) -> Self { let bigint = ManuallyDrop::new(bigint); - let pointer = unsafe { JsBigInt::into_void_ptr(bigint) as u64 }; - debug_assert_eq!(pointer & MASK_POINTER_PAYLOAD, pointer); + let pointer = unsafe { JsBigInt::into_void_ptr(bigint) }; + debug_assert_eq!( + pointer.addr() & MASK_POINTER_PAYLOAD as usize, + pointer.addr() + ); let value = Self { - value: Cell::new(BIGINT_TYPE | pointer), + value: Cell::new(pointer.map_addr(|addr| addr | BIGINT_TYPE as usize)), }; debug_assert!(value.is_bigint()); debug_assert_eq!(value.tag(), ValueTag::BigInt); @@ -612,7 +629,7 @@ unsafe impl Trace for JsValue { let o = JsObject::from_void_ptr(self.as_pointer()); o.root(); self.value - .set(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE as usize)); } } } @@ -625,7 +642,7 @@ unsafe impl Trace for JsValue { let o = JsObject::from_void_ptr(self.as_pointer()); o.unroot(); self.value - .set(OBJECT_TYPE | (JsObject::into_void_ptr(o) as u64)); + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE as usize)); } } } diff --git a/boa_engine/src/value/operations.rs b/boa_engine/src/value/operations.rs index 45bb7cbd6dd..12411355cb7 100644 --- a/boa_engine/src/value/operations.rs +++ b/boa_engine/src/value/operations.rs @@ -16,18 +16,14 @@ impl JsValue { (JsVariant::Rational(x), JsVariant::Rational(y)) => Self::new(x + y), (JsVariant::Integer(x), JsVariant::Rational(y)) => Self::new(f64::from(x) + y), (JsVariant::Rational(x), JsVariant::Integer(y)) => Self::new(x + f64::from(y)), + (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)), + // String concat (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)), (JsVariant::String(x), _) => { Self::new(JsString::concat(&*x, other.to_string(context)?)) } (_, JsVariant::String(y)) => Self::new(JsString::concat(self.to_string(context)?, &*y)), - (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)), - - // String concat - (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)), - (JsVariant::String(x), _) => Self::from(JsString::concat(&*x, other.to_string(context)?)), - (_, JsVariant::String(y)) => Self::from(JsString::concat(self.to_string(context)?, &*y)), // Slow path: (_, _) => { diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index 91bb507ed20..c67b44a4897 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -63,7 +63,7 @@ fn detach_array_buffer( 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) { + if !JsValue::same_value(&array_buffer.array_buffer_detach_key, &key) { return context.throw_type_error("Cannot detach array buffer with different key"); } From c3dc5709828b844bb98b910a59f28479aefe02c4 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 20 May 2022 17:01:57 -0500 Subject: [PATCH 7/8] Feature gate nan boxing and implement fallback --- Cargo.lock | 3 + boa_engine/Cargo.toml | 3 + boa_engine/src/builtins/dataview/mod.rs | 6 +- boa_engine/src/builtins/proxy/mod.rs | 12 +- boa_engine/src/builtins/string/mod.rs | 2 +- boa_engine/src/value/conversions.rs | 144 +++-- boa_engine/src/value/equality.rs | 2 +- boa_engine/src/value/mod.rs | 784 +---------------------- boa_engine/src/value/platform/default.rs | 216 +++++++ boa_engine/src/value/platform/mod.rs | 74 +++ boa_engine/src/value/platform/x86_64.rs | 732 +++++++++++++++++++++ 11 files changed, 1135 insertions(+), 843 deletions(-) create mode 100644 boa_engine/src/value/platform/default.rs create mode 100644 boa_engine/src/value/platform/mod.rs create mode 100644 boa_engine/src/value/platform/x86_64.rs diff --git a/Cargo.lock b/Cargo.lock index c0fa6c540db..f37fb3f34c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,7 @@ dependencies = [ "boa_interner", "boa_profiler", "boa_unicode", + "cfg-if", "chrono", "criterion", "dyn-clone", @@ -106,6 +107,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", + "num_enum", "once_cell", "rand", "regress", @@ -1077,6 +1079,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn", diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 23ff832f53b..b9cc322d601 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -14,6 +14,7 @@ readme = "../README.md" [features] profiler = ["boa_profiler/profiler"] deser = ["boa_interner/serde"] +nan_boxing = [] # Enable Boa's WHATWG console object implementation. console = [] @@ -43,6 +44,8 @@ once_cell = "1.10.0" tap = "1.0.1" icu = "0.5.0" sptr = "0.3.1" +cfg-if = "1.0.0" +num_enum = "0.5.7" [dev-dependencies] criterion = "0.3.5" diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index c8181b02ee8..1f431841d15 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -100,9 +100,9 @@ impl DataView { ) -> JsResult { let byte_length = args.get_or_undefined(2); - let buffer_obj = args - .get_or_undefined(0); - let buffer_obj = buffer_obj.as_object() + let buffer_obj = args.get_or_undefined(0); + let buffer_obj = buffer_obj + .as_object() .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; // 1. If NewTarget is undefined, throw a TypeError exception. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 5739c209041..3ad88a78151 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -82,7 +82,11 @@ impl Proxy { } // 2. Return ? ProxyCreate(target, handler). - Self::create(&args.get_or_undefined(0), &args.get_or_undefined(1), context) + Self::create( + &args.get_or_undefined(0), + &args.get_or_undefined(1), + context, + ) } // `10.5.14 ProxyCreate ( target, handler )` @@ -131,7 +135,11 @@ impl Proxy { /// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let p be ? ProxyCreate(target, handler). - let p = Self::create(&args.get_or_undefined(0),& args.get_or_undefined(1), context)?; + let p = Self::create( + &args.get_or_undefined(0), + &args.get_or_undefined(1), + context, + )?; // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 4. Set revoker.[[RevocableProxy]] to p. diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 4ef80a55121..31ba682fcaf 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -1521,7 +1521,7 @@ impl String { let fill_string = args.get_or_undefined(1); // 2. Return ? StringPad(O, maxLength, fillString, start). - Self::string_pad(this, &max_length,& fill_string, Placement::Start, context) + Self::string_pad(this, &max_length, &fill_string, Placement::Start, context) } /// String.prototype.trim() diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index e582158e842..44a6efddbe0 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -1,4 +1,6 @@ -use crate::JsString; +use boa_profiler::Profiler; + +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; use super::{Display, JsValue}; @@ -9,31 +11,81 @@ impl From<&Self> for JsValue { } } +impl From for JsValue +where + T: Into, +{ + #[inline] + fn from(value: T) -> Self { + let _timer = Profiler::global().start_event("From", "value"); + + Self::string(value.into()) + } +} + +impl From for JsValue { + #[inline] + fn from(value: char) -> Self { + Self::new(value.to_string()) + } +} + +impl From for JsValue { + #[inline] + fn from(value: JsSymbol) -> Self { + Self::symbol(value) + } +} + +impl From for JsValue { + #[allow(clippy::float_cmp)] + #[inline] + fn from(value: f32) -> Self { + // if value as i32 as f64 == value { + // Self::Integer(value as i32) + // } else { + Self::rational(value.into()) + // } + } +} + +impl From for JsValue { + #[allow(clippy::float_cmp)] + #[inline] + fn from(value: f64) -> Self { + // if value as i32 as f64 == value { + // Self::Integer(value as i32) + // } else { + Self::rational(value) + // } + } +} + impl From for JsValue { #[inline] fn from(value: u8) -> Self { - Self::from(value as i32) + Self::integer(value.into()) } } impl From for JsValue { #[inline] fn from(value: i8) -> Self { - Self::from(value as i32) + Self::integer(value.into()) } } impl From for JsValue { #[inline] fn from(value: u16) -> Self { - Self::from(value as i32) + Self::integer(value.into()) } } impl From for JsValue { #[inline] fn from(value: i16) -> Self { - Self::from(value as i32) + Self::integer(value.into()) } } @@ -41,31 +93,34 @@ impl From for JsValue { #[inline] fn from(value: u32) -> Self { if let Ok(integer) = i32::try_from(value) { - Self::from(integer) + Self::integer(integer) } else { - Self::from(value as f64) + Self::rational(value.into()) } } } -impl From for JsValue { +impl From for JsValue { #[inline] - fn from(value: usize) -> Self { - if let Ok(value) = i32::try_from(value) { - Self::from(value) - } else { - Self::from(value as f64) - } + fn from(value: i32) -> Self { + Self::integer(value) + } +} + +impl From for JsValue { + #[inline] + fn from(value: JsBigInt) -> Self { + Self::bigint(value) } } -impl From for JsValue { +impl From for JsValue { #[inline] - fn from(value: isize) -> Self { + fn from(value: usize) -> Self { if let Ok(value) = i32::try_from(value) { - Self::from(value) + Self::integer(value) } else { - Self::from(value as f64) + Self::rational(value as f64) } } } @@ -74,9 +129,9 @@ impl From for JsValue { #[inline] fn from(value: u64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::from(value) + Self::integer(value) } else { - Self::from(value as f64) + Self::rational(value as f64) } } } @@ -85,57 +140,32 @@ impl From for JsValue { #[inline] fn from(value: i64) -> Self { if let Ok(value) = i32::try_from(value) { - Self::from(value) + Self::integer(value) } else { - Self::from(value as f64) + Self::rational(value as f64) } } } -impl From for JsValue { - #[allow(clippy::float_cmp)] - #[inline] - fn from(value: f32) -> Self { - // if value as i32 as f64 == value { - // Self::Integer(value as i32) - // } else { - Self::from(value as f64) - // } - } -} - -impl From for JsValue { - #[inline] - fn from(value: char) -> Self { - Self::from(value.to_string()) - } -} - -impl From<&str> for JsValue { - #[inline] - fn from(string: &str) -> Self { - Self::from(JsString::new(string)) - } -} - -impl From> for JsValue { +impl From for JsValue { #[inline] - fn from(string: Box) -> Self { - Self::from(JsString::new(string)) + fn from(value: bool) -> Self { + Self::boolean(value) } } -impl From<&String> for JsValue { +impl From for JsValue { #[inline] - fn from(string: &String) -> Self { - Self::from(JsString::new(string.as_str())) + fn from(object: JsObject) -> Self { + let _timer = Profiler::global().start_event("From", "value"); + Self::object(object) } } -impl From for JsValue { +impl From<()> for JsValue { #[inline] - fn from(string: String) -> Self { - Self::from(JsString::new(string)) + fn from(_: ()) -> Self { + Self::null() } } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index 1ea11f848d2..421323ddf60 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -70,7 +70,7 @@ impl JsValue { // b. If n is NaN, return false. // c. Return the result of the comparison x == n. (JsVariant::BigInt(a), JsVariant::String(ref b)) => match JsBigInt::from_string(b) { - Some(b) => a == b, + Some(b) => *a == b, None => false, }, diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 367e88d332b..29bd39acc99 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -1,5 +1,4 @@ #![warn(unsafe_op_in_unsafe_fn)] -#![allow(unstable_name_collisions)] //! This module implements the JavaScript Value. //! //! Javascript values, utility methods and conversion between Javascript values and Rust values. @@ -14,22 +13,17 @@ use crate::{ }, object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, - symbol::{JsSymbol, WellKnownSymbols}, + symbol::WellKnownSymbols, Context, JsBigInt, JsResult, JsString, }; -use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use num_bigint::BigInt; use num_integer::Integer; use num_traits::Zero; use once_cell::sync::Lazy; -use sptr::Strict; use std::{ - cell::Cell, collections::HashSet, fmt::{self, Display}, - marker::PhantomData, - mem::ManuallyDrop, ops::Sub, str::FromStr, }; @@ -40,6 +34,7 @@ mod equality; mod hash; mod integer; mod operations; +mod platform; mod serde_json; mod r#type; @@ -49,6 +44,7 @@ pub use equality::*; pub use hash::*; pub use integer::IntegerOrInfinity; pub use operations::*; +pub use platform::*; pub use r#type::Type; static TWO_E_64: Lazy = Lazy::new(|| { @@ -61,67 +57,6 @@ static TWO_E_63: Lazy = Lazy::new(|| { BigInt::from(TWO_E_63) }); -const SIGN_BIT: u64 = 0x8000000000000000; -const EXPONENT: u64 = 0x7FF0000000000000; -// const MANTISA: u64 = 0x0008000000000000; -const SIGNAL_BIT: u64 = 0x0008000000000000; -const QNAN: u64 = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000 - -pub const CANONICALIZED_NAN: u64 = QNAN; -// const PAYLOAD: u64 = 0x00007FFFFFFFFFFF; -// const TYPE: u64 = !PAYLOAD; - -pub const TAG_MASK: u64 = 0xFFFF_0000_0000_0000; - -pub const DOUBLE_TYPE: u64 = QNAN; -pub const INTEGER_TYPE: u64 = QNAN | (0b001 << 48); -pub const BOOLEAN_TYPE: u64 = QNAN | (0b010 << 48); -pub const UNDEFINED_TYPE: u64 = QNAN | (0b011 << 48); -pub const NULL_TYPE: u64 = QNAN | (0b100 << 48); - -pub const RESERVED1_TYPE: u64 = QNAN | (0b101 << 48); -pub const RESERVED2_TYPE: u64 = QNAN | (0b110 << 48); -pub const RESERVED3_TYPE: u64 = QNAN | (0b111 << 48); - -pub const POINTER_TYPE: u64 = SIGN_BIT | QNAN; -pub const OBJECT_TYPE: u64 = POINTER_TYPE | (0b001 << 48); -pub const STRING_TYPE: u64 = POINTER_TYPE | (0b010 << 48); -pub const SYMBOL_TYPE: u64 = POINTER_TYPE | (0b011 << 48); -pub const BIGINT_TYPE: u64 = POINTER_TYPE | (0b100 << 48); - -pub const RESERVED4_TYPE: u64 = POINTER_TYPE | (0b101 << 48); -pub const RESERVED5_TYPE: u64 = POINTER_TYPE | (0b110 << 48); -pub const RESERVED6_TYPE: u64 = POINTER_TYPE | (0b111 << 48); - -pub const MASK_INT_PAYLOAD: u64 = 0x00000000FFFFFFFF; -pub const MASK_POINTER_PAYLOAD: u64 = 0x0000FFFFFFFFFFFF; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u16)] -pub enum ValueTag { - Double = (DOUBLE_TYPE >> 48) as _, - Integer = (INTEGER_TYPE >> 48) as _, - Boolean = (BOOLEAN_TYPE >> 48) as _, - Undefined = (UNDEFINED_TYPE >> 48) as _, - Null = (NULL_TYPE >> 48) as _, - Object = (OBJECT_TYPE >> 48) as _, - String = (STRING_TYPE >> 48) as _, - Symbol = (SYMBOL_TYPE >> 48) as _, - BigInt = (BIGINT_TYPE >> 48) as _, - // Reserved1 = RESERVED1_TYPE, - // Reserved2 = RESERVED2_TYPE, - // Reserved3 = RESERVED3_TYPE, - // Reserved4 = RESERVED4_TYPE, - // Reserved5 = RESERVED5_TYPE, - // Reserved6 = RESERVED6_TYPE, -} - -#[derive(Debug)] -#[repr(transparent)] -pub struct JsValue { - value: Cell<*mut ()>, -} - impl JsValue { /// Create a new [`JsValue`]. #[inline] @@ -132,715 +67,12 @@ impl JsValue { value.into() } - fn tag(&self) -> ValueTag { - if self.is_rational() { - return ValueTag::Double; - } - unsafe { std::mem::transmute(((self.value.get().addr() & TAG_MASK as usize) >> 48) as u16) } - } - - /// Creates a new number with `NaN` value. - #[inline] - pub const fn nan() -> Self { - Self { - value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN as usize)), - } - } - - pub fn is_nan(&self) -> bool { - self.value.get().addr() as u64 == CANONICALIZED_NAN - } - - /// Creates a new `undefined` value. - #[inline] - pub const fn undefined() -> Self { - Self { - value: Cell::new(sptr::invalid_mut(UNDEFINED_TYPE as usize)), - } - } - - /// Returns true if the value is undefined. - #[inline] - pub fn is_undefined(&self) -> bool { - self.value.get().addr() as u64 == UNDEFINED_TYPE - } - - /// Creates a new `null` value. - #[inline] - pub const fn null() -> Self { - Self { - value: Cell::new(sptr::invalid_mut(NULL_TYPE as usize)), - } - } - - /// Returns true if the value is null. - #[inline] - pub fn is_null(&self) -> bool { - self.value.get().addr() as u64 == NULL_TYPE - } - /// Returns true if the value is null or undefined. #[inline] pub fn is_null_or_undefined(&self) -> bool { self.is_null() || self.is_undefined() } - pub fn is_rational(&self) -> bool { - (self.value.get().addr() as u64 & !SIGN_BIT) <= QNAN - } - - fn as_rational_unchecked(&self) -> f64 { - f64::from_bits(self.value.get().addr() as u64) - } - - pub fn as_rational(&self) -> Option { - if self.is_rational() { - return Some(self.as_rational_unchecked()); - } - - None - } - - pub fn is_i32(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == INTEGER_TYPE - } - - pub fn as_i32_uncheched(&self) -> i32 { - (self.value.get().addr() as u64 & MASK_INT_PAYLOAD) as u32 as i32 - } - - pub fn as_i32(&self) -> Option { - if self.is_i32() { - return Some(self.as_i32_uncheched()); - } - - None - } - - /// Returns true if the value is a boolean. - #[inline] - pub fn is_boolean(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == BOOLEAN_TYPE - } - - pub fn as_boolean_uncheched(&self) -> bool { - (self.value.get().addr() as u64 & 0xFF) != 0 - } - - #[inline] - pub fn as_boolean(&self) -> Option { - if self.is_boolean() { - return Some(self.as_boolean_uncheched()); - } - - None - } - - pub fn as_pointer(&self) -> *mut () { - self.value - .get() - .map_addr(|addr| addr & MASK_POINTER_PAYLOAD as usize) - } - - /// Returns true if the value is an object - #[inline] - pub fn is_object(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == OBJECT_TYPE - } - - /// Returns a reference to the boxed [`JsObject`] without checking - /// if the tag of `self` is valid. - /// - /// # Safety - /// - /// Calling this method with a [`JsValue`] that doesn't box - /// a [`JsObject`] is undefined behaviour. - pub unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> { - unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) } - } - - pub fn as_object(&self) -> Option> { - if self.is_object() { - return unsafe { Some(self.as_object_unchecked()) }; - } - - None - } - - /// Returns true if the value is a string. - #[inline] - pub fn is_string(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == STRING_TYPE - } - - /// Returns a reference to the boxed [`JsString`] without checking - /// if the tag of `self` is valid. - /// - /// # Safety - /// - /// Calling this method with a [`JsValue`] that doesn't box - /// a [`JsString`] is undefined behaviour. - pub unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> { - unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) } - } - - /// Returns the string if the values is a string, otherwise `None`. - #[inline] - pub fn as_string(&self) -> Option> { - if self.is_string() { - return unsafe { Some(self.as_string_unchecked()) }; - } - - None - } - - pub fn is_symbol(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == SYMBOL_TYPE - } - - /// Returns a reference to the boxed [`JsSymbol`] without checking - /// if the tag of `self` is valid. - /// - /// # Safety - /// - /// Calling this method with a [`JsValue`] that doesn't box - /// a [`JsSymbol`] is undefined behaviour. - pub unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> { - unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) } - } - - pub fn as_symbol(&self) -> Option> { - if self.is_symbol() { - return unsafe { Some(self.as_symbol_unchecked()) }; - } - - None - } - - /// Returns true if the value is a bigint. - #[inline] - pub fn is_bigint(&self) -> bool { - self.value.get().addr() as u64 & TAG_MASK == BIGINT_TYPE - } - - /// Returns a reference to the boxed [`JsBigInt`] without checking - /// if the tag of `self` is valid. - /// - /// # Safety - /// - /// Calling this method with a [`JsValue`] that doesn't box - /// a [`JsBigInt`] is undefined behaviour. - #[inline] - pub unsafe fn as_bigint_unchecked(&self) -> Ref<'_, JsBigInt> { - // SAFETY: The safety contract must be upheld by the caller - unsafe { Ref::new(JsBigInt::from_void_ptr(self.as_pointer())) } - } - - pub fn as_bigint(&self) -> Option> { - if self.is_bigint() { - return unsafe { Some(self.as_bigint_unchecked()) }; - } - - None - } - - pub fn variant(&self) -> JsVariant<'_> { - unsafe { - match self.tag() { - ValueTag::Null => JsVariant::Null, - ValueTag::Undefined => JsVariant::Undefined, - ValueTag::Integer => JsVariant::Integer(self.as_i32_uncheched()), - ValueTag::Double => JsVariant::Rational(self.as_rational_unchecked()), - ValueTag::Boolean => JsVariant::Boolean(self.as_boolean_uncheched()), - ValueTag::Object => JsVariant::Object(self.as_object_unchecked()), - ValueTag::String => JsVariant::String(self.as_string_unchecked()), - ValueTag::Symbol => JsVariant::Symbol(self.as_symbol_unchecked()), - ValueTag::BigInt => JsVariant::BigInt(self.as_bigint_unchecked()), - } - } - } -} - -#[derive(Debug)] -pub enum JsVariant<'a> { - Null, - Undefined, - Rational(f64), - Integer(i32), - Boolean(bool), - String(Ref<'a, JsString>), - Symbol(Ref<'a, JsSymbol>), - BigInt(Ref<'a, JsBigInt>), - Object(Ref<'a, JsObject>), -} - -impl From for JsValue { - #[inline] - fn from(value: bool) -> Self { - let value = Self { - value: Cell::new(sptr::invalid_mut( - usize::from(value) | BOOLEAN_TYPE as usize, - )), - }; - debug_assert!(value.is_boolean()); - debug_assert_eq!(value.tag(), ValueTag::Boolean); - value - } -} - -impl From for JsValue { - #[inline] - fn from(value: i32) -> Self { - let value = Self { - value: Cell::new(sptr::invalid_mut( - value as u32 as usize | INTEGER_TYPE as usize, - )), - }; - debug_assert!(value.is_integer()); - debug_assert_eq!(value.tag(), ValueTag::Integer); - value - } -} - -impl From for JsValue { - #[inline] - fn from(value: f64) -> Self { - if value.is_nan() { - return Self { - value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN as usize)), - }; - } - - let value = Self { - value: Cell::new(sptr::invalid_mut(value.to_bits() as usize)), - }; - debug_assert!(value.is_rational()); - debug_assert_eq!(value.tag(), ValueTag::Double); - value - } -} - -impl From for JsValue { - #[inline] - fn from(string: JsString) -> Self { - let string = ManuallyDrop::new(string); - let pointer = unsafe { JsString::into_void_ptr(string) }; - debug_assert_eq!( - pointer.addr() & MASK_POINTER_PAYLOAD as usize, - pointer.addr() - ); - let value = Self { - value: Cell::new(pointer.map_addr(|addr| addr | STRING_TYPE as usize)), - }; - debug_assert!(value.is_string()); - debug_assert_eq!(value.tag(), ValueTag::String); - value - } -} - -impl From for JsValue { - #[inline] - fn from(object: JsObject) -> Self { - let object = ManuallyDrop::new(object); - let pointer = unsafe { JsObject::into_void_ptr(object) }; - debug_assert_eq!( - pointer.addr() & MASK_POINTER_PAYLOAD as usize, - pointer.addr() - ); - debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); - let value = Self { - value: Cell::new(pointer.map_addr(|addr| addr | OBJECT_TYPE as usize)), - }; - debug_assert!(value.is_object()); - debug_assert_eq!(value.tag(), ValueTag::Object); - value - } -} - -impl From for JsValue { - #[inline] - fn from(symbol: JsSymbol) -> Self { - let symbol = ManuallyDrop::new(symbol); - let pointer = unsafe { JsSymbol::into_void_ptr(symbol) }; - debug_assert_eq!( - pointer.addr() & MASK_POINTER_PAYLOAD as usize, - pointer.addr() - ); - let value = Self { - value: Cell::new(pointer.map_addr(|addr| addr | SYMBOL_TYPE as usize)), - }; - debug_assert!(value.is_symbol()); - debug_assert_eq!(value.tag(), ValueTag::Symbol); - value - } -} - -impl From for JsValue { - #[inline] - fn from(bigint: JsBigInt) -> Self { - let bigint = ManuallyDrop::new(bigint); - let pointer = unsafe { JsBigInt::into_void_ptr(bigint) }; - debug_assert_eq!( - pointer.addr() & MASK_POINTER_PAYLOAD as usize, - pointer.addr() - ); - let value = Self { - value: Cell::new(pointer.map_addr(|addr| addr | BIGINT_TYPE as usize)), - }; - debug_assert!(value.is_bigint()); - debug_assert_eq!(value.tag(), ValueTag::BigInt); - value - } -} - -/// This abstracts over every pointer type boxed inside `NaN` values. -/// -/// # Safety -/// -/// Non-exhaustive list of situations that could cause undefined behaviour: -/// - Returning an invalid `*mut ()`. -/// - Returning a `ManuallyDrop` that doesn't correspond with the provided -/// `ptr`. -/// - Dropping `ty` before returning its pointer. -pub(crate) unsafe trait PointerType { - unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop; - - unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); -} - -/// Represents a reference to a boxed pointer type inside a [`JsValue`] -/// -/// This is exclusively used to return references to [`JsString`], [`JsObject`], -/// [`JsSymbol`] and [`JsBigInt`], since `NaN` boxing makes returning proper references -/// difficult. It is mainly returned by the [`JsValue::variant`] method and the -/// `as_` methods for checked casts to pointer types. -/// -/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion -/// to a proper [`reference`] by using the `ref` keyword or the -/// [`Option::as_deref`][`std::option::Option::as_deref`] method. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Ref<'a, T> { - inner: ManuallyDrop, - _marker: PhantomData<&'a T>, -} - -impl Ref<'_, T> { - #[inline] - fn new(inner: ManuallyDrop) -> Self { - Self { - inner, - _marker: PhantomData, - } - } -} - -impl std::borrow::Borrow for Ref<'_, T> { - #[inline] - fn borrow(&self) -> &T { - &**self - } -} - -// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the -// `as_ref` implementations of `T`. -impl AsRef for Ref<'_, T> -where - T: AsRef, -{ - #[inline] - fn as_ref(&self) -> &U { - >::as_ref(&*self) - } -} - -impl std::ops::Deref for Ref<'_, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &*self.inner - } -} - -impl PartialEq for Ref<'_, T> { - #[inline] - fn eq(&self, other: &T) -> bool { - &**self == other - } -} - -impl Drop for JsValue { - #[inline] - fn drop(&mut self) { - unsafe { - match self.tag() { - ValueTag::Object => { - ManuallyDrop::into_inner(JsObject::from_void_ptr(self.as_pointer())); - } - ValueTag::String => { - ManuallyDrop::into_inner(JsString::from_void_ptr(self.as_pointer())); - } - ValueTag::Symbol => { - ManuallyDrop::into_inner(JsSymbol::from_void_ptr(self.as_pointer())); - } - ValueTag::BigInt => { - ManuallyDrop::into_inner(JsBigInt::from_void_ptr(self.as_pointer())); - } - _ => {} - } - } - } -} - -impl Clone for JsValue { - #[inline] - fn clone(&self) -> Self { - unsafe { - match self.tag() { - ValueTag::Object => Self::new(self.as_object_unchecked().clone()), - ValueTag::String => Self::new(self.as_string_unchecked().clone()), - ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), - ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), - _ => Self { - value: Cell::new(self.value.get()), - }, - } - } - } -} - -impl Finalize for JsValue {} - -unsafe impl Trace for JsValue { - unsafe fn trace(&self) { - if let Some(o) = self.as_object() { - // SAFETY: `self.as_object()` must always return a valid `JsObject - unsafe { - o.trace(); - } - } - } - - unsafe fn root(&self) { - if self.tag() == ValueTag::Object { - // SAFETY: Implementors of `PointerType` must guarantee the - // safety of both `from_void_ptr` and `into_void_ptr` - unsafe { - let o = JsObject::from_void_ptr(self.as_pointer()); - o.root(); - self.value - .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE as usize)); - } - } - } - - unsafe fn unroot(&self) { - if self.tag() == ValueTag::Object { - // SAFETY: Implementors of `PointerType` must guarantee the - // safety of both `from_void_ptr` and `into_void_ptr` - unsafe { - let o = JsObject::from_void_ptr(self.as_pointer()); - o.unroot(); - self.value - .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE as usize)); - } - } - } - - #[inline] - fn finalize_glue(&self) { - if let Some(o) = self.as_object() { - o.finalize_glue(); - } - } -} -#[cfg(test)] -mod tests_nan_box { - use super::*; - - #[test] - fn bigint() { - let value = JsValue::new(JsBigInt::new(12345)); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - assert!(!value.is_boolean()); - assert!(!value.is_object()); - assert!(!value.is_string()); - assert!(!value.is_symbol()); - - assert!(value.is_bigint()); - - let bigint = value.as_bigint().unwrap(); - - assert_eq!(&bigint, &JsBigInt::new(12345)); - } - - #[test] - fn symbol() { - let value = JsValue::new(JsSymbol::new(Some("description...".into()))); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - assert!(!value.is_boolean()); - assert!(!value.is_object()); - assert!(!value.is_string()); - - assert!(value.is_symbol()); - - let symbol = value.as_symbol().unwrap(); - - assert_eq!(symbol.description(), Some("description...".into())); - } - - #[test] - fn string() { - let value = JsValue::new("I am a string :)"); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - assert!(!value.is_boolean()); - assert!(!value.is_object()); - - assert!(value.is_string()); - - let string = value.as_string().unwrap(); - - assert_eq!(JsString::refcount(&string), Some(1)); - - assert_eq!(*string, "I am a string :)"); - } - - #[test] - fn object() { - //let context = Context::default(); - - let o1 = JsObject::from_proto_and_data(None, ObjectData::ordinary()); - - // let value = JsValue::new(context.construct_object()); - let value = JsValue::new(o1.clone()); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - assert!(!value.is_boolean()); - - assert!(value.is_object()); - - let o2 = value.as_object().unwrap(); - assert!(JsObject::equals(&o1, &o2)); - } - - #[test] - fn boolean() { - let value = JsValue::new(true); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - - assert!(value.is_boolean()); - assert_eq!(value.as_boolean(), Some(true)); - - let value = JsValue::new(false); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - assert!(!value.is_rational()); - - assert!(value.is_boolean()); - assert_eq!(value.as_boolean(), Some(false)); - } - - #[test] - fn rational() { - let value = JsValue::new(1.3); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - assert!(!value.is_i32()); - - assert!(value.is_rational()); - assert_eq!(value.as_rational(), Some(1.3)); - - let value = JsValue::new(f64::MAX); - assert!(value.is_rational()); - assert_eq!(value.as_rational(), Some(f64::MAX)); - - let value = JsValue::new(f64::MIN); - assert!(value.is_rational()); - assert_eq!(value.as_rational(), Some(f64::MIN)); - - let value = JsValue::nan(); - assert!(value.is_rational()); - assert!(value.as_rational().unwrap().is_nan()); - - let value = JsValue::new(12345); - assert!(!value.is_rational()); - assert_eq!(value.as_rational(), None); - - let value = JsValue::undefined(); - assert!(!value.is_rational()); - assert_eq!(value.as_rational(), None); - - let value = JsValue::null(); - assert!(!value.is_rational()); - assert_eq!(value.as_rational(), None); - } - - #[test] - fn undefined() { - let value = JsValue::undefined(); - - println!("{:?}", value); - println!("{:?}", UNDEFINED_TYPE); - - assert!(value.is_undefined()); - } - - #[test] - fn null() { - let value = JsValue::null(); - - assert!(value.is_null()); - assert!(value.is_null_or_undefined()); - assert!(!value.is_undefined()); - } - - #[test] - fn integer() { - let value = JsValue::new(-0xcafe); - - assert!(!value.is_null()); - assert!(!value.is_null_or_undefined()); - assert!(!value.is_undefined()); - - assert!(value.is_i32()); - - assert_eq!(value.as_i32(), Some(-0xcafe)); - - let value = JsValue::null(); - assert_eq!(value.as_i32(), None); - } -} - -impl JsValue { /// Creates a new number with `Infinity` value. #[inline] pub fn positive_infinity() -> Self { @@ -884,12 +116,6 @@ impl JsValue { self.as_object().filter(|obj| obj.is_constructor()) } - /// Returns true if the value is a 64-bit floating-point number. - #[inline] - pub fn is_double(&self) -> bool { - self.is_rational() - } - /// Returns true if the value is integer. #[inline] #[allow(clippy::float_cmp)] @@ -900,8 +126,8 @@ impl JsValue { if self.is_i32() { true - } else if self.is_rational() { - is_racional_intiger(self.as_rational_unchecked()) + } else if let Some(num) = self.as_rational() { + is_racional_intiger(num) } else { false } diff --git a/boa_engine/src/value/platform/default.rs b/boa_engine/src/value/platform/default.rs new file mode 100644 index 00000000000..62caf88d207 --- /dev/null +++ b/boa_engine/src/value/platform/default.rs @@ -0,0 +1,216 @@ +use boa_gc::{Finalize, Trace}; + +use super::JsVariant; + +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; + +pub type Ref<'a, T> = &'a T; + +/// A Javascript value +#[derive(Trace, Finalize, Debug, Clone)] +pub enum JsValue { + /// `null` - A null value, for when a value doesn't exist. + Null, + /// `undefined` - An undefined value, for when a field or index doesn't exist. + Undefined, + /// `boolean` - A `true` / `false` value, for if a certain criteria is met. + Boolean(bool), + /// `String` - A UTF-8 string, such as `"Hello, world"`. + String(JsString), + /// `Number` - A 64-bit floating point number, such as `3.1415` + Rational(f64), + /// `Number` - A 32-bit integer, such as `42`. + Integer(i32), + /// `BigInt` - holds any arbitrary large signed integer. + BigInt(JsBigInt), + /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. + Object(JsObject), + /// `Symbol` - A Symbol Primitive type. + Symbol(JsSymbol), +} + +impl JsValue { + /// Creates a new `undefined` value. + #[inline] + pub const fn undefined() -> Self { + Self::Undefined + } + + /// Creates a new `null` value. + #[inline] + pub const fn null() -> Self { + Self::Null + } + + /// Creates a new number with `NaN` value. + #[inline] + pub const fn nan() -> Self { + Self::Rational(f64::NAN) + } + + #[inline] + pub fn rational(rational: f64) -> Self { + Self::Rational(rational) + } + + #[inline] + pub fn integer(integer: i32) -> Self { + Self::Integer(integer) + } + + #[inline] + pub fn boolean(boolean: bool) -> Self { + Self::Boolean(boolean) + } + + #[inline] + pub fn object(object: JsObject) -> Self { + Self::Object(object) + } + + #[inline] + pub fn string(string: JsString) -> Self { + Self::String(string) + } + + #[inline] + pub fn symbol(symbol: JsSymbol) -> Self { + Self::Symbol(symbol) + } + + #[inline] + pub fn bigint(bigint: JsBigInt) -> Self { + Self::BigInt(bigint) + } + + #[inline] + pub fn as_rational(&self) -> Option { + match *self { + Self::Rational(rational) => Some(rational), + _ => None, + } + } + + #[inline] + pub fn as_i32(&self) -> Option { + match *self { + Self::Integer(integer) => Some(integer), + _ => None, + } + } + + #[inline] + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(*boolean), + _ => None, + } + } + + #[inline] + pub fn as_object(&self) -> Option<&JsObject> { + match *self { + Self::Object(ref o) => Some(o), + _ => None, + } + } + + /// Returns the string if the values is a string, otherwise `None`. + #[inline] + pub fn as_string(&self) -> Option<&JsString> { + match self { + Self::String(ref string) => Some(string), + _ => None, + } + } + + pub fn as_symbol(&self) -> Option<&JsSymbol> { + match self { + Self::Symbol(symbol) => Some(symbol), + _ => None, + } + } + + /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. + #[inline] + pub fn as_bigint(&self) -> Option<&JsBigInt> { + match self { + Self::BigInt(bigint) => Some(bigint), + _ => None, + } + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + matches!(self, Self::Undefined) + } + + /// Returns true if the value is null. + #[inline] + pub fn is_null(&self) -> bool { + matches!(self, Self::Null) + } + + /// Returns true if the value is a 64-bit floating-point number. + #[inline] + pub fn is_rational(&self) -> bool { + matches!(self, Self::Rational(_)) + } + + /// Returns true if the value is any of the representations of a 64-bit floating-point NaN. + #[inline] + pub fn is_nan(&self) -> bool { + matches!(self, Self::Rational(r) if r.is_nan()) + } + + /// Returns true if the value is a 32-bit signed integer number. + #[inline] + pub fn is_i32(&self) -> bool { + matches!(self, Self::Integer(_)) + } + + /// Returns true if the value is a boolean. + #[inline] + pub fn is_boolean(&self) -> bool { + matches!(self, Self::Boolean(_)) + } + + /// Returns true if the value is an object + #[inline] + pub fn is_object(&self) -> bool { + matches!(self, Self::Object(_)) + } + + /// Returns true if the value is a string. + #[inline] + pub fn is_string(&self) -> bool { + matches!(self, Self::String(_)) + } + + /// Returns true if the value is a symbol. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self, Self::Symbol(_)) + } + + /// Returns true if the value is a bigint. + #[inline] + pub fn is_bigint(&self) -> bool { + matches!(self, Self::BigInt(_)) + } + + pub fn variant(&self) -> JsVariant<'_> { + match self { + Self::Null => JsVariant::Null, + Self::Undefined => JsVariant::Undefined, + Self::Integer(i) => JsVariant::Integer(*i), + Self::Rational(d) => JsVariant::Rational(*d), + Self::Boolean(b) => JsVariant::Boolean(*b), + Self::Object(o) => JsVariant::Object(o), + Self::String(str) => JsVariant::String(str), + Self::Symbol(sym) => JsVariant::Symbol(sym), + Self::BigInt(b) => JsVariant::BigInt(b), + } + } +} diff --git a/boa_engine/src/value/platform/mod.rs b/boa_engine/src/value/platform/mod.rs new file mode 100644 index 00000000000..ce3e236a194 --- /dev/null +++ b/boa_engine/src/value/platform/mod.rs @@ -0,0 +1,74 @@ +use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; +use std::mem::ManuallyDrop; + +// Minimum required definition for a correct `JsValue` implementation: + +// pub const fn undefined() -> Self; +// pub const fn null() -> Self; +// pub const fn nan() -> Self; +// pub fn rational(f64) -> Self; +// pub fn integer(i32) -> Self; +// pub fn boolean(bool) -> Self; +// pub fn object(JsObject) -> Self; +// pub fn string(JsString) -> Self; +// pub fn symbol(JsSymbol) -> Self; +// pub fn bigint(JsBigInt) -> Self; + +// pub fn as_rational(&self) -> Option; +// pub fn as_i32(&self) -> Option; +// pub fn as_boolean(&self) -> Option; +// pub fn as_object(&self) -> Option>; +// pub fn as_string(&self) -> Option>; +// pub fn as_symbol(&self) -> Option>; +// pub fn as_bigint(&self) -> Option>; + +// pub fn is_undefined(&self) -> bool; +// pub fn is_null(&self) -> bool; +// pub fn is_nan(&self) -> bool; +// pub fn is_rational(&self) -> bool; +// pub fn is_i32(&self) -> bool; +// pub fn is_boolean(&self) -> bool; +// pub fn is_object(&self) -> bool; +// pub fn is_string(&self) -> bool; +// pub fn is_symbol(&self) -> bool; +// pub fn is_bigint(&self) -> bool; + +// pub fn variant(&self) -> JsVariant<'_>; + +cfg_if::cfg_if! { + if #[cfg(all(target_arch = "x86_64", target_pointer_width = "64", feature = "nan_boxing"))] { + mod x86_64; + pub use x86_64::*; + } else { + mod default; + pub use default::*; + } +} + +#[derive(Debug)] +pub enum JsVariant<'a> { + Null, + Undefined, + Rational(f64), + Integer(i32), + Boolean(bool), + String(Ref<'a, JsString>), + Symbol(Ref<'a, JsSymbol>), + BigInt(Ref<'a, JsBigInt>), + Object(Ref<'a, JsObject>), +} + +/// This abstracts over every pointer type boxed inside `NaN` values. +/// +/// # Safety +/// +/// Non-exhaustive list of situations that could cause undefined behaviour: +/// - Returning an invalid `*mut ()`. +/// - Returning a `ManuallyDrop` that doesn't correspond with the provided +/// `ptr`. +/// - Dropping `ty` before returning its pointer. +pub(crate) unsafe trait PointerType { + unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop; + + unsafe fn into_void_ptr(ty: ManuallyDrop) -> *mut (); +} diff --git a/boa_engine/src/value/platform/x86_64.rs b/boa_engine/src/value/platform/x86_64.rs new file mode 100644 index 00000000000..c400e950ce9 --- /dev/null +++ b/boa_engine/src/value/platform/x86_64.rs @@ -0,0 +1,732 @@ +#![allow(unstable_name_collisions)] + +use num_enum::TryFromPrimitive; +use std::cell::Cell; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +use boa_gc::{Finalize, Trace}; + +// TODO: Remove if/when https://github.com/rust-lang/rust/issues/95228 gets stabilized +use sptr::Strict; + +use super::JsVariant; + +use crate::{object::JsObject, value::PointerType, JsBigInt, JsString, JsSymbol}; + +// Our `cfg` options must ensure `usize == u64`. +// Using `usize`s only makes it more convenient to use in this module + +const SIGN_BIT: usize = 0x8000_0000_0000_0000; +const EXPONENT: usize = 0x7FF0_0000_0000_0000; +// const MANTISA: usize = 0x000F_FFFF_FFFF_FFFF; +const SIGNAL_BIT: usize = 0x0008_0000_0000_0000; +const QNAN: usize = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000 + +const CANONICALIZED_NAN: usize = QNAN; +// const PAYLOAD: usize = 0x0000_7FFF_FFFF_FFFF; +// const TYPE: usize = !PAYLOAD; + +const TAG_MASK: usize = 0xFFFF_0000_0000_0000; + +const DOUBLE_TYPE: usize = QNAN; +const INTEGER_TYPE: usize = QNAN | (0b001 << 48); +const BOOLEAN_TYPE: usize = QNAN | (0b010 << 48); +const UNDEFINED_TYPE: usize = QNAN | (0b011 << 48); +const NULL_TYPE: usize = QNAN | (0b100 << 48); + +#[allow(unused)] +const RESERVED1_TYPE: usize = QNAN | (0b101 << 48); +#[allow(unused)] +const RESERVED2_TYPE: usize = QNAN | (0b110 << 48); +#[allow(unused)] +const RESERVED3_TYPE: usize = QNAN | (0b111 << 48); + +const POINTER_TYPE: usize = SIGN_BIT | QNAN; +const OBJECT_TYPE: usize = POINTER_TYPE | (0b001 << 48); +const STRING_TYPE: usize = POINTER_TYPE | (0b010 << 48); +const SYMBOL_TYPE: usize = POINTER_TYPE | (0b011 << 48); +const BIGINT_TYPE: usize = POINTER_TYPE | (0b100 << 48); + +#[allow(unused)] +const RESERVED4_TYPE: usize = POINTER_TYPE | (0b101 << 48); +#[allow(unused)] +const RESERVED5_TYPE: usize = POINTER_TYPE | (0b110 << 48); +#[allow(unused)] +const RESERVED6_TYPE: usize = POINTER_TYPE | (0b111 << 48); + +const MASK_INT_PAYLOAD: usize = 0x00000000FFFFFFFF; +const MASK_POINTER_PAYLOAD: usize = 0x0000FFFFFFFFFFFF; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)] +#[repr(u16)] +enum ValueTag { + Double = (DOUBLE_TYPE >> 48) as _, + Integer = (INTEGER_TYPE >> 48) as _, + Boolean = (BOOLEAN_TYPE >> 48) as _, + Undefined = (UNDEFINED_TYPE >> 48) as _, + Null = (NULL_TYPE >> 48) as _, + Object = (OBJECT_TYPE >> 48) as _, + String = (STRING_TYPE >> 48) as _, + Symbol = (SYMBOL_TYPE >> 48) as _, + BigInt = (BIGINT_TYPE >> 48) as _, + // Reserved1 = RESERVED1_TYPE, + // Reserved2 = RESERVED2_TYPE, + // Reserved3 = RESERVED3_TYPE, + // Reserved4 = RESERVED4_TYPE, + // Reserved5 = RESERVED5_TYPE, + // Reserved6 = RESERVED6_TYPE, +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct JsValue { + value: Cell<*mut ()>, +} + +impl JsValue { + /// Creates a new `undefined` value. + #[inline] + pub const fn undefined() -> Self { + Self { + value: Cell::new(sptr::invalid_mut(UNDEFINED_TYPE)), + } + } + + /// Creates a new `null` value. + #[inline] + pub const fn null() -> Self { + Self { + value: Cell::new(sptr::invalid_mut(NULL_TYPE)), + } + } + + /// Creates a new number with `NaN` value. + #[inline] + pub const fn nan() -> Self { + Self { + value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN)), + } + } + + #[inline] + pub fn rational(rational: f64) -> Self { + if rational.is_nan() { + return Self::nan(); + } + + let value = Self { + value: Cell::new(sptr::invalid_mut(rational.to_bits() as usize)), + }; + debug_assert!(value.is_rational()); + debug_assert_eq!(value.tag(), ValueTag::Double); + value + } + + #[inline] + pub fn integer(integer: i32) -> Self { + let value = Self { + value: Cell::new(sptr::invalid_mut(integer as u32 as usize | INTEGER_TYPE)), + }; + debug_assert!(value.is_integer()); + debug_assert_eq!(value.tag(), ValueTag::Integer); + value + } + + #[inline] + pub fn boolean(boolean: bool) -> Self { + let value = Self { + value: Cell::new(sptr::invalid_mut(usize::from(boolean) | BOOLEAN_TYPE)), + }; + debug_assert!(value.is_boolean()); + debug_assert_eq!(value.tag(), ValueTag::Boolean); + value + } + + #[inline] + pub fn object(object: JsObject) -> Self { + let object = ManuallyDrop::new(object); + let pointer = unsafe { JsObject::into_void_ptr(object) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | OBJECT_TYPE)), + }; + debug_assert!(value.is_object()); + debug_assert_eq!(value.tag(), ValueTag::Object); + value + } + + #[inline] + pub fn string(string: JsString) -> Self { + let string = ManuallyDrop::new(string); + let pointer = unsafe { JsString::into_void_ptr(string) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | STRING_TYPE)), + }; + debug_assert!(value.is_string()); + debug_assert_eq!(value.tag(), ValueTag::String); + value + } + + #[inline] + pub fn symbol(symbol: JsSymbol) -> Self { + let symbol = ManuallyDrop::new(symbol); + let pointer = unsafe { JsSymbol::into_void_ptr(symbol) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | SYMBOL_TYPE)), + }; + debug_assert!(value.is_symbol()); + debug_assert_eq!(value.tag(), ValueTag::Symbol); + value + } + + #[inline] + pub fn bigint(bigint: JsBigInt) -> Self { + let bigint = ManuallyDrop::new(bigint); + let pointer = unsafe { JsBigInt::into_void_ptr(bigint) }; + debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr()); + let value = Self { + value: Cell::new(pointer.map_addr(|addr| addr | BIGINT_TYPE)), + }; + debug_assert!(value.is_bigint()); + debug_assert_eq!(value.tag(), ValueTag::BigInt); + value + } + + pub fn as_rational(&self) -> Option { + if self.is_rational() { + return Some(self.as_rational_unchecked()); + } + + None + } + + pub fn as_i32(&self) -> Option { + if self.is_i32() { + return Some(self.as_i32_uncheched()); + } + + None + } + + #[inline] + pub fn as_boolean(&self) -> Option { + if self.is_boolean() { + return Some(self.as_boolean_uncheched()); + } + + None + } + + pub fn as_object(&self) -> Option> { + if self.is_object() { + return unsafe { Some(self.as_object_unchecked()) }; + } + + None + } + + /// Returns the string if the values is a string, otherwise `None`. + #[inline] + pub fn as_string(&self) -> Option> { + if self.is_string() { + return unsafe { Some(self.as_string_unchecked()) }; + } + + None + } + + pub fn as_symbol(&self) -> Option> { + if self.is_symbol() { + return unsafe { Some(self.as_symbol_unchecked()) }; + } + + None + } + + pub fn as_bigint(&self) -> Option> { + if self.is_bigint() { + return unsafe { Some(self.as_bigint_unchecked()) }; + } + + None + } + + /// Returns true if the value is undefined. + #[inline] + pub fn is_undefined(&self) -> bool { + self.value.get().addr() == UNDEFINED_TYPE + } + + /// Returns true if the value is null. + #[inline] + pub fn is_null(&self) -> bool { + self.value.get().addr() == NULL_TYPE + } + + pub fn is_rational(&self) -> bool { + (self.value.get().addr() & !SIGN_BIT) <= QNAN + } + + pub fn is_nan(&self) -> bool { + self.value.get().addr() == CANONICALIZED_NAN + } + + pub fn is_i32(&self) -> bool { + self.value.get().addr() & TAG_MASK == INTEGER_TYPE + } + + /// Returns true if the value is a boolean. + #[inline] + pub fn is_boolean(&self) -> bool { + self.value.get().addr() & TAG_MASK == BOOLEAN_TYPE + } + + /// Returns true if the value is an object + #[inline] + pub fn is_object(&self) -> bool { + self.value.get().addr() & TAG_MASK == OBJECT_TYPE + } + + /// Returns true if the value is a string. + #[inline] + pub fn is_string(&self) -> bool { + self.value.get().addr() & TAG_MASK == STRING_TYPE + } + + pub fn is_symbol(&self) -> bool { + self.value.get().addr() & TAG_MASK == SYMBOL_TYPE + } + + /// Returns true if the value is a bigint. + #[inline] + pub fn is_bigint(&self) -> bool { + self.value.get().addr() & TAG_MASK == BIGINT_TYPE + } + + pub fn variant(&self) -> JsVariant<'_> { + unsafe { + match self.tag() { + ValueTag::Null => JsVariant::Null, + ValueTag::Undefined => JsVariant::Undefined, + ValueTag::Integer => JsVariant::Integer(self.as_i32_uncheched()), + ValueTag::Double => JsVariant::Rational(self.as_rational_unchecked()), + ValueTag::Boolean => JsVariant::Boolean(self.as_boolean_uncheched()), + ValueTag::Object => JsVariant::Object(self.as_object_unchecked()), + ValueTag::String => JsVariant::String(self.as_string_unchecked()), + ValueTag::Symbol => JsVariant::Symbol(self.as_symbol_unchecked()), + ValueTag::BigInt => JsVariant::BigInt(self.as_bigint_unchecked()), + } + } + } + + fn as_pointer(&self) -> *mut () { + self.value + .get() + .map_addr(|addr| addr & MASK_POINTER_PAYLOAD) + } + + fn tag(&self) -> ValueTag { + if self.is_rational() { + return ValueTag::Double; + } + let tag = ((self.value.get().addr() & TAG_MASK) >> 48) as u16; + ValueTag::try_from(tag).expect("Implementation must never construct an invalid tag") + } + + fn as_rational_unchecked(&self) -> f64 { + f64::from_bits(self.value.get().addr() as u64) + } + + fn as_i32_uncheched(&self) -> i32 { + (self.value.get().addr() & MASK_INT_PAYLOAD) as u32 as i32 + } + + fn as_boolean_uncheched(&self) -> bool { + (self.value.get().addr() & 0xFF) != 0 + } + + /// Returns a reference to the boxed [`JsObject`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsObject`] is undefined behaviour. + unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> { + unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsString`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsString`] is undefined behaviour. + unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> { + unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsSymbol`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsSymbol`] is undefined behaviour. + unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> { + unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) } + } + + /// Returns a reference to the boxed [`JsBigInt`] without checking + /// if the tag of `self` is valid. + /// + /// # Safety + /// + /// Calling this method with a [`JsValue`] that doesn't box + /// a [`JsBigInt`] is undefined behaviour. + #[inline] + unsafe fn as_bigint_unchecked(&self) -> Ref<'_, JsBigInt> { + // SAFETY: The safety contract must be upheld by the caller + unsafe { Ref::new(JsBigInt::from_void_ptr(self.as_pointer())) } + } +} + +impl Drop for JsValue { + #[inline] + fn drop(&mut self) { + unsafe { + match self.tag() { + ValueTag::Object => { + ManuallyDrop::into_inner(JsObject::from_void_ptr(self.as_pointer())); + } + ValueTag::String => { + ManuallyDrop::into_inner(JsString::from_void_ptr(self.as_pointer())); + } + ValueTag::Symbol => { + ManuallyDrop::into_inner(JsSymbol::from_void_ptr(self.as_pointer())); + } + ValueTag::BigInt => { + ManuallyDrop::into_inner(JsBigInt::from_void_ptr(self.as_pointer())); + } + _ => {} + } + } + } +} + +impl Clone for JsValue { + #[inline] + fn clone(&self) -> Self { + unsafe { + match self.tag() { + ValueTag::Object => Self::new(self.as_object_unchecked().clone()), + ValueTag::String => Self::new(self.as_string_unchecked().clone()), + ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()), + ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()), + _ => Self { + value: Cell::new(self.value.get()), + }, + } + } + } +} + +impl Finalize for JsValue {} + +unsafe impl Trace for JsValue { + unsafe fn trace(&self) { + if let Some(o) = self.as_object() { + // SAFETY: `self.as_object()` must always return a valid `JsObject + unsafe { + o.trace(); + } + } + } + + unsafe fn root(&self) { + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.root(); + self.value + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE)); + } + } + } + + unsafe fn unroot(&self) { + if self.tag() == ValueTag::Object { + // SAFETY: Implementors of `PointerType` must guarantee the + // safety of both `from_void_ptr` and `into_void_ptr` + unsafe { + let o = JsObject::from_void_ptr(self.as_pointer()); + o.unroot(); + self.value + .set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE)); + } + } + } + + #[inline] + fn finalize_glue(&self) { + if let Some(o) = self.as_object() { + o.finalize_glue(); + } + } +} + +/// Represents a reference to a boxed pointer type inside a [`JsValue`] +/// +/// This is exclusively used to return references to [`JsString`], [`JsObject`], +/// [`JsSymbol`] and [`JsBigInt`], since `NaN` boxing makes returning proper references +/// difficult. It is mainly returned by the [`JsValue::variant`] method and the +/// `as_` methods for checked casts to pointer types. +/// +/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion +/// to a proper [`reference`] by using the `ref` keyword or the +/// [`Option::as_deref`][`std::option::Option::as_deref`] method. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ref<'a, T> { + inner: ManuallyDrop, + _marker: PhantomData<&'a T>, +} + +impl Ref<'_, T> { + #[inline] + fn new(inner: ManuallyDrop) -> Self { + Self { + inner, + _marker: PhantomData, + } + } +} + +// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the +// `as_ref` implementations of `T`. +impl AsRef for Ref<'_, T> +where + T: AsRef, +{ + #[inline] + fn as_ref(&self) -> &U { + >::as_ref(&*self) + } +} + +impl std::ops::Deref for Ref<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl PartialEq for Ref<'_, T> { + #[inline] + fn eq(&self, other: &T) -> bool { + &**self == other + } +} + +impl std::borrow::Borrow for Ref<'_, T> { + #[inline] + fn borrow(&self) -> &T { + &**self + } +} + +#[cfg(test)] +mod tests_nan_box { + use crate::object::ObjectData; + + use super::*; + + #[test] + fn bigint() { + let value = JsValue::new(JsBigInt::new(12345)); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + assert!(!value.is_symbol()); + + assert!(value.is_bigint()); + + let bigint = value.as_bigint().unwrap(); + + assert_eq!(&bigint, &JsBigInt::new(12345)); + } + + #[test] + fn symbol() { + let value = JsValue::new(JsSymbol::new(Some("description...".into()))); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + assert!(!value.is_string()); + + assert!(value.is_symbol()); + + let symbol = value.as_symbol().unwrap(); + + assert_eq!(symbol.description(), Some("description...".into())); + } + + #[test] + fn string() { + let value = JsValue::new("I am a string :)"); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + assert!(!value.is_object()); + + assert!(value.is_string()); + + let string = value.as_string().unwrap(); + + assert_eq!(JsString::refcount(&string), Some(1)); + + assert_eq!(*string, "I am a string :)"); + } + + #[test] + fn object() { + //let context = Context::default(); + + let o1 = JsObject::from_proto_and_data(None, ObjectData::ordinary()); + + // let value = JsValue::new(context.construct_object()); + let value = JsValue::new(o1.clone()); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + assert!(!value.is_boolean()); + + assert!(value.is_object()); + + let o2 = value.as_object().unwrap(); + assert!(JsObject::equals(&o1, &o2)); + } + + #[test] + fn boolean() { + let value = JsValue::new(true); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(true)); + + let value = JsValue::new(false); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + assert!(!value.is_rational()); + + assert!(value.is_boolean()); + assert_eq!(value.as_boolean(), Some(false)); + } + + #[test] + fn rational() { + let value = JsValue::new(1.3); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + assert!(!value.is_i32()); + + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(1.3)); + + let value = JsValue::new(f64::MAX); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MAX)); + + let value = JsValue::new(f64::MIN); + assert!(value.is_rational()); + assert_eq!(value.as_rational(), Some(f64::MIN)); + + let value = JsValue::nan(); + assert!(value.is_rational()); + assert!(value.as_rational().unwrap().is_nan()); + + let value = JsValue::new(12345); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::undefined(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + + let value = JsValue::null(); + assert!(!value.is_rational()); + assert_eq!(value.as_rational(), None); + } + + #[test] + fn undefined() { + let value = JsValue::undefined(); + + println!("{:?}", value); + println!("{:?}", UNDEFINED_TYPE); + + assert!(value.is_undefined()); + } + + #[test] + fn null() { + let value = JsValue::null(); + + assert!(value.is_null()); + assert!(value.is_null_or_undefined()); + assert!(!value.is_undefined()); + } + + #[test] + fn integer() { + let value = JsValue::new(-0xcafe); + + assert!(!value.is_null()); + assert!(!value.is_null_or_undefined()); + assert!(!value.is_undefined()); + + assert!(value.is_i32()); + + assert_eq!(value.as_i32(), Some(-0xcafe)); + + let value = JsValue::null(); + assert_eq!(value.as_i32(), None); + } +} From f67c19752cbbd3f482fee4ed6813c632e3dcdf77 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 20 May 2022 17:38:38 -0500 Subject: [PATCH 8/8] Mirror `Ref` type into `default` --- boa_engine/src/builtins/function/mod.rs | 2 +- boa_engine/src/builtins/generator/mod.rs | 4 +- boa_engine/src/builtins/json/mod.rs | 4 +- boa_engine/src/builtins/map/mod.rs | 2 +- boa_engine/src/builtins/object/mod.rs | 2 +- boa_engine/src/builtins/reflect/mod.rs | 4 +- boa_engine/src/builtins/regexp/mod.rs | 6 +- boa_engine/src/builtins/set/mod.rs | 6 +- boa_engine/src/object/jstypedarray.rs | 4 +- boa_engine/src/value/equality.rs | 4 +- boa_engine/src/value/mod.rs | 12 ++-- boa_engine/src/value/platform/default.rs | 75 +++++++++++++++++++----- 12 files changed, 84 insertions(+), 41 deletions(-) diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 1c53518069c..4cb189893d2 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -344,7 +344,7 @@ impl BuiltInFunctionObject { context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method") })?; - let this_arg = args.get_or_undefined(0).clone(); + let this_arg = args.get_or_undefined(0); let bound_args = args.get(1..).unwrap_or(&[]).to_vec(); let arg_count = bound_args.len() as i64; diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index c3f3280ae45..43cc20e0c02 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -173,7 +173,7 @@ impl Generator { // 1. Let g be the this value. // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) + Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0)), context) } /// `Generator.prototype.throw ( exception )` @@ -195,7 +195,7 @@ impl Generator { // 1. Let g be the this value. // 2. Let C be ThrowCompletion(exception). // 3. Return ? GeneratorResumeAbrupt(g, C, empty). - Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) + Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0)), context) } /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 172a1a99f33..3653bdd8999 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -280,7 +280,7 @@ impl Json { } } - let mut space = args.get_or_undefined(2).clone(); + let mut space = args.get_or_undefined(2); // 5. If Type(space) is Object, then if let Some(space_obj) = space.as_object() { @@ -332,7 +332,7 @@ impl Json { // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). wrapper - .create_data_property_or_throw("", args.get_or_undefined(0).clone(), context) + .create_data_property_or_throw("", args.get_or_undefined(0), context) .expect("CreateDataPropertyOrThrow should never fail here"); // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 1d5877ab394..466ec521b09 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -238,7 +238,7 @@ impl Map { // i. Set p.[[Value]] to value. // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. // 7. Append p as the last element of entries. - map.insert(key, value.clone()); + map.insert(key, value); // ii. Return M. // 8. Return M. return Ok(this.clone()); diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 7cacdeca2da..b3bb13117c4 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -393,7 +393,7 @@ impl Object { if !v.is_object() { return Ok(JsValue::new(false)); } - let mut v = v.clone(); + let mut v = v; let o = JsValue::new(this.to_object(context)?); loop { v = Self::get_prototype_of(this, &[v], context)?; diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 2a020805bc2..87e61002bcb 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -365,9 +365,7 @@ impl Reflect { } else { target.clone().into() }; - Ok(target - .__set__(key, value.clone(), receiver, context)? - .into()) + Ok(target.__set__(key, value, receiver, context)?.into()) } /// Sets the prototype of an object. diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index c52399cdde7..3e7b436e3b2 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -214,12 +214,12 @@ impl RegExp { JsValue::new(regexp.original_flags.clone()), ) } else { - (JsValue::new(regexp.original_source.clone()), flags.clone()) + (JsValue::new(regexp.original_source.clone()), flags) } } else { // a. Let P be pattern. // b. Let F be flags. - (pattern.clone(), flags.clone()) + (pattern.clone(), flags) }; // 7. Let O be ? RegExpAlloc(newTarget). @@ -1254,7 +1254,7 @@ impl RegExp { let length_arg_str = arg_str.encode_utf16().count(); // 5. Let functionalReplace be IsCallable(replaceValue). - let mut replace_value = args.get_or_undefined(1).clone(); + let mut replace_value = args.get_or_undefined(1); let functional_replace = replace_value .as_object() .as_deref() diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 4ef710fdf34..e7f36c0b417 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -142,7 +142,7 @@ impl Set { })?; // 7. Let iteratorRecord be ? GetIterator(iterable). - let iterator_record = iterable.clone().get_iterator(context, None, None)?; + let iterator_record = iterable.get_iterator(context, None, None)?; // 8. Repeat, // a. Let next be ? IteratorStep(iteratorRecord). @@ -202,7 +202,7 @@ impl Set { set.add(if value.as_number().map_or(false, |n| n == -0f64) { JsValue::new(0) } else { - value.clone() + value }); } else { return context.throw_type_error("'this' is not a Set"); @@ -327,7 +327,7 @@ impl Set { let this_arg = if this_arg.is_undefined() { context.global_object().clone().into() } else { - this_arg.clone() + this_arg }; let mut index = 0; diff --git a/boa_engine/src/object/jstypedarray.rs b/boa_engine/src/object/jstypedarray.rs index 389d6d77475..c72b2ffcc38 100644 --- a/boa_engine/src/object/jstypedarray.rs +++ b/boa_engine/src/object/jstypedarray.rs @@ -20,9 +20,7 @@ impl JsTypedArray { #[inline] pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { if object.borrow().is_typed_array() { - Ok(Self { - inner: object.into(), - }) + Ok(Self { inner: object }) } else { context.throw_type_error("object is not a TypedArray") } diff --git a/boa_engine/src/value/equality.rs b/boa_engine/src/value/equality.rs index 421323ddf60..154128281bb 100644 --- a/boa_engine/src/value/equality.rs +++ b/boa_engine/src/value/equality.rs @@ -194,7 +194,9 @@ impl JsValue { fn same_value_non_numeric(x: &Self, y: &Self) -> bool { debug_assert!(x.get_type() == y.get_type()); match (x.variant(), y.variant()) { - (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => true, + (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => { + true + } (JsVariant::String(x), JsVariant::String(y)) => x == y, (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y, (JsVariant::Object(ref x), JsVariant::Object(ref y)) => JsObject::equals(x, y), diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 29bd39acc99..bf229660bbe 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -262,7 +262,9 @@ impl JsValue { pub fn to_bigint(&self, context: &mut Context) -> JsResult { match self.variant() { JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"), - JsVariant::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), + JsVariant::Undefined => { + context.throw_type_error("cannot convert undefined to a BigInt") + } JsVariant::String(string) => { let string = &*string; if let Some(value) = JsBigInt::from_string(string) { @@ -410,11 +412,11 @@ impl JsValue { _ => { let primitive = self.to_primitive(context, PreferredType::String)?; match primitive.variant() { - JsVariant::String(string) => string.clone().into(), - JsVariant::Symbol(symbol) => symbol.clone().into(), - _ => primitive.to_string(context)?.into(), + JsVariant::String(string) => string.clone().into(), + JsVariant::Symbol(symbol) => symbol.clone().into(), + _ => primitive.to_string(context)?.into(), + } } - }, }) } diff --git a/boa_engine/src/value/platform/default.rs b/boa_engine/src/value/platform/default.rs index 62caf88d207..b91177bf8a2 100644 --- a/boa_engine/src/value/platform/default.rs +++ b/boa_engine/src/value/platform/default.rs @@ -4,8 +4,6 @@ use super::JsVariant; use crate::{object::JsObject, JsBigInt, JsString, JsSymbol}; -pub type Ref<'a, T> = &'a T; - /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] pub enum JsValue { @@ -108,34 +106,34 @@ impl JsValue { } #[inline] - pub fn as_object(&self) -> Option<&JsObject> { - match *self { - Self::Object(ref o) => Some(o), + pub fn as_object(&self) -> Option> { + match self { + Self::Object(inner) => Some(Ref { inner }), _ => None, } } /// Returns the string if the values is a string, otherwise `None`. #[inline] - pub fn as_string(&self) -> Option<&JsString> { + pub fn as_string(&self) -> Option> { match self { - Self::String(ref string) => Some(string), + Self::String(inner) => Some(Ref { inner }), _ => None, } } - pub fn as_symbol(&self) -> Option<&JsSymbol> { + pub fn as_symbol(&self) -> Option> { match self { - Self::Symbol(symbol) => Some(symbol), + Self::Symbol(inner) => Some(Ref { inner }), _ => None, } } /// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive. #[inline] - pub fn as_bigint(&self) -> Option<&JsBigInt> { + pub fn as_bigint(&self) -> Option> { match self { - Self::BigInt(bigint) => Some(bigint), + Self::BigInt(inner) => Some(Ref { inner }), _ => None, } } @@ -158,7 +156,7 @@ impl JsValue { matches!(self, Self::Rational(_)) } - /// Returns true if the value is any of the representations of a 64-bit floating-point NaN. + /// Returns true if the value is any of the representations of a 64-bit floating-point `NaN`. #[inline] pub fn is_nan(&self) -> bool { matches!(self, Self::Rational(r) if r.is_nan()) @@ -207,10 +205,55 @@ impl JsValue { Self::Integer(i) => JsVariant::Integer(*i), Self::Rational(d) => JsVariant::Rational(*d), Self::Boolean(b) => JsVariant::Boolean(*b), - Self::Object(o) => JsVariant::Object(o), - Self::String(str) => JsVariant::String(str), - Self::Symbol(sym) => JsVariant::Symbol(sym), - Self::BigInt(b) => JsVariant::BigInt(b), + Self::Object(inner) => JsVariant::Object(Ref { inner }), + Self::String(inner) => JsVariant::String(Ref { inner }), + Self::Symbol(inner) => JsVariant::Symbol(Ref { inner }), + Self::BigInt(inner) => JsVariant::BigInt(Ref { inner }), } } } + +/// Represents a reference to a pointer type inside a [`JsValue`] +/// +/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion +/// to a proper [`reference`] by using the `ref` keyword or the +/// [`Option::as_deref`][`std::option::Option::as_deref`] method. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ref<'a, T> { + inner: &'a T, +} + +// Lift `Ref` over `AsRef`, since implementing `AsRef` would override the +// `as_ref` implementations of `T`. +impl AsRef for Ref<'_, T> +where + T: AsRef, +{ + #[inline] + fn as_ref(&self) -> &U { + >::as_ref(self.inner) + } +} + +impl std::ops::Deref for Ref<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + self.inner + } +} + +impl PartialEq for Ref<'_, T> { + #[inline] + fn eq(&self, other: &T) -> bool { + self.inner == other + } +} + +impl std::borrow::Borrow for Ref<'_, T> { + #[inline] + fn borrow(&self) -> &T { + self.inner + } +}