|
|
|
|
use crate::{
|
|
|
|
|
builtins::{
|
|
|
|
|
function::{BoundFunction, ClassFieldDefinition, OrdinaryFunction},
|
|
|
|
|
Array, Proxy,
|
|
|
|
|
},
|
|
|
|
|
context::intrinsics::{StandardConstructor, StandardConstructors},
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
error::JsNativeError,
|
|
|
|
|
native_function::NativeFunctionObject,
|
|
|
|
|
object::{JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE},
|
|
|
|
|
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
|
|
|
|
|
realm::Realm,
|
|
|
|
|
string::utf16,
|
|
|
|
|
value::Type,
|
Make `JsSymbol` thread-safe (#2539)
The section about `Symbol` on the [specification](https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type) says:
> The Symbol type is the set of all non-String values that may be used as the key of an Object property ([6.1.7](https://tc39.es/ecma262/#sec-object-type)).
Each possible Symbol value is unique and immutable.
Our previous implementation of `JsSymbol` used `Rc` and a thread local `Cell<usize>`. However, this meant that two different symbols in two different threads could share the same hash, making symbols not unique.
Also, the [GlobalSymbolRegistry](https://tc39.es/ecma262/#table-globalsymbolregistry-record-fields) is meant to be shared by all realms, including realms that are not in the same thread as the main one; this forces us to replace our current thread local global symbol registry with a thread-safe one that uses `DashMap` for concurrent access. However, the global symbol registry uses `JsString`s as keys and values, which forces us to either use `Vec<u16>` instead (wasteful and needs to allocate to convert to `JsString` on each access) or make `JsString` thread-safe with an atomic counter. For this reason, I implemented the second option.
This PR changes the following:
- Makes `JsSymbol` thread-safe by using Arc instead of Rc, and making `SYMBOL_HASH_COUNT` an `AtomicU64`.
- ~~Makes `JsString` thread-safe by using `AtomicUsize` instead of `Cell<usize>` for its ref count.~~ EDIT: Talked with @jasonwilliams and we decided to use `Box<[u16]>` for the global registry instead, because this won't penalize common usage of `JsString`, which is used a LOT more than `JsSymbol`.
- Makes the `GLOBAL_SYMBOL_REGISTRY` truly global, using `DashMap` as our global map that is shared by all threads.
- Replaces some thread locals with thread-safe alternatives, such as static arrays and static indices.
- Various improvements to all related code for this.
2 years ago
|
|
|
|
Context, JsResult, JsSymbol, JsValue,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use super::internal_methods::InternalMethodContext;
|
|
|
|
|
|
|
|
|
|
/// Object integrity level.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub enum IntegrityLevel {
|
|
|
|
|
/// Sealed object integrity level.
|
|
|
|
|
///
|
|
|
|
|
/// Preventing new properties from being added to it and marking all existing
|
|
|
|
|
/// properties as non-configurable. Values of present properties can still be
|
|
|
|
|
/// changed as long as they are writable.
|
|
|
|
|
Sealed,
|
|
|
|
|
|
|
|
|
|
/// Frozen object integrity level
|
|
|
|
|
///
|
|
|
|
|
/// A frozen object can no longer be changed; freezing an object prevents new
|
|
|
|
|
/// properties from being added to it, existing properties from being removed,
|
|
|
|
|
/// prevents changing the enumerability, configurability, or writability of
|
|
|
|
|
/// existing properties, and prevents the values of existing properties from
|
|
|
|
|
/// being changed. In addition, freezing an object also prevents its prototype
|
|
|
|
|
/// from being changed.
|
|
|
|
|
Frozen,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IntegrityLevel {
|
|
|
|
|
/// Returns `true` if the integrity level is sealed.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn is_sealed(&self) -> bool {
|
|
|
|
|
matches!(self, Self::Sealed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns `true` if the integrity level is frozen.
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub const fn is_frozen(&self) -> bool {
|
|
|
|
|
matches!(self, Self::Frozen)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl JsObject {
|
|
|
|
|
/// Check if object is extensible.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_extensible(&self, context: &mut Context) -> JsResult<bool> {
|
|
|
|
|
// 1. Return ? O.[[IsExtensible]]().
|
|
|
|
|
self.__is_extensible__(context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get property from object or throw.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
|
|
|
|
|
pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Return ? O.[[Get]](P, O).
|
|
|
|
|
self.__get__(
|
|
|
|
|
&key.into(),
|
|
|
|
|
self.clone().into(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// set property of object or throw if bool flag is passed.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
|
|
|
|
|
pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
V: Into<JsValue>,
|
|
|
|
|
{
|
|
|
|
|
let key = key.into();
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Assert: Type(Throw) is Boolean.
|
|
|
|
|
// 4. Let success be ? O.[[Set]](P, V, O).
|
|
|
|
|
let success = self.__set__(
|
|
|
|
|
key.clone(),
|
|
|
|
|
value.into(),
|
|
|
|
|
self.clone().into(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)?;
|
|
|
|
|
// 5. If success is false and Throw is true, throw a TypeError exception.
|
|
|
|
|
if !success && throw {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message(format!("cannot set non-writable property: {key}"))
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
// 6. Return success.
|
|
|
|
|
Ok(success)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create data property
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
|
|
|
|
|
pub fn create_data_property<K, V>(
|
|
|
|
|
&self,
|
|
|
|
|
key: K,
|
|
|
|
|
value: V,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
V: Into<JsValue>,
|
|
|
|
|
{
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
|
|
|
|
let new_desc = PropertyDescriptor::builder()
|
|
|
|
|
.value(value)
|
|
|
|
|
.writable(true)
|
|
|
|
|
.enumerable(true)
|
|
|
|
|
.configurable(true);
|
|
|
|
|
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
|
|
|
|
self.__define_own_property__(
|
|
|
|
|
&key.into(),
|
|
|
|
|
new_desc.into(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create data property
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
|
|
|
|
|
pub(crate) fn create_data_property_with_slot<K, V>(
|
|
|
|
|
&self,
|
|
|
|
|
key: K,
|
|
|
|
|
value: V,
|
|
|
|
|
context: &mut InternalMethodContext<'_>,
|
|
|
|
|
) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
V: Into<JsValue>,
|
|
|
|
|
{
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
|
|
|
|
|
let new_desc = PropertyDescriptor::builder()
|
|
|
|
|
.value(value)
|
|
|
|
|
.writable(true)
|
|
|
|
|
.enumerable(true)
|
|
|
|
|
.configurable(true);
|
|
|
|
|
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
|
|
|
|
|
self.__define_own_property__(&key.into(), new_desc.into(), context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// todo: CreateMethodProperty
|
|
|
|
|
|
|
|
|
|
/// Create data property or throw
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-createdatapropertyorthrow
|
|
|
|
|
pub fn create_data_property_or_throw<K, V>(
|
|
|
|
|
&self,
|
|
|
|
|
key: K,
|
|
|
|
|
value: V,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
V: Into<JsValue>,
|
|
|
|
|
{
|
|
|
|
|
let key = key.into();
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let success be ? CreateDataProperty(O, P, V).
|
|
|
|
|
let success = self.create_data_property(key.clone(), value, context)?;
|
|
|
|
|
// 4. If success is false, throw a TypeError exception.
|
|
|
|
|
if !success {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message(format!("cannot redefine property: {key}"))
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
// 5. Return success.
|
|
|
|
|
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:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
|
|
|
|
|
pub fn define_property_or_throw<K, P>(
|
|
|
|
|
&self,
|
|
|
|
|
key: K,
|
|
|
|
|
desc: P,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
P: Into<PropertyDescriptor>,
|
|
|
|
|
{
|
|
|
|
|
let key = key.into();
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
|
|
|
|
|
let success = self.__define_own_property__(
|
|
|
|
|
&key,
|
|
|
|
|
desc.into(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)?;
|
|
|
|
|
// 4. If success is false, throw a TypeError exception.
|
|
|
|
|
if !success {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message(format!("cannot redefine property: {key}"))
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
// 5. Return success.
|
|
|
|
|
Ok(success)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Defines the property or throws a `TypeError` if the operation fails.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
|
|
|
|
|
pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
let key = key.into();
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let success be ? O.[[Delete]](P).
|
|
|
|
|
let success = self.__delete__(&key, &mut InternalMethodContext::new(context))?;
|
|
|
|
|
// 4. If success is false, throw a TypeError exception.
|
|
|
|
|
if !success {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message(format!("cannot delete non-configurable property: {key}"))
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
// 5. Return success.
|
|
|
|
|
Ok(success)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if object has property.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
|
|
|
|
|
pub fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Return ? O.[[HasProperty]](P).
|
|
|
|
|
|
|
|
|
|
self.__has_property__(&key.into(), &mut InternalMethodContext::new(context))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if object has an own property.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
|
|
|
|
|
pub fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
let key = key.into();
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
|
|
|
|
let desc = self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
|
|
|
|
|
// 4. If desc is undefined, return false.
|
|
|
|
|
// 5. Return true.
|
|
|
|
|
Ok(desc.is_some())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// `Call ( F, V [ , argumentsList ] )`
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// Panics if the object is currently mutably borrowed.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-call
|
|
|
|
|
#[track_caller]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn call(
|
|
|
|
|
&self,
|
|
|
|
|
this: &JsValue,
|
|
|
|
|
args: &[JsValue],
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<JsValue> {
|
|
|
|
|
// SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List.
|
|
|
|
|
// SKIP: 2. If IsCallable(F) is false, throw a TypeError exception.
|
|
|
|
|
// NOTE(HalidOdat): For object's that are not callable we implement a special __call__ internal method
|
|
|
|
|
// that throws on call.
|
|
|
|
|
|
|
|
|
|
context.vm.push(this.clone()); // this
|
|
|
|
|
context.vm.push(self.clone()); // func
|
|
|
|
|
let argument_count = args.len();
|
|
|
|
|
context.vm.push_values(args);
|
|
|
|
|
|
|
|
|
|
// 3. Return ? F.[[Call]](V, argumentsList).
|
|
|
|
|
let frame_index = context.vm.frames.len();
|
|
|
|
|
let is_complete = self.__call__(argument_count).resolve(context)?;
|
|
|
|
|
|
|
|
|
|
if is_complete {
|
|
|
|
|
return Ok(context.vm.pop());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.vm.frames[frame_index].set_exit_early(true);
|
|
|
|
|
|
|
|
|
|
let result = context.run().consume();
|
|
|
|
|
|
|
|
|
|
context.vm.pop_frame().expect("frame must exist");
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
|
|
|
|
|
///
|
|
|
|
|
/// Construct an instance of this object with the specified arguments.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// Panics if the object is currently mutably borrowed.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-construct
|
|
|
|
|
#[track_caller]
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn construct(
|
|
|
|
|
&self,
|
|
|
|
|
args: &[JsValue],
|
|
|
|
|
new_target: Option<&Self>,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<Self> {
|
|
|
|
|
// 1. If newTarget is not present, set newTarget to F.
|
|
|
|
|
let new_target = new_target.unwrap_or(self);
|
|
|
|
|
|
|
|
|
|
context.vm.push(self.clone()); // func
|
|
|
|
|
let argument_count = args.len();
|
|
|
|
|
context.vm.push_values(args);
|
|
|
|
|
context.vm.push(new_target.clone());
|
|
|
|
|
|
|
|
|
|
// 2. If argumentsList is not present, set argumentsList to a new empty List.
|
|
|
|
|
// 3. Return ? F.[[Construct]](argumentsList, newTarget).
|
|
|
|
|
let frame_index = context.vm.frames.len();
|
|
|
|
|
let is_complete = self.__construct__(argument_count).resolve(context)?;
|
|
|
|
|
|
|
|
|
|
if is_complete {
|
|
|
|
|
let result = context.vm.pop();
|
|
|
|
|
return Ok(result
|
|
|
|
|
.as_object()
|
|
|
|
|
.expect("construct value should be an object")
|
|
|
|
|
.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.vm.frames[frame_index].set_exit_early(true);
|
|
|
|
|
|
|
|
|
|
let result = context.run().consume();
|
|
|
|
|
|
|
|
|
|
context.vm.pop_frame().expect("frame must exist");
|
|
|
|
|
|
|
|
|
|
Ok(result?.as_object().expect("should be an object").clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel
|
|
|
|
|
pub fn set_integrity_level(
|
|
|
|
|
&self,
|
|
|
|
|
level: IntegrityLevel,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool> {
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: level is either sealed or frozen.
|
|
|
|
|
|
|
|
|
|
// 3. Let status be ? O.[[PreventExtensions]]().
|
|
|
|
|
let status = self.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
|
|
|
|
|
// 4. If status is false, return false.
|
|
|
|
|
if !status {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Let keys be ? O.[[OwnPropertyKeys]]().
|
|
|
|
|
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
|
|
|
|
|
|
|
|
|
|
match level {
|
|
|
|
|
// 6. If level is sealed, then
|
|
|
|
|
IntegrityLevel::Sealed => {
|
|
|
|
|
// a. For each element k of keys, do
|
|
|
|
|
for k in keys {
|
|
|
|
|
// i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
|
|
|
|
|
self.define_property_or_throw(
|
|
|
|
|
k,
|
|
|
|
|
PropertyDescriptor::builder().configurable(false).build(),
|
|
|
|
|
context,
|
|
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 7. Else,
|
|
|
|
|
// a. Assert: level is frozen.
|
|
|
|
|
IntegrityLevel::Frozen => {
|
|
|
|
|
// b. For each element k of keys, do
|
|
|
|
|
for k in keys {
|
|
|
|
|
// i. Let currentDesc be ? O.[[GetOwnProperty]](k).
|
|
|
|
|
let current_desc =
|
|
|
|
|
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
|
|
|
|
|
// ii. If currentDesc is not undefined, then
|
|
|
|
|
if let Some(current_desc) = current_desc {
|
|
|
|
|
// 1. If IsAccessorDescriptor(currentDesc) is true, then
|
|
|
|
|
let desc = if current_desc.is_accessor_descriptor() {
|
|
|
|
|
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
|
|
|
|
|
PropertyDescriptor::builder().configurable(false).build()
|
|
|
|
|
// 2. Else,
|
|
|
|
|
} else {
|
|
|
|
|
// a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
|
|
|
|
|
PropertyDescriptor::builder()
|
|
|
|
|
.configurable(false)
|
|
|
|
|
.writable(false)
|
|
|
|
|
.build()
|
|
|
|
|
};
|
|
|
|
|
// 3. Perform ? DefinePropertyOrThrow(O, k, desc).
|
|
|
|
|
self.define_property_or_throw(k, desc, context)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 8. Return true.
|
|
|
|
|
Ok(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel
|
|
|
|
|
pub fn test_integrity_level(
|
|
|
|
|
&self,
|
|
|
|
|
level: IntegrityLevel,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool> {
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Assert: level is either sealed or frozen.
|
|
|
|
|
|
|
|
|
|
// 3. Let extensible be ? IsExtensible(O).
|
|
|
|
|
let extensible = self.is_extensible(context)?;
|
|
|
|
|
|
|
|
|
|
// 4. If extensible is true, return false.
|
|
|
|
|
if extensible {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. NOTE: If the object is extensible, none of its properties are examined.
|
|
|
|
|
// 6. Let keys be ? O.[[OwnPropertyKeys]]().
|
|
|
|
|
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
|
|
|
|
|
|
|
|
|
|
// 7. For each element k of keys, do
|
|
|
|
|
for k in keys {
|
|
|
|
|
// a. Let currentDesc be ? O.[[GetOwnProperty]](k).
|
|
|
|
|
let current_desc =
|
|
|
|
|
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
|
|
|
|
|
// b. If currentDesc is not undefined, then
|
|
|
|
|
if let Some(current_desc) = current_desc {
|
|
|
|
|
// i. If currentDesc.[[Configurable]] is true, return false.
|
|
|
|
|
if current_desc.expect_configurable() {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
// ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
|
|
|
|
|
if level.is_frozen() && current_desc.is_data_descriptor() {
|
|
|
|
|
// 1. If currentDesc.[[Writable]] is true, return false.
|
|
|
|
|
if current_desc.expect_writable() {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 8. Return true.
|
|
|
|
|
Ok(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation [`LengthOfArrayLike ( obj )`][spec].
|
|
|
|
|
///
|
|
|
|
|
/// Returns the value of the "length" property of an array-like object.
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-lengthofarraylike
|
|
|
|
|
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<u64> {
|
|
|
|
|
// 1. Assert: Type(obj) is Object.
|
|
|
|
|
|
|
|
|
|
// NOTE: This is an optimization, most of the cases that `LengthOfArrayLike` will be called
|
|
|
|
|
// is for arrays. The "length" property of an array is stored in the first index.
|
|
|
|
|
if self.is_array() {
|
|
|
|
|
let borrowed_object = self.borrow();
|
|
|
|
|
// NOTE: using `to_u32` instead of `to_length` is an optimization,
|
|
|
|
|
// since arrays are limited to [0, 2^32 - 1] range.
|
|
|
|
|
return borrowed_object.properties().storage[0]
|
|
|
|
|
.to_u32(context)
|
|
|
|
|
.map(u64::from);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
|
|
|
|
|
self.get(utf16!("length"), context)?.to_length(context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
|
|
|
|
|
///
|
|
|
|
|
/// The abstract operation `SpeciesConstructor` takes arguments `O` (an Object) and
|
|
|
|
|
/// `defaultConstructor` (a constructor). It is used to retrieve the constructor that should be
|
|
|
|
|
/// used to create new objects that are derived from `O`. `defaultConstructor` is the
|
|
|
|
|
/// constructor to use if a constructor `@@species` property cannot be found starting from `O`.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
|
|
|
|
|
pub(crate) fn species_constructor<F>(
|
|
|
|
|
&self,
|
|
|
|
|
default_constructor: F,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<Self>
|
|
|
|
|
where
|
|
|
|
|
F: FnOnce(&StandardConstructors) -> &StandardConstructor,
|
|
|
|
|
{
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
|
|
|
|
|
// 2. Let C be ? Get(O, "constructor").
|
|
|
|
|
let c = self.get(CONSTRUCTOR, context)?;
|
|
|
|
|
|
|
|
|
|
// 3. If C is undefined, return defaultConstructor.
|
|
|
|
|
if c.is_undefined() {
|
|
|
|
|
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. If Type(C) is not Object, throw a TypeError exception.
|
|
|
|
|
let c = c.as_object().ok_or_else(|| {
|
|
|
|
|
JsNativeError::typ().with_message("property 'constructor' is not an object")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// 5. Let S be ? Get(C, @@species).
|
Make `JsSymbol` thread-safe (#2539)
The section about `Symbol` on the [specification](https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type) says:
> The Symbol type is the set of all non-String values that may be used as the key of an Object property ([6.1.7](https://tc39.es/ecma262/#sec-object-type)).
Each possible Symbol value is unique and immutable.
Our previous implementation of `JsSymbol` used `Rc` and a thread local `Cell<usize>`. However, this meant that two different symbols in two different threads could share the same hash, making symbols not unique.
Also, the [GlobalSymbolRegistry](https://tc39.es/ecma262/#table-globalsymbolregistry-record-fields) is meant to be shared by all realms, including realms that are not in the same thread as the main one; this forces us to replace our current thread local global symbol registry with a thread-safe one that uses `DashMap` for concurrent access. However, the global symbol registry uses `JsString`s as keys and values, which forces us to either use `Vec<u16>` instead (wasteful and needs to allocate to convert to `JsString` on each access) or make `JsString` thread-safe with an atomic counter. For this reason, I implemented the second option.
This PR changes the following:
- Makes `JsSymbol` thread-safe by using Arc instead of Rc, and making `SYMBOL_HASH_COUNT` an `AtomicU64`.
- ~~Makes `JsString` thread-safe by using `AtomicUsize` instead of `Cell<usize>` for its ref count.~~ EDIT: Talked with @jasonwilliams and we decided to use `Box<[u16]>` for the global registry instead, because this won't penalize common usage of `JsString`, which is used a LOT more than `JsSymbol`.
- Makes the `GLOBAL_SYMBOL_REGISTRY` truly global, using `DashMap` as our global map that is shared by all threads.
- Replaces some thread locals with thread-safe alternatives, such as static arrays and static indices.
- Various improvements to all related code for this.
2 years ago
|
|
|
|
let s = c.get(JsSymbol::species(), context)?;
|
|
|
|
|
|
|
|
|
|
// 6. If S is either undefined or null, return defaultConstructor.
|
|
|
|
|
if s.is_null_or_undefined() {
|
|
|
|
|
return Ok(default_constructor(context.intrinsics().constructors()).constructor());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. If IsConstructor(S) is true, return S.
|
|
|
|
|
if let Some(s) = s.as_constructor() {
|
|
|
|
|
return Ok(s.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 8. Throw a TypeError exception.
|
|
|
|
|
Err(JsNativeError::typ()
|
|
|
|
|
.with_message("property 'constructor' is not a constructor")
|
|
|
|
|
.into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// It is used to iterate over names of object's keys.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
|
|
|
|
|
pub(crate) fn enumerable_own_property_names(
|
|
|
|
|
&self,
|
|
|
|
|
kind: PropertyNameKind,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<Vec<JsValue>> {
|
|
|
|
|
// 1. Assert: Type(O) is Object.
|
|
|
|
|
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
|
|
|
|
|
let own_keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
|
|
|
|
|
// 3. Let properties be a new empty List.
|
|
|
|
|
let mut properties = vec![];
|
|
|
|
|
|
|
|
|
|
// 4. For each element key of ownKeys, do
|
|
|
|
|
for key in own_keys {
|
|
|
|
|
// a. If Type(key) is String, then
|
|
|
|
|
let key_str = match &key {
|
|
|
|
|
PropertyKey::String(s) => Some(s.clone()),
|
|
|
|
|
PropertyKey::Index(i) => Some(i.get().to_string().into()),
|
|
|
|
|
PropertyKey::Symbol(_) => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(key_str) = key_str {
|
|
|
|
|
// i. Let desc be ? O.[[GetOwnProperty]](key).
|
|
|
|
|
let desc =
|
|
|
|
|
self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
|
|
|
|
|
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
|
|
|
|
|
if let Some(desc) = desc {
|
|
|
|
|
if desc.expect_enumerable() {
|
|
|
|
|
match kind {
|
|
|
|
|
// 1. If kind is key, append key to properties.
|
|
|
|
|
PropertyNameKind::Key => properties.push(key_str.into()),
|
|
|
|
|
// 2. Else,
|
|
|
|
|
// a. Let value be ? Get(O, key).
|
|
|
|
|
// b. If kind is value, append value to properties.
|
|
|
|
|
PropertyNameKind::Value => {
|
|
|
|
|
properties.push(self.get(key.clone(), context)?);
|
|
|
|
|
}
|
|
|
|
|
// c. Else,
|
|
|
|
|
// i. Assert: kind is key+value.
|
|
|
|
|
// ii. Let entry be ! CreateArrayFromList(« key, value »).
|
|
|
|
|
// iii. Append entry to properties.
|
|
|
|
|
PropertyNameKind::KeyAndValue => properties.push(
|
|
|
|
|
Array::create_array_from_list(
|
|
|
|
|
[key_str.into(), self.get(key.clone(), context)?],
|
|
|
|
|
context,
|
|
|
|
|
)
|
|
|
|
|
.into(),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Return properties.
|
|
|
|
|
Ok(properties)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `GetMethod ( V, P )`
|
|
|
|
|
///
|
|
|
|
|
/// Retrieves the value of a specific property, when the value of the property is expected to be a function.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
|
|
|
|
|
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<Self>>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// Note: The spec specifies this function for JsValue.
|
|
|
|
|
// It is implemented for JsObject for convenience.
|
|
|
|
|
|
|
|
|
|
// 1. Assert: IsPropertyKey(P) is true.
|
|
|
|
|
// 2. Let func be ? GetV(V, P).
|
|
|
|
|
|
|
|
|
|
match &self.__get__(
|
|
|
|
|
&key.into(),
|
|
|
|
|
self.clone().into(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)? {
|
|
|
|
|
// 3. If func is either undefined or null, return undefined.
|
|
|
|
|
JsValue::Undefined | JsValue::Null => Ok(None),
|
|
|
|
|
// 5. Return func.
|
|
|
|
|
JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())),
|
|
|
|
|
// 4. If IsCallable(func) is false, throw a TypeError exception.
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
_ => Err(JsNativeError::typ()
|
|
|
|
|
.with_message("value returned for property of object is not a function")
|
|
|
|
|
.into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `IsArray ( argument )`
|
|
|
|
|
///
|
|
|
|
|
/// Check if a value is an array.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-isarray
|
|
|
|
|
pub(crate) fn is_array_abstract(&self) -> JsResult<bool> {
|
|
|
|
|
// Note: The spec specifies this function for JsValue.
|
|
|
|
|
// It is implemented for JsObject for convenience.
|
|
|
|
|
|
|
|
|
|
// 2. If argument is an Array exotic object, return true.
|
|
|
|
|
if self.is_array() {
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. If argument is a Proxy exotic object, then
|
|
|
|
|
let object = self.borrow();
|
|
|
|
|
if let Some(proxy) = object.downcast_ref::<Proxy>() {
|
|
|
|
|
// a. If argument.[[ProxyHandler]] is null, throw a TypeError exception.
|
|
|
|
|
// b. Let target be argument.[[ProxyTarget]].
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
let (target, _) = proxy.try_data()?;
|
|
|
|
|
|
|
|
|
|
// c. Return ? IsArray(target).
|
|
|
|
|
return target.is_array_abstract();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Return false.
|
|
|
|
|
Ok(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation [`GetFunctionRealm`][spec].
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm
|
|
|
|
|
pub(crate) fn get_function_realm(&self, context: &mut Context) -> JsResult<Realm> {
|
|
|
|
|
let constructor = self.borrow();
|
|
|
|
|
if let Some(fun) = constructor.downcast_ref::<OrdinaryFunction>() {
|
|
|
|
|
return Ok(fun.realm().clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(f) = constructor.downcast_ref::<NativeFunctionObject>() {
|
|
|
|
|
return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(bound) = constructor.downcast_ref::<BoundFunction>() {
|
|
|
|
|
let fun = bound.target_function().clone();
|
|
|
|
|
drop(constructor);
|
|
|
|
|
return fun.get_function_realm(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(proxy) = constructor.downcast_ref::<Proxy>() {
|
|
|
|
|
let (fun, _) = proxy.try_data()?;
|
|
|
|
|
drop(constructor);
|
|
|
|
|
return fun.get_function_realm(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(context.realm().clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// todo: CopyDataProperties
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `PrivateElementFind ( O, P )`
|
|
|
|
|
///
|
|
|
|
|
/// Get the private element from an object.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-privateelementfind
|
|
|
|
|
#[allow(clippy::similar_names)]
|
|
|
|
|
pub(crate) fn private_element_find(
|
|
|
|
|
&self,
|
|
|
|
|
name: &PrivateName,
|
|
|
|
|
is_getter: bool,
|
|
|
|
|
is_setter: bool,
|
|
|
|
|
) -> Option<PrivateElement> {
|
|
|
|
|
// 1. If O.[[PrivateElements]] contains a PrivateElement whose [[Key]] is P, then
|
|
|
|
|
for (key, value) in &self.borrow().private_elements {
|
|
|
|
|
if key == name {
|
|
|
|
|
// a. Let entry be that PrivateElement.
|
|
|
|
|
// b. Return entry.
|
|
|
|
|
if let PrivateElement::Accessor { getter, setter } = value {
|
|
|
|
|
if getter.is_some() && is_getter || setter.is_some() && is_setter {
|
|
|
|
|
return Some(value.clone());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return Some(value.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Return empty.
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `PrivateFieldAdd ( O, P, value )`
|
|
|
|
|
///
|
|
|
|
|
/// Add private field to an object.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-privatefieldadd
|
|
|
|
|
pub(crate) fn private_field_add(
|
|
|
|
|
&self,
|
|
|
|
|
name: &PrivateName,
|
|
|
|
|
value: JsValue,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<()> {
|
|
|
|
|
// 1. If the host is a web browser, then
|
|
|
|
|
// a. Perform ? HostEnsureCanAddPrivateElement(O).
|
Implement host hooks and job queues APIs (#2529)
Follows from #2528, and should complement #2411 to implement the module import hooks.
~~Similarly to the Intl/ICU4X PR (#2478), this has a lot of trivial changes caused by the new lifetimes. I thought about passing the queue and the hooks by value, but it was very painful having to wrap everything with `Rc` in order to be accessible by the host.
In contrast, `&dyn` can be easily provided by the host and has the advantage of not requiring additional allocations, with the downside of adding two more lifetimes to our `Context`, but I think it's worth.~~ I was able to unify all lifetimes into the shortest one of the three, making our API just like before!
Changes:
- Added a new `HostHooks` trait and a `&dyn HostHooks` field to `Context`. This allows hosts to implement the trait for their custom type, then pass it to the context.
- Added a new `JobQueue` trait and a `&dyn JobQueue` field to our `Context`, allowing custom event loops and other fun things.
- Added two simple implementations of `JobQueue`: `IdleJobQueue` which does nothing and `SimpleJobQueue` which runs all jobs until all successfully complete or until any of them throws an error.
- Modified `boa_cli` to run all jobs until the queue is empty, even if a job returns `Err`. This also prints all errors to the user.
2 years ago
|
|
|
|
context
|
|
|
|
|
.host_hooks()
|
|
|
|
|
.ensure_can_add_private_element(self, context)?;
|
|
|
|
|
|
|
|
|
|
// 2. Let entry be PrivateElementFind(O, P).
|
|
|
|
|
let entry = self.private_element_find(name, false, false);
|
|
|
|
|
|
|
|
|
|
// 3. If entry is not empty, throw a TypeError exception.
|
|
|
|
|
if entry.is_some() {
|
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message("Private field already exists on prototype")
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Append PrivateElement { [[Key]]: P, [[Kind]]: field, [[Value]]: value } to O.[[PrivateElements]].
|
|
|
|
|
self.borrow_mut()
|
|
|
|
|
.private_elements
|
|
|
|
|
.push((name.clone(), PrivateElement::Field(value)));
|
|
|
|
|
|
|
|
|
|
// 5. Return unused.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `PrivateMethodOrAccessorAdd ( O, method )`
|
|
|
|
|
///
|
|
|
|
|
/// Add private method or accessor to an object.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-privatemethodoraccessoradd
|
|
|
|
|
pub(crate) fn private_method_or_accessor_add(
|
|
|
|
|
&self,
|
|
|
|
|
name: &PrivateName,
|
|
|
|
|
method: &PrivateElement,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<()> {
|
|
|
|
|
// 1. Assert: method.[[Kind]] is either method or accessor.
|
|
|
|
|
assert!(matches!(
|
|
|
|
|
method,
|
|
|
|
|
PrivateElement::Method(_) | PrivateElement::Accessor { .. }
|
|
|
|
|
));
|
|
|
|
|
let (getter, setter) = if let PrivateElement::Accessor { getter, setter } = method {
|
|
|
|
|
(getter.is_some(), setter.is_some())
|
|
|
|
|
} else {
|
|
|
|
|
(false, false)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 2. If the host is a web browser, then
|
|
|
|
|
// a. Perform ? HostEnsureCanAddPrivateElement(O).
|
Implement host hooks and job queues APIs (#2529)
Follows from #2528, and should complement #2411 to implement the module import hooks.
~~Similarly to the Intl/ICU4X PR (#2478), this has a lot of trivial changes caused by the new lifetimes. I thought about passing the queue and the hooks by value, but it was very painful having to wrap everything with `Rc` in order to be accessible by the host.
In contrast, `&dyn` can be easily provided by the host and has the advantage of not requiring additional allocations, with the downside of adding two more lifetimes to our `Context`, but I think it's worth.~~ I was able to unify all lifetimes into the shortest one of the three, making our API just like before!
Changes:
- Added a new `HostHooks` trait and a `&dyn HostHooks` field to `Context`. This allows hosts to implement the trait for their custom type, then pass it to the context.
- Added a new `JobQueue` trait and a `&dyn JobQueue` field to our `Context`, allowing custom event loops and other fun things.
- Added two simple implementations of `JobQueue`: `IdleJobQueue` which does nothing and `SimpleJobQueue` which runs all jobs until all successfully complete or until any of them throws an error.
- Modified `boa_cli` to run all jobs until the queue is empty, even if a job returns `Err`. This also prints all errors to the user.
2 years ago
|
|
|
|
context
|
|
|
|
|
.host_hooks()
|
|
|
|
|
.ensure_can_add_private_element(self, context)?;
|
|
|
|
|
|
|
|
|
|
// 3. Let entry be PrivateElementFind(O, method.[[Key]]).
|
|
|
|
|
let entry = self.private_element_find(name, getter, setter);
|
|
|
|
|
|
|
|
|
|
// 4. If entry is not empty, throw a TypeError exception.
|
|
|
|
|
if entry.is_some() {
|
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message("Private method already exists on prototype")
|
|
|
|
|
.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Append method to O.[[PrivateElements]].
|
|
|
|
|
self.borrow_mut()
|
|
|
|
|
.append_private_element(name.clone(), method.clone());
|
|
|
|
|
|
|
|
|
|
// 6. Return unused.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `PrivateGet ( O, P )`
|
|
|
|
|
///
|
|
|
|
|
/// Get the value of a private element.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-privateget
|
|
|
|
|
pub(crate) fn private_get(
|
|
|
|
|
&self,
|
|
|
|
|
name: &PrivateName,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<JsValue> {
|
|
|
|
|
// 1. Let entry be PrivateElementFind(O, P).
|
|
|
|
|
let entry = self.private_element_find(name, true, true);
|
|
|
|
|
|
|
|
|
|
match &entry {
|
|
|
|
|
// 2. If entry is empty, throw a TypeError exception.
|
|
|
|
|
None => Err(JsNativeError::typ()
|
|
|
|
|
.with_message("Private element does not exist on object")
|
|
|
|
|
.into()),
|
|
|
|
|
|
|
|
|
|
// 3. If entry.[[Kind]] is field or method, then
|
|
|
|
|
// a. Return entry.[[Value]].
|
|
|
|
|
Some(PrivateElement::Field(value)) => Ok(value.clone()),
|
|
|
|
|
Some(PrivateElement::Method(value)) => Ok(value.clone().into()),
|
|
|
|
|
|
|
|
|
|
// 4. Assert: entry.[[Kind]] is accessor.
|
|
|
|
|
Some(PrivateElement::Accessor { getter, .. }) => {
|
|
|
|
|
// 5. If entry.[[Get]] is undefined, throw a TypeError exception.
|
|
|
|
|
// 6. Let getter be entry.[[Get]].
|
|
|
|
|
let getter = getter.as_ref().ok_or_else(|| {
|
|
|
|
|
JsNativeError::typ()
|
|
|
|
|
.with_message("private property was defined without a getter")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// 7. Return ? Call(getter, O).
|
|
|
|
|
getter.call(&self.clone().into(), &[], context)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `PrivateSet ( O, P, value )`
|
|
|
|
|
///
|
|
|
|
|
/// Set the value of a private element.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-privateset
|
|
|
|
|
pub(crate) fn private_set(
|
|
|
|
|
&self,
|
|
|
|
|
name: &PrivateName,
|
|
|
|
|
value: JsValue,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<()> {
|
|
|
|
|
// 1. Let entry be PrivateElementFind(O, P).
|
|
|
|
|
// Note: This function is inlined here for mutable access.
|
|
|
|
|
let mut object_mut = self.borrow_mut();
|
|
|
|
|
let entry = object_mut
|
|
|
|
|
.private_elements
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find_map(|(key, value)| if key == name { Some(value) } else { None });
|
|
|
|
|
|
|
|
|
|
match entry {
|
|
|
|
|
// 2. If entry is empty, throw a TypeError exception.
|
|
|
|
|
None => {
|
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message("Private element does not exist on object")
|
|
|
|
|
.into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. If entry.[[Kind]] is field, then
|
|
|
|
|
// a. Set entry.[[Value]] to value.
|
|
|
|
|
Some(PrivateElement::Field(field)) => {
|
|
|
|
|
*field = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Else if entry.[[Kind]] is method, then
|
|
|
|
|
// a. Throw a TypeError exception.
|
|
|
|
|
Some(PrivateElement::Method(_)) => {
|
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message("private method is not writable")
|
|
|
|
|
.into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Else,
|
|
|
|
|
Some(PrivateElement::Accessor { setter, .. }) => {
|
|
|
|
|
// a. Assert: entry.[[Kind]] is accessor.
|
|
|
|
|
// b. If entry.[[Set]] is undefined, throw a TypeError exception.
|
|
|
|
|
// c. Let setter be entry.[[Set]].
|
|
|
|
|
let setter = setter.clone().ok_or_else(|| {
|
|
|
|
|
JsNativeError::typ()
|
|
|
|
|
.with_message("private property was defined without a setter")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// d. Perform ? Call(setter, O, « value »).
|
|
|
|
|
drop(object_mut);
|
|
|
|
|
setter.call(&self.clone().into(), &[value], context)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. Return unused.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `DefineField ( receiver, fieldRecord )`
|
|
|
|
|
///
|
|
|
|
|
/// Define a field on an object.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-definefield
|
|
|
|
|
pub(crate) fn define_field(
|
|
|
|
|
&self,
|
|
|
|
|
field_record: &ClassFieldDefinition,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<()> {
|
|
|
|
|
// 2. Let initializer be fieldRecord.[[Initializer]].
|
|
|
|
|
let initializer = match field_record {
|
|
|
|
|
ClassFieldDefinition::Public(_, function)
|
|
|
|
|
| ClassFieldDefinition::Private(_, function) => function,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 3. If initializer is not empty, then
|
|
|
|
|
// a. Let initValue be ? Call(initializer, receiver).
|
|
|
|
|
// 4. Else, let initValue be undefined.
|
|
|
|
|
let init_value = initializer.call(&self.clone().into(), &[], context)?;
|
|
|
|
|
|
|
|
|
|
match field_record {
|
|
|
|
|
// 1. Let fieldName be fieldRecord.[[Name]].
|
|
|
|
|
// 5. If fieldName is a Private Name, then
|
|
|
|
|
ClassFieldDefinition::Private(field_name, _) => {
|
|
|
|
|
// a. Perform ? PrivateFieldAdd(receiver, fieldName, initValue).
|
|
|
|
|
self.private_field_add(field_name, init_value, context)?;
|
|
|
|
|
}
|
|
|
|
|
// 1. Let fieldName be fieldRecord.[[Name]].
|
|
|
|
|
// 6. Else,
|
|
|
|
|
ClassFieldDefinition::Public(field_name, _) => {
|
|
|
|
|
// a. Assert: IsPropertyKey(fieldName) is true.
|
|
|
|
|
// b. Perform ? CreateDataPropertyOrThrow(receiver, fieldName, initValue).
|
|
|
|
|
self.create_data_property_or_throw(field_name.clone(), init_value, context)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. Return unused.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `InitializeInstanceElements ( O, constructor )`
|
|
|
|
|
///
|
|
|
|
|
/// Add private methods and fields from a class constructor to an object.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript specification][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-initializeinstanceelements
|
|
|
|
|
pub(crate) fn initialize_instance_elements(
|
|
|
|
|
&self,
|
|
|
|
|
constructor: &Self,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<()> {
|
|
|
|
|
let constructor_function = constructor
|
|
|
|
|
.downcast_ref::<OrdinaryFunction>()
|
|
|
|
|
.expect("class constructor must be function object");
|
|
|
|
|
|
|
|
|
|
// 1. Let methods be the value of constructor.[[PrivateMethods]].
|
|
|
|
|
// 2. For each PrivateElement method of methods, do
|
|
|
|
|
for (name, method) in constructor_function.get_private_methods() {
|
|
|
|
|
// a. Perform ? PrivateMethodOrAccessorAdd(O, method).
|
|
|
|
|
self.private_method_or_accessor_add(name, method, context)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Let fields be the value of constructor.[[Fields]].
|
|
|
|
|
// 4. For each element fieldRecord of fields, do
|
|
|
|
|
for field_record in constructor_function.get_fields() {
|
|
|
|
|
// a. Perform ? DefineField(O, fieldRecord).
|
|
|
|
|
self.define_field(field_record, context)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. Return unused.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `Invoke ( V, P [ , argumentsList ] )`
|
|
|
|
|
///
|
|
|
|
|
/// Calls a method property of an ECMAScript object.
|
|
|
|
|
///
|
|
|
|
|
/// Equivalent to the [`JsValue::invoke`] method, but specialized for objects.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-invoke
|
|
|
|
|
pub(crate) fn invoke<K>(
|
|
|
|
|
&self,
|
|
|
|
|
key: K,
|
|
|
|
|
args: &[JsValue],
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<JsValue>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
let this_value: JsValue = self.clone().into();
|
|
|
|
|
|
|
|
|
|
// 1. If argumentsList is not present, set argumentsList to a new empty List.
|
|
|
|
|
// 2. Let func be ? GetV(V, P).
|
|
|
|
|
let func = self.__get__(
|
|
|
|
|
&key.into(),
|
|
|
|
|
this_value.clone(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
// 3. Return ? Call(func, V, argumentsList)
|
|
|
|
|
func.call(&this_value, args, context)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl JsValue {
|
|
|
|
|
/// Abstract operation `GetV ( V, P )`.
|
|
|
|
|
///
|
|
|
|
|
/// Retrieves the value of a specific property of an ECMAScript language value. If the value is
|
|
|
|
|
/// not an object, the property lookup is performed using a wrapper object appropriate for the
|
|
|
|
|
/// type of the value.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-getv
|
|
|
|
|
pub(crate) fn get_v<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// 1. Let O be ? ToObject(V).
|
|
|
|
|
let o = self.to_object(context)?;
|
|
|
|
|
|
|
|
|
|
// 2. Return ? O.[[Get]](P, V).
|
|
|
|
|
|
|
|
|
|
o.__get__(
|
|
|
|
|
&key.into(),
|
|
|
|
|
self.clone(),
|
|
|
|
|
&mut InternalMethodContext::new(context),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `GetMethod ( V, P )`
|
|
|
|
|
///
|
|
|
|
|
/// Retrieves the value of a specific property, when the value of the property is expected to be a function.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
|
|
|
|
|
pub(crate) fn get_method<K>(&self, key: K, context: &mut Context) -> JsResult<Option<JsObject>>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// Note: The spec specifies this function for JsValue.
|
|
|
|
|
// The main part of the function is implemented for JsObject.
|
|
|
|
|
self.to_object(context)?.get_method(key, context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// It is used to create List value whose elements are provided by the indexed properties of
|
|
|
|
|
/// self.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
|
|
|
|
|
pub(crate) fn create_list_from_array_like(
|
|
|
|
|
&self,
|
|
|
|
|
element_types: &[Type],
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<Vec<Self>> {
|
|
|
|
|
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
|
|
|
|
|
let types = if element_types.is_empty() {
|
|
|
|
|
&[
|
|
|
|
|
Type::Undefined,
|
|
|
|
|
Type::Null,
|
|
|
|
|
Type::Boolean,
|
|
|
|
|
Type::String,
|
|
|
|
|
Type::Symbol,
|
|
|
|
|
Type::Number,
|
|
|
|
|
Type::BigInt,
|
|
|
|
|
Type::Object,
|
|
|
|
|
]
|
|
|
|
|
} else {
|
|
|
|
|
element_types
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 2. If Type(obj) is not Object, throw a TypeError exception.
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
let obj = self.as_object().ok_or_else(|| {
|
|
|
|
|
JsNativeError::typ().with_message("cannot create list from a primitive")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// 3. Let len be ? LengthOfArrayLike(obj).
|
|
|
|
|
let len = obj.length_of_array_like(context)?;
|
|
|
|
|
|
|
|
|
|
// 4. Let list be a new empty List.
|
|
|
|
|
let mut list = Vec::with_capacity(len as usize);
|
|
|
|
|
|
|
|
|
|
// 5. Let index be 0.
|
|
|
|
|
// 6. Repeat, while index < len,
|
|
|
|
|
for index in 0..len {
|
|
|
|
|
// a. Let indexName be ! ToString(𝔽(index)).
|
|
|
|
|
// b. Let next be ? Get(obj, indexName).
|
|
|
|
|
let next = obj.get(index, context)?;
|
|
|
|
|
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
|
|
|
|
|
if !types.contains(&next.get_type()) {
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
return Err(JsNativeError::typ().with_message("bad type").into());
|
|
|
|
|
}
|
|
|
|
|
// d. Append next as the last element of list.
|
|
|
|
|
list.push(next.clone());
|
|
|
|
|
// e. Set index to index + 1.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. Return list.
|
|
|
|
|
Ok(list)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation [`Call ( F, V [ , argumentsList ] )`][call].
|
|
|
|
|
///
|
|
|
|
|
/// Calls this value if the value is a callable object.
|
|
|
|
|
///
|
|
|
|
|
/// # Note
|
|
|
|
|
///
|
|
|
|
|
/// It is almost always better to try to obtain a callable object first with [`JsValue::as_callable`],
|
|
|
|
|
/// then calling [`JsObject::call`], since that allows reusing the unwrapped function for other
|
|
|
|
|
/// operations. This method is only an utility method for when the spec directly uses `Call`
|
|
|
|
|
/// without using the value as a proper object.
|
|
|
|
|
///
|
|
|
|
|
/// [call]: https://tc39.es/ecma262/#sec-call
|
|
|
|
|
#[inline]
|
|
|
|
|
pub(crate) fn call(&self, this: &Self, args: &[Self], context: &mut Context) -> JsResult<Self> {
|
|
|
|
|
let Some(object) = self.as_object() else {
|
|
|
|
|
return Err(JsNativeError::typ()
|
|
|
|
|
.with_message(format!(
|
|
|
|
|
"value with type `{}` is not callable",
|
|
|
|
|
self.type_of()
|
|
|
|
|
))
|
|
|
|
|
.into());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
object.call(this, args, context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `( V, P [ , argumentsList ] )`
|
|
|
|
|
///
|
|
|
|
|
/// Calls a method property of an ECMAScript language value.
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-invoke
|
|
|
|
|
pub(crate) fn invoke<K>(&self, key: K, args: &[Self], context: &mut Context) -> JsResult<Self>
|
|
|
|
|
where
|
|
|
|
|
K: Into<PropertyKey>,
|
|
|
|
|
{
|
|
|
|
|
// 1. If argumentsList is not present, set argumentsList to a new empty List.
|
|
|
|
|
// 2. Let func be ? GetV(V, P).
|
|
|
|
|
let func = self.get_v(key, context)?;
|
|
|
|
|
|
|
|
|
|
// 3. Return ? Call(func, V, argumentsList)
|
|
|
|
|
func.call(self, args, context)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Abstract operation `OrdinaryHasInstance ( C, O )`
|
|
|
|
|
///
|
|
|
|
|
/// More information:
|
|
|
|
|
/// - [ECMAScript reference][spec]
|
|
|
|
|
///
|
|
|
|
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
|
|
|
|
|
pub fn ordinary_has_instance(
|
|
|
|
|
function: &Self,
|
|
|
|
|
object: &Self,
|
|
|
|
|
context: &mut Context,
|
|
|
|
|
) -> JsResult<bool> {
|
|
|
|
|
// 1. If IsCallable(C) is false, return false.
|
|
|
|
|
let Some(function) = function.as_callable() else {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 2. If C has a [[BoundTargetFunction]] internal slot, then
|
|
|
|
|
if let Some(bound_function) = function.downcast_ref::<BoundFunction>() {
|
|
|
|
|
// a. Let BC be C.[[BoundTargetFunction]].
|
|
|
|
|
// b. Return ? InstanceofOperator(O, BC).
|
|
|
|
|
return Self::instance_of(
|
|
|
|
|
object,
|
|
|
|
|
&bound_function.target_function().clone().into(),
|
|
|
|
|
context,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Some(mut object) = object.as_object().cloned() else {
|
|
|
|
|
// 3. If Type(O) is not Object, return false.
|
|
|
|
|
return Ok(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 4. Let P be ? Get(C, "prototype").
|
|
|
|
|
let prototype = function.get(PROTOTYPE, context)?;
|
|
|
|
|
|
|
|
|
|
// 5. If Type(P) is not Object, throw a TypeError exception.
|
|
|
|
|
let prototype = prototype.as_object().ok_or_else(|| {
|
|
|
|
|
JsNativeError::typ()
|
Create new lazy Error type (#2283)
This is an experiment that tries to migrate the codebase from eager `Error` objects to lazy ones.
In short words, this redefines `JsResult = Result<JsValue, JsError>`, where `JsError` is a brand new type that stores only the essential part of an error type, and only transforms those errors to `JsObject`s on demand (when having to pass them as arguments to functions or store them inside async/generators).
This change is pretty big, because it unblocks a LOT of code from having to take a `&mut Context` on each call. It also paves the road for possibly making `JsError` a proper variant of `JsValue`, which can be a pretty big optimization for try/catch.
A downside of this is that it exposes some brand new error types to our public API. However, we can now implement `Error` on `JsError`, making our `JsResult` type a bit more inline with Rust's best practices.
~Will mark this as draft, since it's missing some documentation and a lot of examples, but~ it's pretty much feature complete. As always, any comments about the design are very much appreciated!
Note: Since there are a lot of changes which are essentially just rewriting `context.throw` to `JsNativeError::%type%`, I'll leave an "index" of the most important changes here:
- [boa_engine/src/error.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-f15f2715655440626eefda5c46193d29856f4949ad37380c129a8debc6b82f26)
- [boa_engine/src/builtins/error/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-3eb1e4b4b5c7210eb98192a5277f5a239148423c6b970c4ae05d1b267f8f1084)
- [boa_tester/src/exec/mod.rs](https://github.com/boa-dev/boa/pull/2283/files#diff-fc3d7ad7b5e64574258c9febbe56171f3309b74e0c8da35238a76002f3ee34d9)
2 years ago
|
|
|
|
.with_message("function has non-object prototype in instanceof check")
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
// 6. Repeat,
|
|
|
|
|
loop {
|
|
|
|
|
// a. Set O to ? O.[[GetPrototypeOf]]().
|
|
|
|
|
object = match object.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
|
|
|
|
|
Some(obj) => obj,
|
|
|
|
|
// b. If O is null, return false.
|
|
|
|
|
None => return Ok(false),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// c. If SameValue(P, O) is true, return true.
|
|
|
|
|
if JsObject::equals(&object, prototype) {
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|