diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 4cf03d3790..44c23e5345 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -311,17 +311,17 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join - pub(crate) fn join(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + pub(crate) fn join(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let separator = if args.is_empty() { String::from(",") } else { - args.get(0).expect("Could not get argument").to_string() + ctx.to_string(args.get(0).expect("Could not get argument"))? }; let mut elem_strs: Vec = Vec::new(); let length = i32::from(&this.get_field("length")); for n in 0..length { - let elem_str: String = this.get_field(n.to_string()).to_string(); + let elem_str: String = ctx.to_string(&this.get_field(n.to_string()))?; elem_strs.push(elem_str); } @@ -344,7 +344,7 @@ impl Array { pub(crate) fn to_string( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { let method_name = "join"; let mut arguments = vec![Value::from(",")]; @@ -352,7 +352,7 @@ impl Array { let mut method = this.get_field(method_name); // 3. if !method.is_function() { - method = _ctx + method = ctx .realm .global_obj .get_field("Object") @@ -362,15 +362,15 @@ impl Array { arguments = Vec::new(); } // 4. - let join_result = _ctx.call(&method, this, &arguments); - let match_string = match join_result { - Ok(v) => match *v { - ValueData::String(ref s) => (*s).clone(), - _ => "".to_string(), - }, - Err(v) => format!("error: {}", v), + let join = ctx.call(&method, this, &arguments)?; + + let string = if let ValueData::String(ref s) = join.data() { + Value::from(s.as_str()) + } else { + Value::from("") }; - Ok(Value::from(match_string)) + + Ok(string) } /// `Array.prototype.reverse()` diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index f01b4573c6..5b38583396 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -53,7 +53,7 @@ impl BigInt { return Err(RangeError::run_new( format!( "{} can't be converted to BigInt because it isn't an integer", - value + ctx.to_string(value)? ), ctx, )?); @@ -64,6 +64,18 @@ impl BigInt { Ok(data) } + #[inline] + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_native_string_radix(bigint: &AstBigInt, radix: u32) -> String { + bigint.to_str_radix(radix) + } + + #[inline] + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_native_string(bigint: &AstBigInt) -> String { + bigint.to_string() + } + /// `BigInt.prototype.toString( [radix] )` /// /// The `toString()` method returns a string representing the specified BigInt object. @@ -91,9 +103,10 @@ impl BigInt { ctx, )?); } - Ok(Value::from( - this.to_bigint().unwrap().to_str_radix(radix as u32), - )) + Ok(Value::from(Self::to_native_string_radix( + &this.to_bigint().unwrap(), + radix as u32, + ))) } /// `BigInt.prototype.valueOf()` diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index b181e21745..64428f0e9c 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -69,11 +69,11 @@ pub fn logger(msg: LogMessage, console_state: &ConsoleState) { } /// This represents the `console` formatter. -pub fn formatter(data: &[Value]) -> String { - let target = get_arg_at_index::(data, 0).unwrap_or_default(); +pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result { + let target = ctx.to_string(&data.get(0).cloned().unwrap_or_default())?; match data.len() { - 0 => String::new(), - 1 => target, + 0 => Ok(String::new()), + 1 => Ok(target), _ => { let mut formatted = String::new(); let mut arg_index = 1; @@ -103,7 +103,7 @@ pub fn formatter(data: &[Value]) -> String { /* string */ 's' => { let arg = - get_arg_at_index::(data, arg_index).unwrap_or_default(); + ctx.to_string(&data.get(arg_index).cloned().unwrap_or_default())?; formatted.push_str(&arg); arg_index += 1 } @@ -124,7 +124,7 @@ pub fn formatter(data: &[Value]) -> String { formatted.push_str(&format!(" {}", rest)) } - formatted + Ok(formatted) } } } @@ -140,7 +140,7 @@ pub fn formatter(data: &[Value]) -> String { /// /// [spec]: https://console.spec.whatwg.org/#assert /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert -pub fn assert(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn assert(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let assertion = get_arg_at_index::(args, 0).unwrap_or_default(); if !assertion { @@ -155,9 +155,10 @@ pub fn assert(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVa args[0] = Value::from(concat); } - this.with_internal_state_ref(|state| { - logger(LogMessage::Error(formatter(&args[..])), state) - }); + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Error(formatter(&args, ctx)?), state); + Ok(()) + })?; } Ok(Value::undefined()) @@ -191,8 +192,11 @@ pub fn clear(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://console.spec.whatwg.org/#debug /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug -pub fn debug(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); +pub fn debug(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Log(formatter(args, ctx)?), state); + Ok(()) + })?; Ok(Value::undefined()) } @@ -206,8 +210,11 @@ pub fn debug(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal /// /// [spec]: https://console.spec.whatwg.org/#error /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error -pub fn error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|state| logger(LogMessage::Error(formatter(&args[..])), state)); +pub fn error(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Error(formatter(args, ctx)?), state); + Ok(()) + })?; Ok(Value::undefined()) } @@ -221,8 +228,11 @@ pub fn error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal /// /// [spec]: https://console.spec.whatwg.org/#info /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info -pub fn info(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|state| logger(LogMessage::Info(formatter(&args[..])), state)); +pub fn info(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Info(formatter(args, ctx)?), state); + Ok(()) + })?; Ok(Value::undefined()) } @@ -236,8 +246,11 @@ pub fn info(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://console.spec.whatwg.org/#log /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log -pub fn log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); +pub fn log(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Log(formatter(args, ctx)?), state); + Ok(()) + })?; Ok(Value::undefined()) } @@ -251,9 +264,12 @@ pub fn log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://console.spec.whatwg.org/#trace /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace -pub fn trace(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn trace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { if !args.is_empty() { - this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Log(formatter(args, ctx)?), state); + Ok(()) + })?; /* TODO: get and print stack trace */ this.with_internal_state_ref(|state| { @@ -277,8 +293,11 @@ pub fn trace(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal /// /// [spec]: https://console.spec.whatwg.org/#warn /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn -pub fn warn(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|state| logger(LogMessage::Warn(formatter(&args[..])), state)); +pub fn warn(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| { + logger(LogMessage::Warn(formatter(args, ctx)?), state); + Ok(()) + })?; Ok(Value::undefined()) } @@ -292,8 +311,11 @@ pub fn warn(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://console.spec.whatwg.org/#count /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count -pub fn count(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); +pub fn count(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let label = match args.get(0) { + Some(value) => ctx.to_string(value)?, + None => "default".to_owned(), + }; this.with_internal_state_mut(|state: &mut ConsoleState| { let msg = format!("count {}:", &label); @@ -316,8 +338,11 @@ pub fn count(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal /// /// [spec]: https://console.spec.whatwg.org/#countreset /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset -pub fn count_reset(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); +pub fn count_reset(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let label = match args.get(0) { + Some(value) => ctx.to_string(value)?, + None => "default".to_owned(), + }; this.with_internal_state_mut(|state: &mut ConsoleState| { state.count_map.remove(&label); @@ -346,8 +371,11 @@ fn system_time_in_ms() -> u128 { /// /// [spec]: https://console.spec.whatwg.org/#time /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time -pub fn time(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); +pub fn time(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let label = match args.get(0) { + Some(value) => ctx.to_string(value)?, + None => "default".to_owned(), + }; this.with_internal_state_mut(|state: &mut ConsoleState| { if state.timer_map.get(&label).is_some() { @@ -374,8 +402,11 @@ pub fn time(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://console.spec.whatwg.org/#timelog /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog -pub fn time_log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); +pub fn time_log(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let label = match args.get(0) { + Some(value) => ctx.to_string(value)?, + None => "default".to_owned(), + }; this.with_internal_state_mut(|state: &mut ConsoleState| { if let Some(t) = state.timer_map.get(&label) { @@ -406,8 +437,11 @@ pub fn time_log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Result /// /// [spec]: https://console.spec.whatwg.org/#timeend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd -pub fn time_end(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); +pub fn time_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let label = match args.get(0) { + Some(value) => ctx.to_string(value)?, + None => "default".to_owned(), + }; this.with_internal_state_mut(|state: &mut ConsoleState| { if let Some(t) = state.timer_map.remove(&label) { @@ -437,8 +471,8 @@ pub fn time_end(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Result /// /// [spec]: https://console.spec.whatwg.org/#group /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group -pub fn group(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let group_label = formatter(args); +pub fn group(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let group_label = formatter(args, ctx)?; this.with_internal_state_mut(|state: &mut ConsoleState| { logger(LogMessage::Info(format!("group: {}", &group_label)), state); diff --git a/boa/src/builtins/console/tests.rs b/boa/src/builtins/console/tests.rs index 88b88913bc..68d820dd7b 100644 --- a/boa/src/builtins/console/tests.rs +++ b/boa/src/builtins/console/tests.rs @@ -1,61 +1,82 @@ -use crate::builtins::{console::formatter, value::Value}; +use crate::{ + builtins::{console::formatter, value::Value}, + exec::Interpreter, + realm::Realm, +}; #[test] fn formatter_no_args_is_empty_string() { - assert_eq!(formatter(&[]), "") + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + assert_eq!(formatter(&[], &mut engine).unwrap(), ""); } #[test] fn formatter_empty_format_string_is_empty_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); let val = Value::string("".to_string()); - let res = formatter(&[val]); - assert_eq!(res, ""); + assert_eq!(formatter(&[val], &mut engine).unwrap(), ""); } #[test] fn formatter_format_without_args_renders_verbatim() { - let val = [Value::string("%d %s %% %f".to_string())]; - let res = formatter(&val); + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let val = [Value::string("%d %s %% %f")]; + let res = formatter(&val, &mut engine).unwrap(); assert_eq!(res, "%d %s %% %f"); } #[test] fn formatter_empty_format_string_concatenates_rest_of_args() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let val = [ - Value::string("".to_string()), - Value::string("to powinno zostać".to_string()), - Value::string("połączone".to_string()), + Value::string(""), + Value::string("to powinno zostać"), + Value::string("połączone"), ]; - let res = formatter(&val); + let res = formatter(&val, &mut engine).unwrap(); assert_eq!(res, " to powinno zostać połączone"); } #[test] fn formatter_utf_8_checks() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let val = [ Value::string("Są takie chwile %dą %są tu%sów %привет%ź".to_string()), Value::integer(123), Value::rational(1.23), Value::string("ł".to_string()), ]; - let res = formatter(&val); + let res = formatter(&val, &mut engine).unwrap(); assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź"); } #[test] fn formatter_trailing_format_leader_renders() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let val = [ Value::string("%%%%%".to_string()), Value::string("|".to_string()), ]; - let res = formatter(&val); - assert_eq!(res, "%%% |") + let res = formatter(&val, &mut engine).unwrap(); + assert_eq!(res, "%%% |"); } #[test] #[allow(clippy::approx_constant)] fn formatter_float_format_works() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let val = [Value::string("%f".to_string()), Value::rational(3.1415)]; - let res = formatter(&val); - assert_eq!(res, "3.141500") + let res = formatter(&val, &mut engine).unwrap(); + assert_eq!(res, "3.141500"); } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 035f4fead5..f656b09a0a 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -37,13 +37,9 @@ mod tests; /// /// [spec]: https://tc39.es/ecma262/#sec-json.parse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse -pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { match serde_json::from_str::( - &args - .get(0) - .expect("cannot get argument for JSON.parse") - .clone() - .to_string(), + &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, ) { Ok(json) => { let j = Value::from(json); @@ -51,7 +47,7 @@ pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> Re Some(reviver) if reviver.is_function() => { let mut holder = Value::new_object(None); holder.set_field(Value::from(""), j); - walk(reviver, interpreter, &mut holder, Value::from("")) + walk(reviver, ctx, &mut holder, Value::from("")) } _ => Ok(j), } @@ -66,18 +62,13 @@ pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> Re /// for possible transformation. /// /// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse -fn walk( - reviver: &Value, - interpreter: &mut Interpreter, - holder: &mut Value, - key: Value, -) -> ResultValue { +fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue { let mut value = holder.get_field(key.clone()); let obj = value.as_object().as_deref().cloned(); if let Some(obj) = obj { for key in obj.properties.keys() { - let v = walk(reviver, interpreter, &mut value, Value::from(key.as_str())); + let v = walk(reviver, ctx, &mut value, Value::from(key.as_str())); match v { Ok(v) if !v.is_undefined() => { value.set_field(Value::from(key.as_str()), v); @@ -89,7 +80,7 @@ fn walk( } } } - interpreter.call(reviver, holder, &[key, value]) + ctx.call(reviver, holder, &[key, value]) } /// `JSON.stringify( value[, replacer[, space]] )` @@ -108,7 +99,7 @@ fn walk( /// /// [spec]: https://tc39.es/ecma262/#sec-json.stringify /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify -pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let object = match args.get(0) { Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), None => return Ok(Value::undefined()), @@ -135,7 +126,7 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) - let mut this_arg = object.clone(); object_to_return.set_property( key.to_owned(), - Property::default().value(interpreter.call( + Property::default().value(ctx.call( replacer, &mut this_arg, &[Value::string(key), val.clone()], @@ -152,12 +143,12 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) - if key == "length" { None } else { - Some(replacer.get_field(key.to_string())) + Some(replacer.get_field(key.to_owned())) } }); for field in fields { if let Some(value) = object - .get_property(&field.to_string()) + .get_property(&ctx.to_string(&field)?) .map(|prop| prop.value.as_ref().map(|v| v.to_json())) .flatten() { diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 05cde1dcf1..4683a192b3 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -208,7 +208,8 @@ impl Number { } // https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230 - pub(crate) fn num_to_string(mut value: f64, radix: u8) -> String { + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_native_string_radix(mut value: f64, radix: u8) -> String { assert!(radix >= 2); assert!(radix <= 36); assert!(value.is_finite()); @@ -313,6 +314,22 @@ impl Number { String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into() } + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_native_string(x: f64) -> String { + if x == -0. { + return "0".to_owned(); + } else if x.is_nan() { + return "NaN".to_owned(); + } else if x.is_infinite() && x.is_sign_positive() { + return "Infinity".to_owned(); + } else if x.is_infinite() && x.is_sign_negative() { + return "-Infinity".to_owned(); + } + + // FIXME: This is not spec compliant. + format!("{}", x) + } + /// `Number.prototype.toString( [radix] )` /// /// The `toString()` method returns a string representing the specified Number object. @@ -333,7 +350,15 @@ impl Number { let x = Self::to_number(this).to_number(); // 2. If radix is undefined, let radixNumber be 10. // 3. Else, let radixNumber be ? ToInteger(radix). - let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; + let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; + + // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. + if radix < 2 || radix > 36 { + return Err(RangeError::run_new( + "radix must be an integer at least 2 and no greater than 36", + ctx, + )?); + } if x == -0. { return Ok(Value::from("0")); @@ -345,19 +370,11 @@ impl Number { return Ok(Value::from("-Infinity")); } - // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. - if radix_number < 2 || radix_number > 36 { - return Err(RangeError::run_new( - "radix must be an integer at least 2 and no greater than 36", - ctx, - )?); - } - // 5. If radixNumber = 10, return ! ToString(x). // This part should use exponential notations for long integer numbers commented tests - if radix_number == 10 { + if radix == 10 { // return Ok(to_value(format!("{}", Self::to_number(this).to_num()))); - return Ok(Value::from(format!("{}", x))); + return Ok(Value::from(Self::to_native_string(x))); } // This is a Optimization from the v8 source code to print values that can fit in a single character @@ -369,7 +386,7 @@ impl Number { // } // 6. Return the String representation of this Number value using the radix specified by radixNumber. - Ok(Value::from(Self::num_to_string(x, radix_number))) + Ok(Value::from(Self::to_native_string_radix(x, radix))) } /// `Number.prototype.toString()` diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 912d15203c..57763a4ab7 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -566,9 +566,9 @@ pub fn set_prototype_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> R } /// Define a property in an object -pub fn define_property(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn define_property(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("Cannot get object"); - let prop = String::from(args.get(1).expect("Cannot get object")); + let prop = ctx.to_string(args.get(1).expect("Cannot get object"))?; let desc = Property::from(args.get(2).expect("Cannot get object")); obj.set_property(prop, desc); Ok(Value::undefined()) @@ -599,11 +599,11 @@ pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultVa /// /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty -pub fn has_own_property(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn has_own_property(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let prop = if args.is_empty() { None } else { - Some(String::from(args.get(0).expect("Cannot get object"))) + Some(ctx.to_string(args.get(0).expect("Cannot get object"))?) }; Ok(Value::from( prop.is_some() diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index af82d3bfe8..16fd0bfefe 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -64,7 +64,7 @@ impl RegExp { pub(crate) fn make_regexp( this: &mut Value, args: &[Value], - _: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { if args.is_empty() { return Err(Value::undefined()); @@ -82,10 +82,10 @@ impl RegExp { if slots.get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags if let Some(body) = slots.get("OriginalSource") { - regex_body = String::from(body); + regex_body = ctx.to_string(body)?; } if let Some(flags) = slots.get("OriginalFlags") { - regex_flags = String::from(flags); + regex_flags = ctx.to_string(flags)?; } } } @@ -295,8 +295,8 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test - pub(crate) fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let arg_str = String::from(args.get(0).expect("could not get argument")); + pub(crate) fn test(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?; let mut last_index = usize::from(&this.get_field("lastIndex")); let result = this.with_internal_state_ref(|regex: &RegExp| { let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) { @@ -328,8 +328,8 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec - pub(crate) fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let arg_str = String::from(args.get(0).expect("could not get argument")); + pub(crate) fn exec(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?; let mut last_index = usize::from(&this.get_field("lastIndex")); let result = this.with_internal_state_ref(|regex: &RegExp| { let mut locations = regex.matcher.capture_locations(); @@ -407,8 +407,8 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let body = String::from(&this.get_internal_slot("OriginalSource")); + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let body = ctx.to_string(&this.get_internal_slot("OriginalSource"))?; let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone()); Ok(Value::from(format!("/{}/{}", body, flags))) } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 06ba55460b..fb4efc76b2 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -70,8 +70,11 @@ impl String { #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value - let primitive_val = this.get_internal_slot("StringData"); - Ok(Value::from(format!("{}", primitive_val))) + match this.get_internal_slot("StringData").data() { + ValueData::String(ref string) => Ok(Value::from(string.clone())), + // Throw expection here: + _ => panic!("TypeError: this is not a string"), + } } /// `String.prototype.charAt( index )` @@ -93,7 +96,7 @@ impl String { pub(crate) fn char_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; let pos = i32::from( args.get(0) .expect("failed to get argument for String method"), @@ -139,7 +142,7 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. @@ -179,7 +182,7 @@ impl String { pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let mut new_str = ctx.value_to_rust_string(this); + let mut new_str = ctx.to_string(this)?; for arg in args { let concat_str = arg.to_string(); @@ -203,7 +206,7 @@ impl String { pub(crate) fn repeat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; let repeat_times = usize::from( args.get(0) @@ -226,7 +229,7 @@ impl String { pub(crate) fn slice(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; let start = i32::from( args.get(0) @@ -277,13 +280,13 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // TODO: Should throw TypeError if pattern is regular expression - let search_string = StdString::from( + let search_string = ctx.to_string( args.get(0) .expect("failed to get argument for String method"), - ); + )?; let length = primitive_val.chars().count() as i32; let search_length = search_string.chars().count() as i32; @@ -324,13 +327,13 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // TODO: Should throw TypeError if search_string is regular expression - let search_string = StdString::from( + let search_string = ctx.to_string( args.get(0) .expect("failed to get argument for String method"), - ); + )?; let length = primitive_val.chars().count() as i32; let search_length = search_string.chars().count() as i32; @@ -368,13 +371,13 @@ impl String { pub(crate) fn includes(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // TODO: Should throw TypeError if search_string is regular expression - let search_string = StdString::from( + let search_string = ctx.to_string( args.get(0) .expect("failed to get argument for String method"), - ); + )?; let length = primitive_val.chars().count() as i32; @@ -428,7 +431,7 @@ impl String { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace pub(crate) fn replace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // TODO: Support Symbol replacer - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; if args.is_empty() { return Ok(Value::from(primitive_val)); } @@ -501,7 +504,7 @@ impl String { let result = ctx.call(&replace_object, this, &results).unwrap(); - ctx.value_to_rust_string(&result) + ctx.to_string(&result)? } _ => "undefined".to_string(), } @@ -531,13 +534,13 @@ impl String { pub(crate) fn index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // TODO: Should throw TypeError if search_string is regular expression - let search_string = StdString::from( + let search_string = ctx.to_string( args.get(0) .expect("failed to get argument for String method"), - ); + )?; let length = primitive_val.chars().count() as i32; @@ -584,13 +587,13 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // TODO: Should throw TypeError if search_string is regular expression - let search_string = StdString::from( + let search_string = ctx.to_string( args.get(0) .expect("failed to get argument for String method"), - ); + )?; let length = primitive_val.chars().count() as i32; @@ -633,7 +636,7 @@ impl String { pub(crate) fn r#match(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let mut re = RegExp::make_regexp(&mut Value::from(Object::default()), &[args[0].clone()], ctx)?; - RegExp::r#match(&mut re, ctx.value_to_rust_string(this), ctx) + RegExp::r#match(&mut re, ctx.to_string(this)?, ctx) } /// Abstract method `StringPad`. @@ -690,7 +693,7 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd pub(crate) fn pad_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; if args.is_empty() { return Err(Value::from("padEnd requires maxLength argument")); } @@ -699,11 +702,10 @@ impl String { .expect("failed to get argument for String method"), ); - let fill_string = match args.len() { - 1 => None, - _ => Some(StdString::from( - args.get(1).expect("Could not get argument"), - )), + let fill_string = if args.len() != 1 { + Some(ctx.to_string(args.get(1).expect("Could not get argument"))?) + } else { + None }; Self::string_pad(primitive_val, max_length, fill_string, false) @@ -726,7 +728,7 @@ impl String { args: &[Value], ctx: &mut Interpreter, ) -> ResultValue { - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; if args.is_empty() { return Err(Value::from("padStart requires maxLength argument")); } @@ -737,9 +739,7 @@ impl String { let fill_string = match args.len() { 1 => None, - _ => Some(StdString::from( - args.get(1).expect("Could not get argument"), - )), + _ => Some(ctx.to_string(args.get(1).expect("Could not get argument"))?), }; Self::string_pad(primitive_val, max_length, fill_string, true) @@ -777,7 +777,7 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trim /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim pub(crate) fn trim(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str = ctx.value_to_rust_string(this); + let this_str = ctx.to_string(this)?; Ok(Value::from( this_str.trim_matches(Self::is_trimmable_whitespace), )) @@ -796,7 +796,7 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimstart /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart pub(crate) fn trim_start(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str = ctx.value_to_rust_string(this); + let this_str = ctx.to_string(this)?; Ok(Value::from( this_str.trim_start_matches(Self::is_trimmable_whitespace), )) @@ -815,7 +815,7 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd pub(crate) fn trim_end(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str = ctx.value_to_rust_string(this); + let this_str = ctx.to_string(this)?; Ok(Value::from( this_str.trim_end_matches(Self::is_trimmable_whitespace), )) @@ -839,7 +839,7 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let this_str = ctx.value_to_rust_string(this); + let this_str = ctx.to_string(this)?; // The Rust String is mapped to uppercase using the builtin .to_lowercase(). // There might be corner cases where it does not behave exactly like Javascript expects Ok(Value::from(this_str.to_lowercase())) @@ -865,7 +865,7 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let this_str = ctx.value_to_rust_string(this); + let this_str = ctx.to_string(this)?; // The Rust String is mapped to uppercase using the builtin .to_uppercase(). // There might be corner cases where it does not behave exactly like Javascript expects Ok(Value::from(this_str.to_uppercase())) @@ -888,7 +888,7 @@ impl String { ) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // If no args are specified, start is 'undefined', defaults to 0 let start = if args.is_empty() { 0 @@ -936,7 +936,7 @@ impl String { pub(crate) fn substr(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); + let primitive_val = ctx.to_string(this)?; // If no args are specified, start is 'undefined', defaults to 0 let mut start = if args.is_empty() { 0 @@ -1016,7 +1016,7 @@ impl String { if arg.is_null() { RegExp::make_regexp( &mut Value::from(Object::default()), - &[Value::from(ctx.value_to_rust_string(arg)), Value::from("g")], + &[Value::from(ctx.to_string(arg)?), Value::from("g")], ctx, ) } else if arg.is_undefined() { @@ -1036,7 +1036,7 @@ impl String { ), }?; - RegExp::match_all(&mut re, ctx.value_to_rust_string(this)) + RegExp::match_all(&mut re, ctx.to_string(this)?) } /// Create a new `String` object. diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index b2459cf40f..98682c783c 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -19,12 +19,6 @@ impl From> for Value { } } -impl From<&Value> for String { - fn from(value: &Value) -> Self { - value.to_string() - } -} - impl From<&str> for Value { fn from(value: &str) -> Value { Value::string(value) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs new file mode 100644 index 0000000000..b035bb35bf --- /dev/null +++ b/boa/src/builtins/value/display.rs @@ -0,0 +1,222 @@ +use super::*; + +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.data(), f) + } +} + +/// A helper macro for printing objects +/// Can be used to print both properties and internal slots +/// All of the overloads take: +/// - The object to be printed +/// - The function with which to print +/// - The indentation for the current level (for nested objects) +/// - A HashSet with the addresses of the already printed objects for the current branch +/// (used to avoid infinite loops when there are cyclic deps) +macro_rules! print_obj_value { + (all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { + { + let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters); + let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true); + + props.reserve(internals.len()); + props.append(&mut internals); + + props + } + }; + (internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { + print_obj_value!(impl internal_slots, $obj, |(key, val)| { + format!( + "{:>width$}: {}", + key, + $display_fn(&val, $encounters, $indent.wrapping_add(4), true), + width = $indent, + ) + }) + }; + (props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { + print_obj_value!(impl properties, $obj, |(key, val)| { + let v = &val + .value + .as_ref() + .expect("Could not get the property's value"); + + format!( + "{:>width$}: {}", + key, + $display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals), + width = $indent, + ) + }) + }; + + // A private overload of the macro + // DO NOT use directly + (impl $field:ident, $v:expr, $f:expr) => { + $v + .borrow() + .$field + .iter() + .map($f) + .collect::>() + }; +} + +pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { + match x { + // We don't want to print private (compiler) or prototype properties + ValueData::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 { + ObjectKind::String => match v + .borrow() + .internal_slots + .get("StringData") + .expect("Cannot get primitive value from String") + .data() + { + ValueData::String(ref string) => format!("\"{}\"", string), + _ => unreachable!("[[StringData]] should always contain String"), + }, + ObjectKind::Boolean => { + let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); + + format!("Boolean {{ {} }}", bool_data) + } + ObjectKind::Array => { + let len = i32::from( + &v.borrow() + .properties + .get("length") + .unwrap() + .value + .clone() + .expect("Could not borrow value"), + ); + + if len == 0 { + return String::from("[]"); + } + + let arr = (0..len) + .map(|i| { + // Introduce recursive call to stringify any objects + // which are part of the Array + log_string_from( + &v.borrow() + .properties + .get(&i.to_string()) + .unwrap() + .value + .clone() + .expect("Could not borrow value"), + print_internals, + ) + }) + .collect::>() + .join(", "); + + format!("[ {} ]", arr) + } + _ => display_obj(&x, print_internals), + } + } + ValueData::Symbol(ref sym) => { + let desc: Value = sym.borrow().get_internal_slot("Description"); + match *desc { + ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), + _ => String::from("Symbol()"), + } + } + + _ => format!("{}", x), + } +} + +/// A helper function for specifically printing object values +pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String { + // A simple helper for getting the address of a value + // TODO: Find a more general place for this, as it can be used in other situations as well + fn address_of(t: &T) -> usize { + let my_ptr: *const T = t; + my_ptr as usize + } + + // We keep track of which objects we have encountered by keeping their + // in-memory address in this set + let mut encounters = HashSet::new(); + + fn display_obj_internal( + data: &ValueData, + encounters: &mut HashSet, + indent: usize, + print_internals: bool, + ) -> String { + if let ValueData::Object(ref v) = *data { + // The in-memory address of the current object + let addr = address_of(v.borrow().deref()); + + // We need not continue if this object has already been + // printed up the current chain + if encounters.contains(&addr) { + return String::from("[Cycle]"); + } + + // Mark the current object as encountered + encounters.insert(addr); + + let result = if print_internals { + print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n") + } else { + print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals) + .join(",\n") + }; + + // If the current object is referenced in a different branch, + // it will not cause an infinte printing loop, so it is safe to be printed again + encounters.remove(&addr); + + let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)]) + .expect("Could not create the closing brace's indentation string"); + + format!("{{\n{}\n{}}}", result, closing_indent) + } else { + // Every other type of data is printed as is + format!("{}", data) + } + } + + display_obj_internal(v, &mut encounters, 4, print_internals) +} + +impl Display for ValueData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Null => write!(f, "null"), + Self::Undefined => write!(f, "undefined"), + Self::Boolean(v) => write!(f, "{}", v), + Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { + // If a description exists use it + Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), + _ => write!(f, "Symbol()"), + }, + Self::String(ref v) => write!(f, "{}", v), + Self::Rational(v) => write!( + f, + "{}", + match v { + _ if v.is_nan() => "NaN".to_string(), + _ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(), + _ if v.is_infinite() => "Infinity".to_string(), + _ => v.to_string(), + } + ), + Self::Object(_) => write!(f, "{}", log_string_from(self, true)), + Self::Integer(v) => write!(f, "{}", v), + Self::BigInt(ref num) => write!(f, "{}n", num), + } + } +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 3721ed82ce..192754a687 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -27,8 +27,10 @@ use std::{ }; pub mod conversions; +pub mod display; pub mod operations; pub use conversions::*; +pub(crate) use display::display_obj; pub use operations::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) @@ -153,12 +155,6 @@ impl Deref for Value { } } -impl Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] pub enum ValueData { @@ -794,217 +790,3 @@ impl Default for ValueData { Self::Undefined } } - -/// A helper macro for printing objects -/// Can be used to print both properties and internal slots -/// All of the overloads take: -/// - The object to be printed -/// - The function with which to print -/// - The indentation for the current level (for nested objects) -/// - A HashSet with the addresses of the already printed objects for the current branch -/// (used to avoid infinite loops when there are cyclic deps) -macro_rules! print_obj_value { - (all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { - { - let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters); - let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true); - - props.reserve(internals.len()); - - props.append(&mut internals); - - props - } - }; - (internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { - print_obj_value!(impl internal_slots, $obj, |(key, val)| { - format!( - "{}{}: {}", - String::from_utf8(vec![b' '; $indent]) - .expect("Could not create indentation string"), - key, - $display_fn(&val, $encounters, $indent.wrapping_add(4), true) - ) - }) - }; - (props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { - print_obj_value!(impl properties, $obj, |(key, val)| { - let v = &val - .value - .as_ref() - .expect("Could not get the property's value"); - - format!( - "{}{}: {}", - String::from_utf8(vec![b' '; $indent]) - .expect("Could not create indentation string"), - key, - $display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals) - ) - }) - }; - - // A private overload of the macro - // DO NOT use directly - (impl $field:ident, $v:expr, $f:expr) => { - $v - .borrow() - .$field - .iter() - .map($f) - .collect::>() - }; -} - -pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { - match x { - // We don't want to print private (compiler) or prototype properties - ValueData::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 { - ObjectKind::String => String::from( - v.borrow() - .internal_slots - .get("StringData") - .expect("Cannot get primitive value from String"), - ), - ObjectKind::Boolean => { - let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); - - format!("Boolean {{ {} }}", bool_data) - } - ObjectKind::Array => { - let len = i32::from( - &v.borrow() - .properties - .get("length") - .unwrap() - .value - .clone() - .expect("Could not borrow value"), - ); - - if len == 0 { - return String::from("[]"); - } - - let arr = (0..len) - .map(|i| { - // Introduce recursive call to stringify any objects - // which are part of the Array - log_string_from( - &v.borrow() - .properties - .get(&i.to_string()) - .unwrap() - .value - .clone() - .expect("Could not borrow value"), - print_internals, - ) - }) - .collect::>() - .join(", "); - - format!("[ {} ]", arr) - } - _ => display_obj(&x, print_internals), - } - } - ValueData::Symbol(ref sym) => { - let desc: Value = sym.borrow().get_internal_slot("Description"); - match *desc { - ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), - _ => String::from("Symbol()"), - } - } - - _ => format!("{}", x), - } -} - -/// A helper function for specifically printing object values -pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String { - // A simple helper for getting the address of a value - // TODO: Find a more general place for this, as it can be used in other situations as well - fn address_of(t: &T) -> usize { - let my_ptr: *const T = t; - my_ptr as usize - } - - // We keep track of which objects we have encountered by keeping their - // in-memory address in this set - let mut encounters = HashSet::new(); - - fn display_obj_internal( - data: &ValueData, - encounters: &mut HashSet, - indent: usize, - print_internals: bool, - ) -> String { - if let ValueData::Object(ref v) = *data { - // The in-memory address of the current object - let addr = address_of(v.borrow().deref()); - - // We need not continue if this object has already been - // printed up the current chain - if encounters.contains(&addr) { - return String::from("[Cycle]"); - } - - // Mark the current object as encountered - encounters.insert(addr); - - let result = if print_internals { - print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n") - } else { - print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals) - .join(",\n") - }; - - // If the current object is referenced in a different branch, - // it will not cause an infinte printing loop, so it is safe to be printed again - encounters.remove(&addr); - - let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)]) - .expect("Could not create the closing brace's indentation string"); - - format!("{{\n{}\n{}}}", result, closing_indent) - } else { - // Every other type of data is printed as is - format!("{}", data) - } - } - - display_obj_internal(v, &mut encounters, 4, print_internals) -} - -impl Display for ValueData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Null => write!(f, "null"), - Self::Undefined => write!(f, "undefined"), - Self::Boolean(v) => write!(f, "{}", v), - Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { - // If a description exists use it - Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), - _ => write!(f, "Symbol()"), - }, - Self::String(ref v) => write!(f, "{}", v), - Self::Rational(v) => write!( - f, - "{}", - match v { - _ if v.is_nan() => "NaN".to_string(), - _ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(), - _ if v.is_infinite() => "Infinity".to_string(), - _ => v.to_string(), - } - ), - Self::Object(_) => write!(f, "{}", log_string_from(self, true)), - Self::Integer(v) => write!(f, "{}", v), - Self::BigInt(ref num) => write!(f, "{}n", num), - } - } -} diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 57fc661ca7..f81a3b1153 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -24,7 +24,7 @@ impl Executable for Call { Node::GetField(ref obj, ref field) => { let obj = obj.run(interpreter)?; let field = field.run(interpreter)?; - (obj.clone(), obj.get_field(field.to_string())) + (obj.clone(), obj.get_field(field)) } _ => ( interpreter.realm().global_obj.clone(), diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 4f0c826681..12e43fefc4 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -20,6 +20,7 @@ use crate::{ }, property::Property, value::{ResultValue, Value, ValueData}, + BigInt, Number, }, realm::Realm, syntax::ast::{ @@ -57,6 +58,11 @@ impl Interpreter { &self.realm } + /// Retrieves the `Realm` of this executor as a mutable reference. + pub(crate) fn realm_mut(&mut self) -> &mut Realm { + &mut self.realm + } + /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions pub(crate) fn create_function( &mut self, @@ -107,11 +113,6 @@ impl Interpreter { val } - /// Retrieves the `Realm` of this executor as a mutable reference. - pub(crate) fn realm_mut(&mut self) -> &mut Realm { - &mut self.realm - } - /// pub(crate) fn call( &mut self, @@ -130,18 +131,21 @@ impl Interpreter { } /// Converts a value into a rust heap allocated string. - pub(crate) fn value_to_rust_string(&mut self, value: &Value) -> String { - match *value.deref().borrow() { - ValueData::Null => String::from("null"), - ValueData::Boolean(ref boolean) => boolean.to_string(), - ValueData::Rational(ref num) => num.to_string(), - ValueData::Integer(ref num) => num.to_string(), - ValueData::String(ref string) => string.clone(), + #[allow(clippy::wrong_self_convention)] + pub fn to_string(&mut self, value: &Value) -> Result { + match value.data() { + ValueData::Null => Ok("null".to_owned()), + ValueData::Undefined => Ok("undefined".to_owned()), + ValueData::Boolean(boolean) => Ok(boolean.to_string()), + ValueData::Rational(rational) => Ok(Number::to_native_string(*rational)), + ValueData::Integer(integer) => Ok(integer.to_string()), + ValueData::String(string) => Ok(string.clone()), + ValueData::Symbol(_) => panic!("TypeError exception."), + ValueData::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)), ValueData::Object(_) => { - let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); - self.to_string(&prim_value).to_string() + let primitive = self.to_primitive(&mut value.clone(), Some("string")); + self.to_string(&primitive) } - _ => String::from("undefined"), } } @@ -227,36 +231,16 @@ impl Interpreter { } } - /// Converts a value into a `String`. - /// - /// https://tc39.es/ecma262/#sec-tostring - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(&mut self, value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Undefined => Value::from("undefined"), - ValueData::Null => Value::from("null"), - ValueData::Boolean(ref boolean) => Value::from(boolean.to_string()), - ValueData::Rational(ref num) => Value::from(num.to_string()), - ValueData::Integer(ref num) => Value::from(num.to_string()), - ValueData::String(ref string) => Value::from(string.clone()), - ValueData::BigInt(ref bigint) => Value::from(bigint.to_string()), - ValueData::Object(_) => { - let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); - self.to_string(&prim_value) - } - _ => Value::from("function(){...}"), - } - } - /// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key. + /// /// https://tc39.es/ecma262/#sec-topropertykey #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_property_key(&mut self, value: &mut Value) -> Value { + pub(crate) fn to_property_key(&mut self, value: &mut Value) -> ResultValue { let key = self.to_primitive(value, Some("string")); if key.is_symbol() { - key + Ok(key) } else { - self.to_string(&key) + self.to_string(&key).map(Value::from) } } @@ -343,9 +327,9 @@ impl Interpreter { ValueData::Object(_) => { let prim_value = self.to_primitive(&mut (value.clone()), Some("number")); self.to_string(&prim_value) - .to_string() + .expect("cannot convert value to string") .parse::() - .expect("cannot parse valur to x64") + .expect("cannot parse value to f64") } _ => { // TODO: Make undefined? diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index d4cb97495c..325142dddd 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -89,7 +89,7 @@ impl Executable for BinOp { if !v_b.is_object() { panic!("TypeError: {} is not an Object.", v_b); } - let key = interpreter.to_property_key(&mut v_a); + let key = interpreter.to_property_key(&mut v_a)?; interpreter.has_property(&mut v_b, &key) } }))