Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disallow changing type of already created objects #3410

Merged
merged 4 commits into from
Oct 25, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Disallow changing type of already created objects
jedel1043 committed Oct 23, 2023
commit 02adff460b8d46147ae8669727a32a2b6e061545
6 changes: 3 additions & 3 deletions boa_ast/src/expression/regexp.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 3 deletions boa_cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 {
15 changes: 4 additions & 11 deletions boa_engine/src/builtins/error/type.rs
Original file line number Diff line number Diff line change
@@ -20,11 +20,11 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute,
realm::Realm,
string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsResult, JsString, JsValue, NativeFunction,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_profiler::Profiler;

@@ -115,16 +115,14 @@ pub(crate) struct ThrowTypeError;

impl IntrinsicObject for ThrowTypeError {
fn init(realm: &Realm) {
fn throw_type_error(_: &JsValue, _: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
let obj = BuiltInBuilder::callable_with_intrinsic::<Self>(realm, |_, _, _| {
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())
.static_property(utf16!("name"), js_string!(), Attribute::empty())
@@ -133,11 +131,6 @@ 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),
constructor: None,
realm: realm.clone(),
}
}

fn get(intrinsics: &Intrinsics) -> JsObject {
199 changes: 52 additions & 147 deletions boa_engine/src/builtins/mod.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
124 changes: 62 additions & 62 deletions boa_engine/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
@@ -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,12 @@ impl RegExp {
Ok(None)
}

/// `22.2.3.2.1 RegExpAlloc ( newTarget )`
///
/// 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]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
pub(crate) fn initialize(
obj: JsObject,
/// Compiles a `RegExp` from the provided pattern and flags.
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() {
@@ -376,27 +338,53 @@ impl RegExp {
// 19. Set obj.[[RegExpRecord]] to rer.
// 20. Set obj.[[RegExpMatcher]] to CompilePattern of parseResult with argument rer.
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 {
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));
/// `22.2.3.2.2 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> {
let regexp = Self::compile_native_regexp(pattern, flags, context)?;
let data = ObjectData::regexp(regexp);

// 16. Perform ? Set(obj, "lastIndex", +0𝔽, true).
obj.set(utf16!("lastIndex"), 0, true, context)?;
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.
Ok(obj.into())
@@ -410,10 +398,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 +1845,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 +1880,21 @@ 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();

// Should just override the already existing `lastIndex` property.
obj.properties_mut().storage[0] = 0.into();

*obj.as_regexp_mut()
.expect("already checked that the object was a RegExp") = regexp;
}

Ok(this.into())
}
}

29 changes: 29 additions & 0 deletions boa_engine/src/context/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -1323,6 +1323,8 @@ pub(crate) struct ObjectTemplates {
symbol: ObjectTemplate,
bigint: ObjectTemplate,
boolean: ObjectTemplate,
regexp: ObjectTemplate,
regexp_without_proto: ObjectTemplate,

unmapped_arguments: ObjectTemplate,
mapped_arguments: ObjectTemplate,
@@ -1369,6 +1371,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 +1482,8 @@ impl ObjectTemplates {
symbol,
bigint,
boolean,
regexp,
regexp_without_proto: regexp_without_prototype,
unmapped_arguments,
mapped_arguments,
function_with_prototype,
@@ -1564,6 +1574,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 changes: 3 additions & 1 deletion boa_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
@@ -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();
///
50 changes: 19 additions & 31 deletions boa_engine/src/module/mod.rs
Original file line number Diff line number Diff line change
@@ -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,47 @@ 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(),
),
Some(NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
module.link(context)?;
Ok(JsValue::undefined())
},
self.clone(),
)),
None,
context,
)?
)
.then(
Some(
FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| Ok(module.evaluate(context).into()),
self.clone(),
),
)
.build(),
),
Some(NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| Ok(module.evaluate(context).into()),
self.clone(),
)),
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 change: 0 additions & 1 deletion boa_engine/src/module/synthetic.rs
Original file line number Diff line number Diff line change
@@ -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].
24 changes: 23 additions & 1 deletion boa_engine/src/native_function.rs
Original file line number Diff line number Diff line change
@@ -5,7 +5,10 @@
use boa_gc::{custom_trace, Finalize, Gc, Trace};

use crate::{object::JsPromise, Context, JsResult, JsValue};
use crate::{
builtins::function::ConstructorKind, object::JsPromise, realm::Realm, Context, JsResult,
JsValue,
};

/// The required signature for all native built-in function pointers.
///
@@ -56,6 +59,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:
12 changes: 9 additions & 3 deletions boa_engine/src/object/builtins/jsfunction.rs
Original file line number Diff line number Diff line change
@@ -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 {
390 changes: 177 additions & 213 deletions boa_engine/src/object/builtins/jspromise.rs

Large diffs are not rendered by default.

10 changes: 1 addition & 9 deletions boa_engine/src/object/builtins/jsregexp.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 23 deletions boa_engine/src/object/internal_methods/function.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 58 additions & 44 deletions boa_engine/src/object/mod.rs
Original file line number Diff line number Diff line change
@@ -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,20 +725,16 @@ 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
};

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 changes: 2 additions & 2 deletions boa_engine/src/object/operations.rs
Original file line number Diff line number Diff line change
@@ -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() {
57 changes: 22 additions & 35 deletions boa_examples/src/bin/modules.rs
Original file line number Diff line number Diff line change
@@ -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;
@@ -57,50 +56,38 @@ fn main() -> Result<(), Box<dyn Error>> {
// which allows async loads and async fetches.
.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
// and exports on the full module graph, initializing module
// environments. This returns a plain `Err` since all modules
// must link at the same time.
module.link(context)?;
Ok(JsValue::undefined())
},
module.clone(),
),
)
.build(),
),
Some(NativeFunction::from_copy_closure_with_captures(
|_, _, module, context| {
// After loading, link all modules by resolving the imports
// and exports on the full module graph, initializing module
// environments. This returns a plain `Err` since all modules
// must link at the same time.
module.link(context)?;
Ok(JsValue::undefined())
},
module.clone(),
)),
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
// top-level await statements, which defers module execution to the
// job queue.
|_, _, module, context| Ok(module.evaluate(context).into()),
module.clone(),
),
)
.build(),
),
Some(NativeFunction::from_copy_closure_with_captures(
// Finally, evaluate the root module.
// This returns a `JsPromise` since a module could have
// top-level await statements, which defers module execution to the
// job queue.
|_, _, module, context| Ok(module.evaluate(context).into()),
module.clone(),
)),
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 changes: 2 additions & 2 deletions boa_examples/src/bin/synthetic.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 20 deletions boa_tester/src/exec/mod.rs
Original file line number Diff line number Diff line change
@@ -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())
}