Browse Source

Make `Error` and `%NativeError%` spec compliant (#1879)

This PR makes `Error` and `%NativeError%` spec compliant.

It changes the following:
- Adds cause argument object.
- Makes `message` non-enumerable.
pull/1880/head
Halid Odat 3 years ago
parent
commit
3fe7d09096
  1. 35
      boa_engine/src/builtins/error/eval.rs
  2. 49
      boa_engine/src/builtins/error/mod.rs
  3. 35
      boa_engine/src/builtins/error/range.rs
  4. 35
      boa_engine/src/builtins/error/reference.rs
  5. 35
      boa_engine/src/builtins/error/syntax.rs
  6. 35
      boa_engine/src/builtins/error/type.rs
  7. 30
      boa_engine/src/builtins/error/uri.rs
  8. 39
      boa_engine/src/object/operations.rs

35
boa_engine/src/builtins/error/eval.rs

@ -12,7 +12,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -22,6 +22,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
/// JavaScript `EvalError` impleentation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct EvalError;
@ -64,14 +66,29 @@ impl EvalError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,
StandardObjects::eval_error_object,
context,
)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

49
boa_engine/src/builtins/error/mod.rs

@ -38,6 +38,8 @@ pub(crate) use self::reference::ReferenceError;
pub(crate) use self::syntax::SyntaxError;
pub(crate) use self::uri::UriError;
use super::JsArgs;
/// Built-in `Error` object.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Error;
@ -73,7 +75,27 @@ impl Error {
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// `Error( message )`
pub(crate) fn install_error_cause(
o: &JsObject,
options: &JsValue,
context: &mut Context,
) -> JsResult<()> {
// 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then
if let Some(options) = options.as_object() {
if options.has_property("cause", context)? {
// a. Let cause be ? Get(options, "cause").
let cause = options.get("cause", context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
o.create_non_enumerable_data_property_or_throw("cause", cause, context);
}
}
// 2. Return unused.
Ok(())
}
/// `Error( message [ , options ] )`
///
/// Create a new error object.
pub(crate) fn constructor(
@ -81,15 +103,28 @@ impl Error {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Self::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
/// `Error.prototype.toString()`

35
boa_engine/src/builtins/error/range.rs

@ -10,7 +10,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -20,6 +20,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
/// JavaScript `RangeError` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct RangeError;
@ -62,14 +64,29 @@ impl RangeError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,
StandardObjects::range_error_object,
context,
)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

35
boa_engine/src/builtins/error/reference.rs

@ -10,7 +10,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -20,6 +20,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReferenceError;
@ -61,14 +63,29 @@ impl ReferenceError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,
StandardObjects::reference_error_object,
context,
)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

35
boa_engine/src/builtins/error/syntax.rs

@ -12,7 +12,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -22,6 +22,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
/// JavaScript `SyntaxError` impleentation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct SyntaxError;
@ -64,14 +66,29 @@ impl SyntaxError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,
StandardObjects::syntax_error_object,
context,
)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

35
boa_engine/src/builtins/error/type.rs

@ -16,7 +16,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -26,6 +26,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
/// JavaScript `TypeError` implementation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct TypeError;
@ -68,14 +70,29 @@ impl TypeError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype = get_prototype_from_constructor(
new_target,
StandardObjects::type_error_object,
context,
)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

30
boa_engine/src/builtins/error/uri.rs

@ -11,7 +11,7 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
use crate::{
builtins::BuiltIn,
builtins::{BuiltIn, JsArgs},
context::StandardObjects,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
@ -21,6 +21,8 @@ use crate::{
};
use boa_profiler::Profiler;
use super::Error;
/// JavaScript `URIError` impleentation.
#[derive(Debug, Clone, Copy)]
pub(crate) struct UriError;
@ -63,14 +65,26 @@ impl UriError {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
if let Some(message) = args.get(0) {
if !message.is_undefined() {
obj.set("message", message.to_string(context)?, false, context)?;
}
get_prototype_from_constructor(new_target, StandardObjects::uri_error_object, context)?;
let o = JsObject::from_proto_and_data(prototype, ObjectData::error());
// 3. If message is not undefined, then
let message = args.get_or_undefined(0);
if !message.is_undefined() {
// a. Let msg be ? ToString(message).
let msg = message.to_string(context)?;
// b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
o.create_non_enumerable_data_property_or_throw("message", msg, context);
}
Ok(obj.into())
// 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?;
// 5. Return O.
Ok(o.into())
}
}

39
boa_engine/src/object/operations.rs

@ -2,7 +2,7 @@ use crate::{
builtins::Array,
context::{StandardConstructor, StandardObjects},
object::JsObject,
property::{PropertyDescriptor, PropertyKey, PropertyNameKind},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::Type,
Context, JsResult, JsValue,
@ -156,6 +156,43 @@ impl JsObject {
Ok(success)
}
/// Create non-enumerable data property or throw
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyinfallibly
pub(crate) fn create_non_enumerable_data_property_or_throw<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
// 1. Assert: O is an ordinary, extensible object with no non-configurable properties.
// 2. Let newDesc be the PropertyDescriptor {
// [[Value]]: V,
// [[Writable]]: true,
// [[Enumerable]]: false,
// [[Configurable]]: true
// }.
let new_desc = PropertyDescriptorBuilder::new()
.value(value)
.writable(true)
.enumerable(false)
.configurable(true)
.build();
// 3. Perform ! DefinePropertyOrThrow(O, P, newDesc).
self.define_property_or_throw(key, new_desc, context)
.expect("should not fail according to spec");
// 4. Return unused.
}
/// Define property or throw.
///
/// More information:

Loading…
Cancel
Save