Browse Source

Disallow changing type of already created objects (#3410)

* Disallow changing type of already created objects

* Fix regressions

* Fix spec steps

* Rollback restrictions and add new conversion
pull/3422/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
06bb7dea80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      boa_ast/src/expression/regexp.rs
  2. 6
      boa_cli/src/main.rs
  3. 31
      boa_engine/src/builtins/error/type.rs
  4. 199
      boa_engine/src/builtins/mod.rs
  5. 150
      boa_engine/src/builtins/regexp/mod.rs
  6. 30
      boa_engine/src/context/intrinsics.rs
  7. 4
      boa_engine/src/context/mod.rs
  8. 26
      boa_engine/src/module/mod.rs
  9. 1
      boa_engine/src/module/synthetic.rs
  10. 34
      boa_engine/src/native_function.rs
  11. 12
      boa_engine/src/object/builtins/jsfunction.rs
  12. 305
      boa_engine/src/object/builtins/jspromise.rs
  13. 10
      boa_engine/src/object/builtins/jsregexp.rs
  14. 41
      boa_engine/src/object/internal_methods/function.rs
  15. 102
      boa_engine/src/object/mod.rs
  16. 4
      boa_engine/src/object/operations.rs
  17. 17
      boa_examples/src/bin/modules.rs
  18. 4
      boa_examples/src/bin/synthetic.rs
  19. 25
      boa_tester/src/exec/mod.rs

6
boa_ast/src/expression/regexp.rs

@ -35,21 +35,21 @@ pub struct RegExpLiteral {
}
impl RegExpLiteral {
/// Create a new [`RegExp`].
/// Create a new [`RegExpLiteral`].
#[inline]
#[must_use]
pub const fn new(pattern: Sym, flags: Sym) -> Self {
Self { pattern, flags }
}
/// Get the pattern part of the [`RegExp`].
/// Get the pattern part of the [`RegExpLiteral`].
#[inline]
#[must_use]
pub const fn pattern(&self) -> Sym {
self.pattern
}
/// Get the flags part of the [`RegExp`].
/// Get the flags part of the [`RegExpLiteral`].
#[inline]
#[must_use]
pub const fn flags(&self) -> Sym {

6
boa_cli/src/main.rs

@ -325,7 +325,7 @@ fn evaluate_files(
Err(v) => eprintln!("Uncaught {v}"),
}
} else if args.module {
let result = (|| {
let result: JsResult<PromiseState> = (|| {
let module = Module::parse(Source::from_bytes(&buffer), None, context)?;
loader.insert(
@ -334,10 +334,10 @@ fn evaluate_files(
module.clone(),
);
let promise = module.load_link_evaluate(context)?;
let promise = module.load_link_evaluate(context);
context.run_jobs();
promise.state()
Ok(promise.state())
})();
match result {

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

@ -20,7 +20,8 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind},
native_function::NativeFunctionObject,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
@ -115,15 +116,6 @@ pub(crate) struct ThrowTypeError;
impl IntrinsicObject for ThrowTypeError {
fn init(realm: &Realm) {
fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
Err(JsNativeError::typ()
.with_message(
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
functions or the arguments objects for calls to them",
)
.into())
}
let obj = BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(realm.intrinsics().constructors().function().prototype())
.static_property(utf16!("length"), 0, Attribute::empty())
@ -132,12 +124,21 @@ impl IntrinsicObject for ThrowTypeError {
let mut obj = obj.borrow_mut();
obj.extensible = false;
*obj.kind_mut() = ObjectKind::NativeFunction {
function: NativeFunction::from_fn_ptr(throw_type_error),
*obj.as_native_function_mut()
.expect("`%ThrowTypeError%` must be a function") = NativeFunctionObject {
f: NativeFunction::from_fn_ptr(|_, _, _| {
Err(JsNativeError::typ()
.with_message(
"'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \
functions or the arguments objects for calls to them",
)
.into())
}),
constructor: None,
realm: realm.clone(),
}
realm: Some(realm.clone()),
};
obj.extensible = false;
}
fn get(intrinsics: &Intrinsics) -> JsObject {

199
boa_engine/src/builtins/mod.rs

@ -46,6 +46,9 @@ pub(crate) mod options;
#[cfg(feature = "temporal")]
pub mod temporal;
use boa_gc::GcRefMut;
use self::function::ConstructorKind;
pub(crate) use self::{
array::Array,
async_function::AsyncFunction,
@ -100,11 +103,11 @@ use crate::{
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
native_function::{NativeFunction, NativeFunctionPointer},
native_function::{NativeFunction, NativeFunctionObject, NativeFunctionPointer},
object::{
shape::{property_table::PropertyTableInner, slot::SlotAttributes},
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, ObjectKind,
CONSTRUCTOR, PROTOTYPE,
FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR,
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
@ -405,85 +408,6 @@ pub(crate) fn set_default_global_bindings(context: &mut Context<'_>) -> JsResult
// === Builder typestate ===
#[derive(Debug)]
enum BuiltInObjectInitializer {
Shared(JsObject),
Unique { object: Object, data: ObjectData },
}
impl BuiltInObjectInitializer {
/// Inserts a new property descriptor into the builtin.
fn insert<K, P>(&mut self, key: K, property: P)
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
match self {
Self::Shared(obj) => obj.borrow_mut().insert(key, property),
Self::Unique { object, .. } => object.insert(key, property),
};
}
/// Sets the prototype of the builtin
fn set_prototype(&mut self, prototype: JsObject) {
match self {
Self::Shared(obj) => {
let mut obj = obj.borrow_mut();
obj.set_prototype(prototype);
}
Self::Unique { object, .. } => {
object.set_prototype(prototype);
}
}
}
/// Sets the `ObjectData` of the builtin.
///
/// # Panics
///
/// Panics if the builtin is a shared builtin and the data's vtable is not the same as the
/// builtin's vtable.
fn set_data(&mut self, new_data: ObjectData) {
match self {
Self::Shared(obj) => {
assert!(
std::ptr::eq(obj.vtable(), new_data.internal_methods),
"intrinsic object's vtable didn't match with new data"
);
*obj.borrow_mut().kind_mut() = new_data.kind;
}
Self::Unique { ref mut data, .. } => *data = new_data,
}
}
/// Gets a shared object from the builtin, transitioning its state if it's necessary.
fn as_shared(&mut self) -> JsObject {
match std::mem::replace(
self,
Self::Unique {
object: Object::default(),
data: ObjectData::ordinary(),
},
) {
Self::Shared(obj) => {
*self = Self::Shared(obj.clone());
obj
}
Self::Unique { mut object, data } => {
*object.kind_mut() = data.kind;
let obj = JsObject::from_object_and_vtable(object, data.internal_methods);
*self = Self::Shared(obj.clone());
obj
}
}
}
/// Converts the builtin into a shared object.
fn into_shared(mut self) -> JsObject {
self.as_shared()
}
}
/// Marker for a constructor function.
struct Constructor {
prototype: JsObject,
@ -528,11 +452,11 @@ struct OrdinaryObject;
/// Applies the pending builder data to the object.
trait ApplyToObject {
fn apply_to(self, object: &mut BuiltInObjectInitializer);
fn apply_to(self, object: &JsObject);
}
impl ApplyToObject for Constructor {
fn apply_to(self, object: &mut BuiltInObjectInitializer) {
fn apply_to(self, object: &JsObject) {
object.insert(
PROTOTYPE,
PropertyDescriptor::builder()
@ -542,15 +466,13 @@ impl ApplyToObject for Constructor {
.configurable(false),
);
let object = object.as_shared();
{
let mut prototype = self.prototype.borrow_mut();
prototype.set_prototype(self.inherits);
prototype.insert(
CONSTRUCTOR,
PropertyDescriptor::builder()
.value(object)
.value(object.clone())
.writable(self.attributes.writable())
.enumerable(self.attributes.enumerable())
.configurable(self.attributes.configurable()),
@ -560,21 +482,23 @@ impl ApplyToObject for Constructor {
}
impl ApplyToObject for ConstructorNoProto {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
fn apply_to(self, _: &JsObject) {}
}
impl ApplyToObject for OrdinaryFunction {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
fn apply_to(self, _: &JsObject) {}
}
impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &mut BuiltInObjectInitializer) {
let function = ObjectData::native_function(
NativeFunction::from_fn_ptr(self.function),
S::IS_CONSTRUCTOR,
self.realm,
);
object.set_data(function);
fn apply_to(self, object: &JsObject) {
{
let mut function =
GcRefMut::try_map(object.borrow_mut(), Object::as_native_function_mut)
.expect("Builtin must be a function object");
function.f = NativeFunction::from_fn_ptr(self.function);
function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base);
function.realm = Some(self.realm);
}
object.insert(
utf16!("length"),
PropertyDescriptor::builder()
@ -597,7 +521,7 @@ impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
}
impl ApplyToObject for OrdinaryObject {
fn apply_to(self, _: &mut BuiltInObjectInitializer) {}
fn apply_to(self, _: &JsObject) {}
}
/// Builder for creating built-in objects, like `Array`.
@ -608,30 +532,18 @@ impl ApplyToObject for OrdinaryObject {
#[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"]
struct BuiltInBuilder<'ctx, Kind> {
realm: &'ctx Realm,
object: BuiltInObjectInitializer,
object: JsObject,
kind: Kind,
prototype: JsObject,
}
impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
// fn new(realm: &'ctx Realm) -> BuiltInBuilder<'ctx, OrdinaryObject> {
// BuiltInBuilder {
// realm,
// object: BuiltInObjectInitializer::Unique {
// object: Object::default(),
// data: ObjectData::ordinary(),
// },
// kind: OrdinaryObject,
// prototype: realm.intrinsics().constructors().object().prototype(),
// }
// }
fn with_intrinsic<I: IntrinsicObject>(
realm: &'ctx Realm,
) -> BuiltInBuilder<'ctx, OrdinaryObject> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
object: I::get(realm.intrinsics()),
kind: OrdinaryObject,
prototype: realm.intrinsics().constructors().object().prototype(),
}
@ -836,12 +748,6 @@ impl BuiltInConstructorWithPrototype<'_> {
}
fn build(mut self) {
let function = ObjectKind::NativeFunction {
function: NativeFunction::from_fn_ptr(self.function),
constructor: Some(function::ConstructorKind::Base),
realm: self.realm.clone(),
};
let length = self.length;
let name = self.name.clone();
let prototype = self.prototype.clone();
@ -871,7 +777,12 @@ impl BuiltInConstructorWithPrototype<'_> {
}
let mut object = self.object.borrow_mut();
*object.kind_mut() = function;
let function = object
.as_native_function_mut()
.expect("Builtin must be a function object");
function.f = NativeFunction::from_fn_ptr(self.function);
function.constructor = Some(ConstructorKind::Base);
function.realm = Some(self.realm.clone());
object
.properties_mut()
.shape
@ -886,19 +797,18 @@ impl BuiltInConstructorWithPrototype<'_> {
}
fn build_without_prototype(mut self) {
let function = ObjectKind::NativeFunction {
function: NativeFunction::from_fn_ptr(self.function),
constructor: Some(function::ConstructorKind::Base),
realm: self.realm.clone(),
};
let length = self.length;
let name = self.name.clone();
self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE);
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
let mut object = self.object.borrow_mut();
*object.kind_mut() = function;
let function = object
.as_native_function_mut()
.expect("Builtin must be a function object");
function.f = NativeFunction::from_fn_ptr(self.function);
function.constructor = Some(ConstructorKind::Base);
function.realm = Some(self.realm.clone());
object
.properties_mut()
.shape
@ -940,11 +850,11 @@ impl BuiltInCallable<'_> {
fn build(self) -> JsFunction {
let object = self.realm.intrinsics().templates().function().create(
ObjectData::native_function(
NativeFunction::from_fn_ptr(self.function),
false,
self.realm.clone(),
),
ObjectData::native_function(NativeFunctionObject {
f: NativeFunction::from_fn_ptr(self.function),
constructor: None,
realm: Some(self.realm.clone()),
}),
vec![JsValue::new(self.length), JsValue::new(self.name)],
);
@ -968,7 +878,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(I::get(realm.intrinsics())),
object: I::get(realm.intrinsics()),
kind: Callable {
function,
name: js_string!(""),
@ -987,7 +897,7 @@ impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> {
) -> BuiltInBuilder<'ctx, Callable<OrdinaryFunction>> {
BuiltInBuilder {
realm,
object: BuiltInObjectInitializer::Shared(object),
object,
kind: Callable {
function,
name: js_string!(""),
@ -1025,12 +935,7 @@ impl<'ctx> BuiltInBuilder<'ctx, Callable<Constructor>> {
impl<T> BuiltInBuilder<'_, T> {
/// Adds a new static method to the builtin object.
fn static_method<B>(
mut self,
function: NativeFunctionPointer,
binding: B,
length: usize,
) -> Self
fn static_method<B>(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self
where
B: Into<FunctionBinding>,
{
@ -1052,7 +957,7 @@ impl<T> BuiltInBuilder<'_, T> {
}
/// Adds a new static data property to the builtin object.
fn static_property<K, V>(mut self, key: K, value: V, attribute: Attribute) -> Self
fn static_property<K, V>(self, key: K, value: V, attribute: Attribute) -> Self
where
K: Into<PropertyKey>,
V: Into<JsValue>,
@ -1096,22 +1001,22 @@ impl<FnTyp> BuiltInBuilder<'_, Callable<FnTyp>> {
impl BuiltInBuilder<'_, OrdinaryObject> {
/// Build the builtin object.
fn build(mut self) -> JsObject {
self.kind.apply_to(&mut self.object);
fn build(self) -> JsObject {
self.kind.apply_to(&self.object);
self.object.set_prototype(self.prototype);
self.object.set_prototype(Some(self.prototype));
self.object.into_shared()
self.object
}
}
impl<FnTyp: ApplyToObject + IsConstructor> BuiltInBuilder<'_, Callable<FnTyp>> {
/// Build the builtin callable.
fn build(mut self) -> JsFunction {
self.kind.apply_to(&mut self.object);
fn build(self) -> JsFunction {
self.kind.apply_to(&self.object);
self.object.set_prototype(self.prototype);
self.object.set_prototype(Some(self.prototype));
JsFunction::from_object_unchecked(self.object.into_shared())
JsFunction::from_object_unchecked(self.object)
}
}

150
boa_engine/src/builtins/regexp/mod.rs

@ -15,10 +15,9 @@ use crate::{
error::JsNativeError,
js_string,
object::{
internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, ObjectKind,
CONSTRUCTOR,
internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, CONSTRUCTOR,
},
property::{Attribute, PropertyDescriptorBuilder},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16, CodePoint},
symbol::JsSymbol,
@ -252,10 +251,11 @@ impl BuiltInConstructor for RegExp {
};
// 7. Let O be ? RegExpAlloc(newTarget).
let o = Self::alloc(new_target, context)?;
let proto =
get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
// 8.Return ? RegExpInitialize(O, P, F).
Self::initialize(o, &p, &f, context)
Self::initialize(Some(proto), &p, &f, context)
}
}
@ -294,50 +294,16 @@ impl RegExp {
Ok(None)
}
/// `22.2.3.2.1 RegExpAlloc ( newTarget )`
/// Compiles a `RegExp` from the provided pattern and flags.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpalloc
pub(crate) fn alloc(new_target: &JsValue, context: &mut Context<'_>) -> JsResult<JsObject> {
// 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[RegExpMatcher]], [[OriginalSource]], [[OriginalFlags]] »).
let proto =
get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
let obj = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
ObjectData::ordinary(),
);
// 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
obj.define_property_or_throw(
utf16!("lastIndex"),
PropertyDescriptorBuilder::new()
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
context,
)
.expect("this cannot fail per spec");
// 3. Return obj.
Ok(obj)
}
/// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// Equivalent to the beginning of [`RegExpInitialize ( obj, pattern, flags )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
pub(crate) fn initialize(
obj: JsObject,
fn compile_native_regexp(
pattern: &JsValue,
flags: &JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<RegExp> {
// 1. If pattern is undefined, let P be the empty String.
// 2. Else, let P be ? ToString(pattern).
let p = if pattern.is_undefined() {
@ -362,43 +328,66 @@ impl RegExp {
Ok(result) => result,
};
// 10. If u is true, then
// a. Let patternText be StringToCodePoints(P).
// 11. Else,
// a. Let patternText be the result of interpreting each of P's 16-bit elements as a Unicode BMP code point. UTF-16 decoding is not applied to the elements.
// 12. Let parseResult be ParsePattern(patternText, u).
// 13. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
// 14. Assert: parseResult is a Pattern Parse Node.
// 15. Set obj.[[OriginalSource]] to P.
// 16. Set obj.[[OriginalFlags]] to F.
// 17. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
// 18. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[CapturingGroupsCount]]: capturingGroupsCount }.
// 19. Set obj.[[RegExpRecord]] to rer.
// 20. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
// 13. Let parseResult be ParsePattern(patternText, u, v).
// 14. If parseResult is a non-empty List of SyntaxError objects, throw a SyntaxError exception.
let matcher =
match Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags)) {
Err(error) => {
return Err(JsNativeError::syntax()
Regex::from_unicode(p.code_points().map(CodePoint::as_u32), Flags::from(flags))
.map_err(|error| {
JsNativeError::syntax()
.with_message(format!("failed to create matcher: {}", error.text))
.into());
}
Ok(val) => val,
};
})?;
let regexp = Self {
// 15. Assert: parseResult is a Pattern Parse Node.
// 16. Set obj.[[OriginalSource]] to P.
// 17. Set obj.[[OriginalFlags]] to F.
// 18. Let capturingGroupsCount be CountLeftCapturingParensWithin(parseResult).
// 19. Let rer be the RegExp Record { [[IgnoreCase]]: i, [[Multiline]]: m, [[DotAll]]: s, [[Unicode]]: u, [[UnicodeSets]]: v, [[CapturingGroupsCount]]: capturingGroupsCount }.
// 20. Set obj.[[RegExpRecord]] to rer.
// 21. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
Ok(RegExp {
matcher,
flags,
original_source: p,
original_flags: f,
};
// Safe to directly initialize since previous assertions ensure `obj` is a `Regexp` object.
*obj.borrow_mut().kind_mut() = ObjectKind::RegExp(Box::new(regexp));
})
}
// 16. Perform ? Set(obj, "lastIndex", +0𝔽, true).
obj.set(utf16!("lastIndex"), 0, true, context)?;
/// `RegExpInitialize ( obj, pattern, flags )`
///
/// If prototype is `None`, initializes the prototype to `%RegExp%.prototype`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
pub(crate) fn initialize(
prototype: Option<JsObject>,
pattern: &JsValue,
flags: &JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// Has the steps of `RegExpInitialize`.
let regexp = Self::compile_native_regexp(pattern, flags, context)?;
let data = ObjectData::regexp(regexp);
// 22. Perform ? Set(obj, "lastIndex", +0𝔽, true).
let obj = if let Some(prototype) = prototype {
let mut template = context
.intrinsics()
.templates()
.regexp_without_proto()
.clone();
template.set_prototype(prototype);
template.create(data, vec![0.into()])
} else {
context
.intrinsics()
.templates()
.regexp()
.create(data, vec![0.into()])
};
// 16. Return obj.
// 23. Return obj.
Ok(obj.into())
}
@ -410,10 +399,8 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexpcreate
pub(crate) fn create(p: &JsValue, f: &JsValue, context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let obj be ? RegExpAlloc(%RegExp%).
let obj = Self::alloc(&context.global_object().get(Self::NAME, context)?, context)?;
// 2. Return ? RegExpInitialize(obj, P, F).
Self::initialize(obj, p, f, context)
Self::initialize(None, p, f, context)
}
/// `get RegExp [ @@species ]`
@ -1859,6 +1846,7 @@ impl RegExp {
fn compile(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]).
let this = this
.as_object()
.filter(|o| o.borrow().is_regexp())
@ -1893,8 +1881,20 @@ impl RegExp {
// b. Let F be flags.
(pattern.clone(), flags.clone())
};
let regexp = Self::compile_native_regexp(&pattern, &flags, context)?;
// 5. Return ? RegExpInitialize(O, P, F).
Self::initialize(this, &pattern, &flags, context)
{
let mut obj = this.borrow_mut();
*obj.as_regexp_mut()
.expect("already checked that the object was a RegExp") = regexp;
}
this.set(utf16!("lastIndex"), 0, true, context)?;
Ok(this.into())
}
}

30
boa_engine/src/context/intrinsics.rs

@ -1324,6 +1324,9 @@ pub(crate) struct ObjectTemplates {
bigint: ObjectTemplate,
boolean: ObjectTemplate,
regexp: ObjectTemplate,
regexp_without_proto: ObjectTemplate,
unmapped_arguments: ObjectTemplate,
mapped_arguments: ObjectTemplate,
@ -1369,6 +1372,12 @@ impl ObjectTemplates {
);
string.set_prototype(constructors.string().prototype());
let mut regexp_without_prototype = ObjectTemplate::new(root_shape);
regexp_without_prototype.property(js_string!("lastIndex").into(), Attribute::WRITABLE);
let mut regexp = regexp_without_prototype.clone();
regexp.set_prototype(constructors.regexp().prototype());
let name_property_key: PropertyKey = js_string!("name").into();
let mut function = ObjectTemplate::new(root_shape);
function.property(
@ -1474,6 +1483,8 @@ impl ObjectTemplates {
symbol,
bigint,
boolean,
regexp,
regexp_without_proto: regexp_without_prototype,
unmapped_arguments,
mapped_arguments,
function_with_prototype,
@ -1564,6 +1575,25 @@ impl ObjectTemplates {
&self.boolean
}
/// Cached regexp object template.
///
/// Transitions:
///
/// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
pub(crate) const fn regexp(&self) -> &ObjectTemplate {
&self.regexp
}
/// Cached regexp object template without `__proto__` template.
///
/// Transitions:
///
/// 1. `"lastIndex"`: (`WRITABLE` , `PERMANENT`,`NON_ENUMERABLE`)
/// 2. `__proto__`: `RegExp.prototype`
pub(crate) const fn regexp_without_proto(&self) -> &ObjectTemplate {
&self.regexp_without_proto
}
/// Cached unmapped arguments object template.
///
/// Transitions:

4
boa_engine/src/context/mod.rs

@ -75,7 +75,9 @@ use self::intrinsics::StandardConstructor;
/// let arg = ObjectInitializer::new(&mut context)
/// .property(js_string!("x"), 12, Attribute::READONLY)
/// .build();
/// context.register_global_property(js_string!("arg"), arg, Attribute::all());
/// context
/// .register_global_property(js_string!("arg"), arg, Attribute::all())
/// .expect("property shouldn't exist");
///
/// let value = context.eval(Source::from_bytes("test(arg)")).unwrap();
///

26
boa_engine/src/module/mod.rs

@ -47,7 +47,7 @@ use crate::{
builtins::promise::{PromiseCapability, PromiseState},
environments::DeclarativeEnvironment,
js_string,
object::{FunctionObjectBuilder, JsObject, JsPromise, ObjectData},
object::{JsObject, JsPromise, ObjectData},
realm::Realm,
Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction,
};
@ -430,7 +430,7 @@ impl Module {
ModuleKind::Synthetic(synth) => {
// a. Let promise be ! module.Evaluate().
let promise: JsPromise = synth.evaluate(context);
let state = promise.state()?;
let state = promise.state();
match state {
PromiseState::Pending => {
unreachable!("b. Assert: promise.[[PromiseState]] is not pending.")
@ -467,59 +467,53 @@ impl Module {
///
/// loader.insert(Path::new("main.mjs").to_path_buf(), module.clone());
///
/// let promise = module.load_link_evaluate(context).unwrap();
/// let promise = module.load_link_evaluate(context);
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state().unwrap(),
/// promise.state(),
/// PromiseState::Fulfilled(JsValue::undefined())
/// );
/// ```
#[allow(dropping_copy_types)]
#[inline]
pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsResult<JsPromise> {
pub fn load_link_evaluate(&self, context: &mut Context<'_>) -> JsPromise {
let main_timer = Profiler::global().start_event("Module evaluation", "Main");
let promise = self
.load(context)
.then(
Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
module.link(context)?;
Ok(JsValue::undefined())
},
self.clone(),
),
)
.build(),
.to_js_function(context.realm()),
),
None,
context,
)?
)
.then(
Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| Ok(module.evaluate(context).into()),
self.clone(),
),
)
.build(),
.to_js_function(context.realm()),
),
None,
context,
)?;
);
// The main_timer needs to be dropped before the Profiler is.
drop(main_timer);
Profiler::global().drop();
Ok(promise)
promise
}
/// Abstract operation [`GetModuleNamespace ( module )`][spec].

1
boa_engine/src/module/synthetic.rs

@ -198,7 +198,6 @@ impl SyntheticModule {
pub(super) fn load(context: &mut Context<'_>) -> JsPromise {
// 1. Return ! PromiseResolve(%Promise%, undefined).
JsPromise::resolve(JsValue::undefined(), context)
.expect("creating a promise from the %Promise% constructor must not fail")
}
/// Concrete method [`GetExportedNames ( [ exportStarSet ] )`][spec].

34
boa_engine/src/native_function.rs

@ -5,7 +5,12 @@
use boa_gc::{custom_trace, Finalize, Gc, Trace};
use crate::{object::JsPromise, Context, JsResult, JsValue};
use crate::{
builtins::function::ConstructorKind,
object::{FunctionObjectBuilder, JsFunction, JsPromise},
realm::Realm,
Context, JsResult, JsValue,
};
/// The required signature for all native built-in function pointers.
///
@ -56,6 +61,25 @@ where
}
}
#[derive(Clone, Debug, Finalize)]
/// The data of an object containing a `NativeFunction`.
pub struct NativeFunctionObject {
/// The rust function.
pub(crate) f: NativeFunction,
/// The kind of the function constructor if it is a constructor.
pub(crate) constructor: Option<ConstructorKind>,
/// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized.
pub(crate) realm: Option<Realm>,
}
// SAFETY: this traces all fields that need to be traced by the GC.
unsafe impl Trace for NativeFunctionObject {
custom_trace!(this, {
mark(&this.f);
mark(&this.realm);
});
}
/// A callable Rust function that can be invoked by the engine.
///
/// `NativeFunction` functions are divided in two:
@ -280,4 +304,12 @@ impl NativeFunction {
Inner::Closure(ref c) => c.call(this, args, context),
}
}
/// Converts this `NativeFunction` into a `JsFunction` without setting its name or length.
///
/// Useful to create functions that will only be used once, such as callbacks.
#[must_use]
pub fn to_js_function(self, realm: &Realm) -> JsFunction {
FunctionObjectBuilder::new(realm, self).build()
}
}

12
boa_engine/src/object/builtins/jsfunction.rs

@ -1,13 +1,15 @@
//! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
use crate::{
builtins::function::ConstructorKind,
native_function::NativeFunctionObject,
object::{
internal_methods::function::{
NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS,
},
JsObject, JsObjectType, Object,
JsObject, JsObjectType, Object, ObjectKind,
},
value::TryFromJs,
Context, JsNativeError, JsResult, JsValue,
Context, JsNativeError, JsResult, JsValue, NativeFunction,
};
use boa_gc::{Finalize, Trace};
use std::ops::Deref;
@ -32,7 +34,11 @@ impl JsFunction {
pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
Self {
inner: JsObject::from_object_and_vtable(
Object::default(),
Object::with_kind(ObjectKind::NativeFunction(NativeFunctionObject {
f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
constructor: constructor.then_some(ConstructorKind::Base),
realm: None,
})),
if constructor {
&NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else {

305
boa_engine/src/object/builtins/jspromise.rs

@ -8,9 +8,8 @@ use crate::{
promise::{PromiseState, ResolvingFunctions},
Promise,
},
context::intrinsics::StandardConstructors,
job::NativeJob,
object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData},
object::{JsObject, JsObjectType, ObjectData},
value::TryFromJs,
Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction,
};
@ -52,38 +51,28 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// Ok(JsValue::undefined())
/// },
/// context,
/// )?;
/// );
///
/// let promise = promise
/// .then(
/// Some(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| {
/// Err(JsError::from_opaque(
/// args.get_or_undefined(0).clone(),
/// )
/// Err(JsError::from_opaque(args.get_or_undefined(0).clone())
/// .into())
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// ),
/// None,
/// context,
/// )?
/// )
/// .catch(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, _| {
/// Ok(args.get_or_undefined(0).clone())
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// context,
/// )?
/// )
/// .finally(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| {
/// context.global_object().clone().set(
/// js_string!("finally"),
@ -92,16 +81,15 @@ use boa_gc::{Finalize, Gc, GcRefCell, Trace};
/// context,
/// )?;
/// Ok(JsValue::undefined())
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("hello world!").into())
/// );
///
@ -160,12 +148,12 @@ impl JsPromise {
/// Ok(JsValue::undefined())
/// },
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("hello world").into())
/// );
/// # Ok(())
@ -173,7 +161,7 @@ impl JsPromise {
/// ```
///
/// [`Promise()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
pub fn new<F>(executor: F, context: &mut Context<'_>) -> JsResult<Self>
pub fn new<F>(executor: F, context: &mut Context<'_>) -> Self
where
F: FnOnce(&ResolvingFunctions, &mut Context<'_>) -> JsResult<JsValue>,
{
@ -188,10 +176,11 @@ impl JsPromise {
let e = e.to_opaque(context);
resolvers
.reject
.call(&JsValue::undefined(), &[e], context)?;
.call(&JsValue::undefined(), &[e], context)
.expect("default `reject` function cannot throw");
}
Ok(Self { inner: promise })
Self { inner: promise }
}
/// Creates a new pending promise and returns it and its associated `ResolvingFunctions`.
@ -214,13 +203,13 @@ impl JsPromise {
///
/// let (promise, resolvers) = JsPromise::new_pending(context);
///
/// assert_eq!(promise.state()?, PromiseState::Pending);
/// assert_eq!(promise.state(), PromiseState::Pending);
///
/// resolvers
/// .reject
/// .call(&JsValue::undefined(), &[5.into()], context)?;
///
/// assert_eq!(promise.state()?, PromiseState::Rejected(5.into()));
/// assert_eq!(promise.state(), PromiseState::Rejected(5.into()));
///
/// # Ok(())
/// # }
@ -262,7 +251,7 @@ impl JsPromise {
/// let promise = JsPromise::from_object(promise)?;
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(JsValue::undefined())
/// );
///
@ -295,7 +284,6 @@ impl JsPromise {
/// # builtins::promise::PromiseState,
/// # Context, JsResult, JsValue
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// async fn f() -> JsResult<JsValue> {
/// Ok(JsValue::null())
/// }
@ -305,9 +293,7 @@ impl JsPromise {
///
/// context.run_jobs();
///
/// assert_eq!(promise.state()?, PromiseState::Fulfilled(JsValue::null()));
/// # Ok(())
/// # }
/// assert_eq!(promise.state(), PromiseState::Fulfilled(JsValue::null()));
/// ```
///
/// [async_fn]: crate::native_function::NativeFunction::from_async_fn
@ -353,29 +339,26 @@ impl JsPromise {
/// # builtins::promise::PromiseState,
/// # Context, js_string
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::resolve(js_string!("resolved!"), context)?;
/// let promise = JsPromise::resolve(js_string!("resolved!"), context);
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("resolved!").into())
/// );
///
/// # Ok(())
/// # }
/// ```
///
/// [`Promise.resolve()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
/// [thenables]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> JsResult<Self> {
pub fn resolve<V: Into<JsValue>>(value: V, context: &mut Context<'_>) -> Self {
Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(),
value.into(),
context,
)
.and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
}
/// Creates a `JsPromise` that is rejected with the reason `error`.
@ -394,32 +377,29 @@ impl JsPromise {
/// # builtins::promise::PromiseState,
/// # Context, js_string, JsError
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::reject(
/// JsError::from_opaque(js_string!("oops!").into()),
/// context,
/// )?;
/// );
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Rejected(js_string!("oops!").into())
/// );
///
/// # Ok(())
/// # }
/// ```
///
/// [`Promise.reject`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
/// [thenable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> JsResult<Self> {
pub fn reject<E: Into<JsError>>(error: E, context: &mut Context<'_>) -> Self {
Promise::promise_reject(
&context.intrinsics().constructors().promise().constructor(),
&error.into(),
context,
)
.and_then(Self::from_object)
.expect("default resolving functions cannot throw and must return a promise")
}
/// Gets the current state of the promise.
@ -433,26 +413,21 @@ impl JsPromise {
/// # builtins::promise::PromiseState,
/// # Context
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::new_pending(context).0;
///
/// assert_eq!(promise.state()?, PromiseState::Pending);
///
/// # Ok(())
/// # }
/// assert_eq!(promise.state(), PromiseState::Pending);
/// ```
#[inline]
pub fn state(&self) -> JsResult<PromiseState> {
// TODO: if we can guarantee that objects cannot change type after creation,
// we can remove this throw.
let promise = self.inner.borrow();
let promise = promise
#[must_use]
pub fn state(&self) -> PromiseState {
self.inner
.borrow()
.as_promise()
.ok_or_else(|| JsNativeError::typ().with_message("object is not a Promise"))?;
Ok(promise.state().clone())
.expect("objects cannot change type after creation")
.state()
.clone()
}
/// Schedules callback functions to run when the promise settles.
@ -489,7 +464,6 @@ impl JsPromise {
/// # object::{builtins::JsPromise, FunctionObjectBuilder},
/// # Context, JsArgs, JsError, JsValue, NativeFunction,
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::new(
@ -502,44 +476,40 @@ impl JsPromise {
/// Ok(JsValue::undefined())
/// },
/// context,
/// )?
/// )
/// .then(
/// Some(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0)
/// .to_string(context)
/// .map(JsValue::from)
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// ),
/// None,
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("255.255").into())
/// );
///
/// # Ok(())
/// # }
/// ```
///
/// [`Promise.prototype.then`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
#[inline]
#[allow(clippy::return_self_not_must_use)] // Could just be used to add handlers on an existing promise
pub fn then(
&self,
on_fulfilled: Option<JsFunction>,
on_rejected: Option<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
let result_promise = Promise::inner_then(self, on_fulfilled, on_rejected, context)?;
Self::from_object(result_promise)
) -> Self {
Promise::inner_then(self, on_fulfilled, on_rejected, context)
.and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
}
/// Schedules a callback to run when the promise is rejected.
@ -559,7 +529,6 @@ impl JsPromise {
/// # object::{builtins::JsPromise, FunctionObjectBuilder},
/// # Context, JsArgs, JsNativeError, JsValue, NativeFunction,
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::new(
@ -574,35 +543,30 @@ impl JsPromise {
/// Ok(JsValue::undefined())
/// },
/// context,
/// )?
/// )
/// .catch(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, args, context| {
/// args.get_or_undefined(0)
/// .to_string(context)
/// .map(JsValue::from)
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("TypeError: thrown").into())
/// );
///
/// # Ok(())
/// # }
/// ```
///
/// [`Promise.prototype.catch`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
/// [then]: JsPromise::then
#[inline]
pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
#[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
pub fn catch(&self, on_rejected: JsFunction, context: &mut Context<'_>) -> Self {
self.then(None, Some(on_rejected), context)
}
@ -633,7 +597,7 @@ impl JsPromise {
/// js_string!("finally"),
/// false,
/// Attribute::all(),
/// );
/// )?;
///
/// let promise = JsPromise::new(
/// |resolvers, context| {
@ -647,10 +611,8 @@ impl JsPromise {
/// Ok(JsValue::undefined())
/// },
/// context,
/// )?
/// )
/// .finally(
/// FunctionObjectBuilder::new(
/// context.realm(),
/// NativeFunction::from_fn_ptr(|_, _, context| {
/// context.global_object().clone().set(
/// js_string!("finally"),
@ -659,11 +621,10 @@ impl JsPromise {
/// context,
/// )?;
/// Ok(JsValue::undefined())
/// }),
/// )
/// .build(),
/// })
/// .to_js_function(context.realm()),
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
@ -682,10 +643,16 @@ impl JsPromise {
/// [`Promise.prototype.finally()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
/// [then]: JsPromise::then
#[inline]
pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> JsResult<Self> {
let c = self.species_constructor(StandardConstructors::promise, context)?;
let (then, catch) = Promise::then_catch_finally_closures(c, on_finally, context);
self.then(Some(then), Some(catch), context)
#[allow(clippy::return_self_not_must_use)] // Could just be used to add a handler on an existing promise
pub fn finally(&self, on_finally: JsFunction, context: &mut Context<'_>) -> Self {
let (then, catch) = Promise::then_catch_finally_closures(
context.intrinsics().constructors().promise().constructor(),
on_finally,
context,
);
Promise::inner_then(self, Some(then), Some(catch), context)
.and_then(Self::from_object)
.expect("`inner_then` cannot fail for native `JsPromise`")
}
/// Waits for a list of promises to settle with fulfilled values, rejecting the aggregate promise
@ -707,26 +674,26 @@ impl JsPromise {
///
/// let promise1 = JsPromise::all(
/// [
/// JsPromise::resolve(0, context)?,
/// JsPromise::resolve(2, context)?,
/// JsPromise::resolve(4, context)?,
/// JsPromise::resolve(0, context),
/// JsPromise::resolve(2, context),
/// JsPromise::resolve(4, context),
/// ],
/// context,
/// )?;
/// );
///
/// let promise2 = JsPromise::all(
/// [
/// JsPromise::resolve(1, context)?,
/// JsPromise::reject(JsNativeError::typ(), context)?,
/// JsPromise::resolve(3, context)?,
/// JsPromise::resolve(1, context),
/// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(3, context),
/// ],
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// let array = promise1
/// .state()?
/// .state()
/// .as_fulfilled()
/// .and_then(JsValue::as_object)
/// .unwrap()
@ -736,7 +703,7 @@ impl JsPromise {
/// assert_eq!(array.at(1, context)?, 2.into());
/// assert_eq!(array.at(2, context)?, 4.into());
///
/// let error = promise2.state()?.as_rejected().unwrap().clone();
/// let error = promise2.state().as_rejected().unwrap().clone();
/// assert_eq!(error.to_string(context)?, js_string!("TypeError"));
///
/// # Ok(())
@ -744,7 +711,7 @@ impl JsPromise {
/// ```
///
/// [`Promise.all`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
pub fn all<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
pub fn all<I>(promises: I, context: &mut Context<'_>) -> Self
where
I: IntoIterator<Item = Self>,
{
@ -757,12 +724,15 @@ impl JsPromise {
.constructor()
.into();
let value = Promise::all(c, &[promises.into()], context)?;
let value = value
let value = Promise::all(c, &[promises.into()], context)
.expect("Promise.all cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("Promise.all always returns an object on success");
.expect("`Promise.all` always returns an object on success");
Self::from_object(value.clone())
Self::from_object(object.clone())
.expect("`Promise::all` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
/// Waits for a list of promises to settle, fulfilling with an array of the outcomes of every
@ -784,17 +754,17 @@ impl JsPromise {
///
/// let promise = JsPromise::all_settled(
/// [
/// JsPromise::resolve(1, context)?,
/// JsPromise::reject(JsNativeError::typ(), context)?,
/// JsPromise::resolve(3, context)?,
/// JsPromise::resolve(1, context),
/// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(3, context),
/// ],
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// let array = promise
/// .state()?
/// .state()
/// .as_fulfilled()
/// .and_then(JsValue::as_object)
/// .unwrap()
@ -830,7 +800,7 @@ impl JsPromise {
/// ```
///
/// [`Promise.allSettled`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
pub fn all_settled<I>(promises: I, context: &mut Context<'_>) -> Self
where
I: IntoIterator<Item = Self>,
{
@ -843,12 +813,15 @@ impl JsPromise {
.constructor()
.into();
let value = Promise::all_settled(c, &[promises.into()], context)?;
let value = value
let value = Promise::all_settled(c, &[promises.into()], context)
.expect("`Promise.all_settled` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("Promise.allSettled always returns an object on success");
.expect("`Promise.all_settled` always returns an object on success");
Self::from_object(value.clone())
Self::from_object(object.clone())
.expect("`Promise::all_settled` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
/// Returns the first promise that fulfills from a list of promises.
@ -869,32 +842,28 @@ impl JsPromise {
/// # object::builtins::JsPromise,
/// # Context, JsNativeError,
/// # };
/// # fn main() -> Result<(), Box<dyn Error>> {
/// let context = &mut Context::default();
///
/// let promise = JsPromise::any(
/// [
/// JsPromise::reject(JsNativeError::syntax(), context)?,
/// JsPromise::reject(JsNativeError::typ(), context)?,
/// JsPromise::resolve(js_string!("fulfilled"), context)?,
/// JsPromise::reject(JsNativeError::range(), context)?,
/// JsPromise::reject(JsNativeError::syntax(), context),
/// JsPromise::reject(JsNativeError::typ(), context),
/// JsPromise::resolve(js_string!("fulfilled"), context),
/// JsPromise::reject(JsNativeError::range(), context),
/// ],
/// context,
/// )?;
/// );
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Fulfilled(js_string!("fulfilled").into())
/// );
///
/// # Ok(())
/// # }
/// ```
///
/// [`Promise.any`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
pub fn any<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
pub fn any<I>(promises: I, context: &mut Context<'_>) -> Self
where
I: IntoIterator<Item = Self>,
{
@ -907,12 +876,15 @@ impl JsPromise {
.constructor()
.into();
let value = Promise::any(c, &[promises.into()], context)?;
let value = value
let value = Promise::any(c, &[promises.into()], context)
.expect("`Promise.any` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("Promise.any always returns an object on success");
.expect("`Promise.any` always returns an object on success");
Self::from_object(value.clone())
Self::from_object(object.clone())
.expect("`Promise::any` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
/// Returns the first promise that settles from a list of promises.
@ -939,22 +911,24 @@ impl JsPromise {
/// let (b, resolvers_b) = JsPromise::new_pending(context);
/// let (c, resolvers_c) = JsPromise::new_pending(context);
///
/// let promise = JsPromise::race([a, b, c], context)?;
/// let promise = JsPromise::race([a, b, c], context);
///
/// resolvers_b.reject.call(&JsValue::undefined(), &[], context);
/// resolvers_b
/// .reject
/// .call(&JsValue::undefined(), &[], context)?;
/// resolvers_a
/// .resolve
/// .call(&JsValue::undefined(), &[5.into()], context);
/// .call(&JsValue::undefined(), &[5.into()], context)?;
/// resolvers_c.reject.call(
/// &JsValue::undefined(),
/// &[js_string!("c error").into()],
/// context,
/// );
/// )?;
///
/// context.run_jobs();
///
/// assert_eq!(
/// promise.state()?,
/// promise.state(),
/// PromiseState::Rejected(JsValue::undefined())
/// );
///
@ -963,7 +937,7 @@ impl JsPromise {
/// ```
///
/// [`Promise.race`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
pub fn race<I>(promises: I, context: &mut Context<'_>) -> JsResult<Self>
pub fn race<I>(promises: I, context: &mut Context<'_>) -> Self
where
I: IntoIterator<Item = Self>,
{
@ -976,12 +950,15 @@ impl JsPromise {
.constructor()
.into();
let value = Promise::race(c, &[promises.into()], context)?;
let value = value
let value = Promise::race(c, &[promises.into()], context)
.expect("`Promise.race` cannot fail with the default `%Promise%` constructor");
let object = value
.as_object()
.expect("Promise.race always returns an object on success");
.expect("`Promise.race` always returns an object on success");
Self::from_object(value.clone())
Self::from_object(object.clone())
.expect("`Promise::race` with the default `%Promise%` constructor always returns a native `JsPromise`")
}
/// Creates a `JsFuture` from this `JsPromise`.
@ -1003,7 +980,7 @@ impl JsPromise {
/// let context = &mut Context::default();
///
/// let (promise, resolvers) = JsPromise::new_pending(context);
/// let promise_future = promise.into_js_future(context)?;
/// let promise_future = promise.into_js_future(context);
///
/// let future1 = async move { promise_future.await };
///
@ -1023,7 +1000,7 @@ impl JsPromise {
/// # Ok(())
/// # }
/// ```
pub fn into_js_future(self, context: &mut Context<'_>) -> JsResult<JsFuture> {
pub fn into_js_future(self, context: &mut Context<'_>) -> JsFuture {
// Mostly based from:
// https://docs.rs/wasm-bindgen-futures/0.4.37/src/wasm_bindgen_futures/lib.rs.html#109-168
@ -1057,24 +1034,18 @@ impl JsPromise {
let resolve = {
let state = state.clone();
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
finish(state, Ok(args.get_or_undefined(0).clone()));
Ok(JsValue::undefined())
},
state,
),
)
.build()
};
let reject = {
let state = state.clone();
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
move |_, args, state, _| {
let err = JsError::from_opaque(args.get_or_undefined(0).clone());
@ -1082,14 +1053,16 @@ impl JsPromise {
Ok(JsValue::undefined())
},
state,
),
)
.build()
};
drop(self.then(Some(resolve), Some(reject), context)?);
drop(self.then(
Some(resolve.to_js_function(context.realm())),
Some(reject.to_js_function(context.realm())),
context,
));
Ok(JsFuture { inner: state })
JsFuture { inner: state }
}
}

10
boa_engine/src/object/builtins/jsregexp.rs

@ -60,15 +60,7 @@ impl JsRegExp {
where
S: Into<JsValue>,
{
let constructor = &context
.intrinsics()
.constructors()
.regexp()
.constructor()
.into();
let obj = RegExp::alloc(constructor, context)?;
let regexp = RegExp::initialize(obj, &pattern.into(), &flags.into(), context)?
let regexp = RegExp::initialize(None, &pattern.into(), &flags.into(), context)?
.as_object()
.expect("RegExp::initialize must return a RegExp object")
.clone();

41
boa_engine/src/object/internal_methods/function.rs

@ -2,9 +2,10 @@ use crate::{
builtins::function::{arguments::Arguments, FunctionKind, ThisMode},
context::intrinsics::StandardConstructors,
environments::{FunctionSlots, ThisBindingStatus},
native_function::NativeFunctionObject,
object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject, ObjectData, ObjectKind,
JsObject, ObjectData,
},
vm::{CallFrame, CallFrameFlags},
Context, JsNativeError, JsResult, JsValue,
@ -336,21 +337,18 @@ pub(crate) fn native_function_call(
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let object = obj.borrow();
let ObjectKind::NativeFunction {
function,
let NativeFunctionObject {
f: function,
constructor,
realm,
} = object.kind()
else {
unreachable!("the object should be a native function object");
};
} = obj
.borrow()
.as_native_function()
.cloned()
.expect("the object should be a native function object");
let mut realm = realm.clone();
let function = function.clone();
let constructor = *constructor;
drop(object);
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
@ -385,21 +383,18 @@ fn native_function_construct(
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let object = obj.borrow();
let ObjectKind::NativeFunction {
function,
let NativeFunctionObject {
f: function,
constructor,
realm,
} = object.kind()
else {
unreachable!("the object should be a native function object");
};
} = obj
.borrow()
.as_native_function()
.cloned()
.expect("the object should be a native function object");
let mut realm = realm.clone();
let function = function.clone();
let constructor = *constructor;
drop(object);
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);

102
boa_engine/src/object/mod.rs

@ -67,7 +67,7 @@ use crate::{
context::intrinsics::StandardConstructor,
js_string,
module::ModuleNamespace,
native_function::NativeFunction,
native_function::{NativeFunction, NativeFunctionObject},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
string::utf16,
@ -356,16 +356,7 @@ pub enum ObjectKind {
GeneratorFunction(OrdinaryFunction),
/// A native rust function.
NativeFunction {
/// The rust function.
function: NativeFunction,
/// The kind of the function constructor if it is a constructor.
constructor: Option<ConstructorKind>,
/// The [`Realm`] in which the function is defined.
realm: Realm,
},
NativeFunction(NativeFunctionObject),
/// The `Set` object kind.
Set(OrderedSet),
@ -510,10 +501,7 @@ unsafe impl Trace for ObjectKind {
Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f),
Self::BoundFunction(f) => mark(f),
Self::Generator(g) => mark(g),
Self::NativeFunction { function, constructor: _, realm } => {
mark(function);
mark(realm);
}
Self::NativeFunction(f) => mark(f),
Self::Set(s) => mark(s),
Self::SetIterator(i) => mark(i),
Self::StringIterator(i) => mark(i),
@ -659,9 +647,9 @@ impl ObjectData {
/// Create the `RegExp` object data
#[must_use]
pub fn reg_exp(reg_exp: Box<RegExp>) -> Self {
pub fn regexp(reg_exp: RegExp) -> Self {
Self {
kind: ObjectKind::RegExp(reg_exp),
kind: ObjectKind::RegExp(Box::new(reg_exp)),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
@ -737,8 +725,8 @@ impl ObjectData {
/// Create the native function object data
#[must_use]
pub fn native_function(function: NativeFunction, constructor: bool, realm: Realm) -> Self {
let internal_methods = if constructor {
pub fn native_function(function: NativeFunctionObject) -> Self {
let internal_methods = if function.constructor.is_some() {
&NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else {
&NATIVE_FUNCTION_INTERNAL_METHODS
@ -746,11 +734,7 @@ impl ObjectData {
Self {
internal_methods,
kind: ObjectKind::NativeFunction {
function,
constructor: constructor.then_some(ConstructorKind::Base),
realm,
},
kind: ObjectKind::NativeFunction(function),
}
}
@ -1210,9 +1194,12 @@ impl Debug for ObjectKind {
}
impl Object {
/// Returns a mutable reference to the kind of an object.
pub(crate) fn kind_mut(&mut self) -> &mut ObjectKind {
&mut self.kind
/// Creates a new `Object` with the specified `ObjectKind`.
pub(crate) fn with_kind(kind: ObjectKind) -> Self {
Self {
kind,
..Self::default()
}
}
/// Returns the shape of the object.
@ -1726,6 +1713,16 @@ impl Object {
}
}
/// Gets a mutable reference to the regexp data if the object is a regexp.
#[inline]
#[must_use]
pub fn as_regexp_mut(&mut self) -> Option<&mut RegExp> {
match &mut self.kind {
ObjectKind::RegExp(regexp) => Some(regexp),
_ => None,
}
}
/// Checks if it a `TypedArray` object.
#[inline]
#[must_use]
@ -1958,6 +1955,30 @@ impl Object {
}
}
/// Returns `true` if it holds a native Rust function.
#[inline]
#[must_use]
pub const fn is_native_function(&self) -> bool {
matches!(self.kind, ObjectKind::NativeFunction { .. })
}
/// Returns this `NativeFunctionObject` if this object contains a `NativeFunctionObject`.
pub(crate) fn as_native_function(&self) -> Option<&NativeFunctionObject> {
match &self.kind {
ObjectKind::NativeFunction(f) => Some(f),
_ => None,
}
}
/// Returns a mutable reference to this `NativeFunctionObject` if this object contains a
/// `NativeFunctionObject`.
pub(crate) fn as_native_function_mut(&mut self) -> Option<&mut NativeFunctionObject> {
match &mut self.kind {
ObjectKind::NativeFunction(f) => Some(f),
_ => None,
}
}
/// Gets the prototype instance of this object.
#[inline]
#[must_use]
@ -1990,13 +2011,6 @@ impl Object {
matches!(self.kind, ObjectKind::NativeObject(_))
}
/// Returns `true` if it holds a native Rust function.
#[inline]
#[must_use]
pub const fn is_native_function(&self) -> bool {
matches!(self.kind, ObjectKind::NativeFunction { .. })
}
/// Gets the native object data if the object is a `NativeObject`.
#[inline]
#[must_use]
@ -2602,11 +2616,11 @@ impl<'realm> FunctionObjectBuilder<'realm> {
#[must_use]
pub fn build(self) -> JsFunction {
let object = self.realm.intrinsics().templates().function().create(
ObjectData::native_function(
self.function,
self.constructor.is_some(),
self.realm.clone(),
),
ObjectData::native_function(NativeFunctionObject {
f: self.function,
constructor: self.constructor,
realm: Some(self.realm.clone()),
}),
vec![self.length.into(), self.name.into()],
);
@ -3044,11 +3058,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
let mut constructor = self.constructor_object;
constructor.insert(utf16!("length"), length);
constructor.insert(utf16!("name"), name);
let data = ObjectData::native_function(
self.function,
self.kind.is_some(),
self.context.realm().clone(),
);
let data = ObjectData::native_function(NativeFunctionObject {
f: self.function,
constructor: self.kind,
realm: Some(self.context.realm().clone()),
});
constructor.kind = data.kind;

4
boa_engine/src/object/operations.rs

@ -720,8 +720,8 @@ impl JsObject {
return Ok(fun.realm().clone());
}
if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() {
return Ok(realm.clone());
if let ObjectKind::NativeFunction(f) = constructor.kind() {
return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone()));
}
if let Some(bound) = constructor.as_bound_function() {

17
boa_examples/src/bin/modules.rs

@ -4,7 +4,6 @@ use boa_engine::{
builtins::promise::PromiseState,
js_string,
module::{ModuleLoader, SimpleModuleLoader},
object::FunctionObjectBuilder,
Context, JsError, JsNativeError, JsValue, Module, NativeFunction,
};
use boa_parser::Source;
@ -58,8 +57,6 @@ fn main() -> Result<(), Box<dyn Error>> {
.load(context)
.then(
Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
// After loading, link all modules by resolving the imports
@ -70,17 +67,14 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(JsValue::undefined())
},
module.clone(),
),
)
.build(),
.to_js_function(context.realm()),
),
None,
context,
)?
)
.then(
Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
// Finally, evaluate the root module.
// This returns a `JsPromise` since a module could have
@ -88,19 +82,18 @@ fn main() -> Result<(), Box<dyn Error>> {
// job queue.
|_, _, module, context| Ok(module.evaluate(context).into()),
module.clone(),
),
)
.build(),
.to_js_function(context.realm()),
),
None,
context,
)?;
);
// Very important to push forward the job queue after queueing promises.
context.run_jobs();
// Checking if the final promise didn't return an error.
match promise_result.state()? {
match promise_result.state() {
PromiseState::Pending => return Err("module didn't execute!".into()),
PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined())

4
boa_examples/src/bin/synthetic.rs

@ -59,13 +59,13 @@ fn main() -> Result<(), Box<dyn Error>> {
// This uses the utility function to load, link and evaluate a module without having to deal
// with callbacks. For an example demonstrating the whole lifecycle of a module, see
// `modules.rs`
let promise_result = module.load_link_evaluate(context)?;
let promise_result = module.load_link_evaluate(context);
// Very important to push forward the job queue after queueing promises.
context.run_jobs();
// Checking if the final promise didn't return an error.
match promise_result.state()? {
match promise_result.state() {
PromiseState::Pending => return Err("module didn't execute!".into()),
PromiseState::Fulfilled(v) => {
assert_eq!(v, JsValue::undefined())

25
boa_tester/src/exec/mod.rs

@ -250,17 +250,11 @@ impl Test {
module.clone(),
);
let promise = match module.load_link_evaluate(context) {
Ok(promise) => promise,
Err(err) => return (false, format!("Uncaught {err}")),
};
let promise = module.load_link_evaluate(context);
context.run_jobs();
match promise
.state()
.expect("tester can only use builtin promises")
{
match promise.state() {
PromiseState::Pending => {
return (false, "module should have been executed".to_string())
}
@ -364,10 +358,7 @@ impl Test {
context.run_jobs();
match promise
.state()
.expect("tester can only use builtin promises")
{
match promise.state() {
PromiseState::Pending => {
return (false, "module didn't try to load".to_string())
}
@ -428,10 +419,7 @@ impl Test {
context.run_jobs();
match promise
.state()
.expect("tester can only use builtin promises")
{
match promise.state() {
PromiseState::Pending => {
return (false, "module didn't try to load".to_string())
}
@ -449,10 +437,7 @@ impl Test {
context.run_jobs();
match promise
.state()
.expect("tester can only use builtin promises")
{
match promise.state() {
PromiseState::Pending => {
return (false, "module didn't try to evaluate".to_string())
}

Loading…
Cancel
Save