Browse Source

Refactor `construct` and `PromiseCapability` to preserve `JsObject` invariants (#2136)

This Pull Request changes the signature of `construct` to always return `JsObject`s, and refactors `PromiseCapability` to store only `JsObject`/`JsFunction`s. This preserves the following invariants specified in the spec:

https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget
> The [[Construct]] internal method of an ECMAScript function object ... returns either a normal completion containing an Object or a throw completion ... 

https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promisecapability-records

Table 82: [PromiseCapability Record](https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promisecapability-records) Fields
Field Name | Value | Meaning
-- | -- | --
[[Promise]] | an Object | An object that is usable as a promise.
[[Resolve]] | a function object | The function that is used to resolve the given promise.
[[Reject]] | a function object | The function that is used to reject the given promise.

Co-authored-by: Iban Eguia <razican@protonmail.ch>
pull/2140/head
jedel1043 2 years ago
parent
commit
0ebb3cf439
  1. 29
      boa_engine/src/builtins/array/mod.rs
  2. 20
      boa_engine/src/builtins/array_buffer/mod.rs
  3. 2
      boa_engine/src/builtins/error/type.rs
  4. 17
      boa_engine/src/builtins/function/mod.rs
  5. 6
      boa_engine/src/builtins/generator_function/mod.rs
  6. 122
      boa_engine/src/builtins/promise/mod.rs
  7. 12
      boa_engine/src/builtins/promise/promise_job.rs
  8. 4
      boa_engine/src/builtins/reflect/mod.rs
  9. 12
      boa_engine/src/builtins/regexp/mod.rs
  10. 7
      boa_engine/src/builtins/typed_array/mod.rs
  11. 26
      boa_engine/src/bytecompiler.rs
  12. 2
      boa_engine/src/object/internal_methods/bound_function.rs
  13. 2
      boa_engine/src/object/internal_methods/function.rs
  14. 4
      boa_engine/src/object/internal_methods/mod.rs
  15. 8
      boa_engine/src/object/internal_methods/proxy.rs
  16. 14
      boa_engine/src/object/jsfunction.rs
  17. 19
      boa_engine/src/object/mod.rs
  18. 2
      boa_engine/src/object/operations.rs
  19. 84
      boa_engine/src/vm/code_block.rs
  20. 4
      boa_engine/src/vm/mod.rs

29
boa_engine/src/builtins/array/mod.rs

@ -366,10 +366,7 @@ impl Array {
// 7. If IsConstructor(C) is false, throw a TypeError exception. // 7. If IsConstructor(C) is false, throw a TypeError exception.
if let Some(c) = c.as_constructor() { if let Some(c) = c.as_constructor() {
// 8. Return ? Construct(C, « 𝔽(length) »). // 8. Return ? Construct(C, « 𝔽(length) »).
Ok(c.construct(&[JsValue::new(length)], Some(c), context)? c.construct(&[JsValue::new(length)], Some(c), context)
.as_object()
.expect("constructing an object should always return an object")
.clone())
} else { } else {
context.throw_type_error("Symbol.species must be a constructor") context.throw_type_error("Symbol.species must be a constructor")
} }
@ -418,13 +415,7 @@ impl Array {
// b. Else, // b. Else,
// i. Let A be ? ArrayCreate(0en). // i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor Some(constructor) => constructor.construct(&[], None, context)?,
.construct(&[], None, context)?
.as_object()
.cloned()
.ok_or_else(|| {
context.construct_type_error("Object constructor didn't return an object")
})?,
_ => Self::array_create(0, None, context)?, _ => Self::array_create(0, None, context)?,
}; };
@ -497,13 +488,7 @@ impl Array {
// 10. Else, // 10. Else,
// a. Let A be ? ArrayCreate(len). // a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor Some(constructor) => constructor.construct(&[len.into()], None, context)?,
.construct(&[len.into()], None, context)?
.as_object()
.cloned()
.ok_or_else(|| {
context.construct_type_error("Object constructor didn't return an object")
})?,
_ => Self::array_create(len, None, context)?, _ => Self::array_create(len, None, context)?,
}; };
@ -579,13 +564,7 @@ impl Array {
// 5. Else, // 5. Else,
// a. Let A be ? ArrayCreate(len). // a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor Some(constructor) => constructor.construct(&[len.into()], None, context)?,
.construct(&[len.into()], None, context)?
.as_object()
.cloned()
.ok_or_else(|| {
context.construct_type_error("object constructor didn't return an object")
})?,
_ => Self::array_create(len, None, context)?, _ => Self::array_create(len, None, context)?,
}; };

20
boa_engine/src/builtins/array_buffer/mod.rs

@ -243,13 +243,9 @@ impl ArrayBuffer {
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?;
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
let new_obj = new.as_object().cloned().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned non-object value")
})?;
{ {
let new_obj = new_obj.borrow(); let new_obj = new.borrow();
// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned invalid object") context.construct_type_error("ArrayBuffer constructor returned invalid object")
})?; })?;
@ -264,11 +260,16 @@ impl ArrayBuffer {
} }
} }
// 20. If SameValue(new, O) is true, throw a TypeError exception. // 20. If SameValue(new, O) is true, throw a TypeError exception.
if JsValue::same_value(&new, this) { if this
.as_object()
.map(|obj| JsObject::equals(obj, &new))
.unwrap_or_default()
{
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
} }
let mut new_obj_borrow = new_obj.borrow_mut(); {
let mut new_obj_borrow = new.borrow_mut();
let new_array_buffer = new_obj_borrow let new_array_buffer = new_obj_borrow
.as_array_buffer_mut() .as_array_buffer_mut()
.expect("Already checked that `new_obj` was an `ArrayBuffer`"); .expect("Already checked that `new_obj` was an `ArrayBuffer`");
@ -299,9 +300,10 @@ impl ArrayBuffer {
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len); copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len);
}
// 27. Return new. // 27. Return new.
Ok(new) Ok(new.into())
} }
/// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )` /// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )`

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

@ -103,7 +103,7 @@ pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject {
context.intrinsics().constructors().function().prototype(), context.intrinsics().constructors().function().prototype(),
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function: throw_type_error, function: throw_type_error,
constructor: false, constructor: None,
}), }),
); );

17
boa_engine/src/builtins/function/mod.rs

@ -108,7 +108,7 @@ impl ThisMode {
} }
} }
#[derive(Debug, Trace, Finalize, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone, Copy)]
pub enum ConstructorKind { pub enum ConstructorKind {
Base, Base,
Derived, Derived,
@ -178,12 +178,14 @@ pub enum Function {
Native { Native {
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
function: NativeFunctionSignature, function: NativeFunctionSignature,
constructor: bool, #[unsafe_ignore_trace]
constructor: Option<ConstructorKind>,
}, },
Closure { Closure {
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>, function: Box<dyn ClosureFunctionSignature>,
constructor: bool, #[unsafe_ignore_trace]
constructor: Option<ConstructorKind>,
captures: Captures, captures: Captures,
}, },
Ordinary { Ordinary {
@ -205,6 +207,11 @@ impl fmt::Debug for Function {
impl Function { impl Function {
/// Returns true if the function object is a constructor. /// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
self.constructor().is_some()
}
/// Returns the constructor kind if the function is constructable, or `None` otherwise.
pub fn constructor(&self) -> Option<ConstructorKind> {
match self { match self {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor,
Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor, Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor,
@ -251,7 +258,7 @@ pub(crate) fn make_builtin_fn<N>(
.prototype(), .prototype(),
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function, function,
constructor: false, constructor: None,
}), }),
); );
let attribute = PropertyDescriptor::builder() let attribute = PropertyDescriptor::builder()
@ -417,7 +424,7 @@ impl BuiltInFunctionObject {
prototype, prototype,
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()), function: |_, _, _| Ok(JsValue::undefined()),
constructor: true, constructor: Some(ConstructorKind::Base),
}), }),
); );

6
boa_engine/src/builtins/generator_function/mod.rs

@ -21,6 +21,8 @@ use crate::{
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
use super::function::ConstructorKind;
/// The internal representation on a `Generator` object. /// The internal representation on a `Generator` object.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GeneratorFunction; pub struct GeneratorFunction;
@ -71,7 +73,7 @@ impl BuiltIn for GeneratorFunction {
constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native { constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor, function: Self::constructor,
constructor: true, constructor: Some(ConstructorKind::Base),
}); });
prototype.set_prototype(Some( prototype.set_prototype(Some(
@ -124,7 +126,7 @@ impl GeneratorFunction {
prototype, prototype,
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()), function: |_, _, _| Ok(JsValue::undefined()),
constructor: true, constructor: Some(ConstructorKind::Base),
}), }),
); );

122
boa_engine/src/builtins/promise/mod.rs

@ -13,7 +13,7 @@ use crate::{
job::JobCallback, job::JobCallback,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData, JsFunction, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -39,10 +39,14 @@ macro_rules! if_abrupt_reject_promise {
// 1. If value is an abrupt completion, then // 1. If value is an abrupt completion, then
Err(value) => { Err(value) => {
// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
$context.call(&$capability.reject, &JsValue::undefined(), &[value])?; $context.call(
&$capability.reject.clone().into(),
&JsValue::undefined(),
&[value],
)?;
// b. Return capability.[[Promise]]. // b. Return capability.[[Promise]].
return Ok($capability.promise.clone()); return Ok($capability.promise.clone().into());
} }
// 2. Else if value is a Completion Record, set value to value.[[Value]]. // 2. Else if value is a Completion Record, set value to value.[[Value]].
Ok(value) => value, Ok(value) => value,
@ -83,20 +87,9 @@ enum ReactionType {
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone, Trace, Finalize)]
struct PromiseCapability { struct PromiseCapability {
promise: JsValue, promise: JsObject,
resolve: JsValue, resolve: JsFunction,
reject: JsValue, reject: JsFunction,
}
#[derive(Debug, Trace, Finalize)]
struct PromiseCapabilityCaptures {
promise_capability: Gc<boa_gc::Cell<PromiseCapability>>,
}
#[derive(Debug, Trace, Finalize)]
struct ReactionJobCaptures {
reaction: ReactionRecord,
argument: JsValue,
} }
impl PromiseCapability { impl PromiseCapability {
@ -107,6 +100,12 @@ impl PromiseCapability {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability /// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability
fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> { fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
#[derive(Debug, Clone, Trace, Finalize)]
struct RejectResolve {
reject: JsValue,
resolve: JsValue,
}
match c.as_constructor() { match c.as_constructor() {
// 1. If IsConstructor(C) is false, throw a TypeError exception. // 1. If IsConstructor(C) is false, throw a TypeError exception.
None => context.throw_type_error("PromiseCapability: expected constructor"), None => context.throw_type_error("PromiseCapability: expected constructor"),
@ -115,20 +114,17 @@ impl PromiseCapability {
// 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1).
// 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
let promise_capability = Gc::new(boa_gc::Cell::new(Self { let promise_capability = Gc::new(boa_gc::Cell::new(RejectResolve {
promise: JsValue::Undefined, reject: JsValue::undefined(),
reject: JsValue::Undefined, resolve: JsValue::undefined(),
resolve: JsValue::Undefined,
})); }));
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called:
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
let executor = FunctionBuilder::closure_with_captures( let executor = FunctionBuilder::closure_with_captures(
context, context,
|_this, args: &[JsValue], captures: &mut PromiseCapabilityCaptures, context| { |_this, args: &[JsValue], captures, context| {
let promise_capability: &mut Self = let mut promise_capability = captures.borrow_mut();
&mut captures.promise_capability.try_borrow_mut().expect("msg");
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
if !promise_capability.resolve.is_undefined() { if !promise_capability.resolve.is_undefined() {
return context.throw_type_error( return context.throw_type_error(
@ -154,9 +150,7 @@ impl PromiseCapability {
// e. Return undefined. // e. Return undefined.
Ok(JsValue::Undefined) Ok(JsValue::Undefined)
}, },
PromiseCapabilityCaptures { promise_capability.clone(),
promise_capability: promise_capability.clone(),
},
) )
.name("") .name("")
.length(2) .length(2)
@ -166,29 +160,37 @@ impl PromiseCapability {
// 6. Let promise be ? Construct(C, « executor »). // 6. Let promise be ? Construct(C, « executor »).
let promise = c.construct(&[executor], Some(&c), context)?; let promise = c.construct(&[executor], Some(&c), context)?;
let promise_capability: &mut Self = let promise_capability = promise_capability.borrow();
&mut promise_capability.try_borrow_mut().expect("msg");
let resolve = promise_capability.resolve.clone(); let resolve = promise_capability.resolve.clone();
let reject = promise_capability.reject.clone(); let reject = promise_capability.reject.clone();
// 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception. // 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
if !resolve.is_callable() { let resolve = resolve
return context .as_object()
.throw_type_error("promiseCapability.[[Resolve]] is not callable"); .cloned()
} .and_then(JsFunction::from_object)
.ok_or_else(|| {
context
.construct_type_error("promiseCapability.[[Resolve]] is not callable")
})?;
// 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
if !reject.is_callable() { let reject = reject
return context .as_object()
.throw_type_error("promiseCapability.[[Reject]] is not callable"); .cloned()
} .and_then(JsFunction::from_object)
.ok_or_else(|| {
context.construct_type_error("promiseCapability.[[Reject]] is not callable")
})?;
// 9. Set promiseCapability.[[Promise]] to promise. // 9. Set promiseCapability.[[Promise]] to promise.
promise_capability.reject = promise;
// 10. Return promiseCapability. // 10. Return promiseCapability.
Ok(promise_capability.clone()) Ok(PromiseCapability {
promise,
resolve,
reject,
})
} }
} }
} }
@ -246,13 +248,6 @@ struct ResolvingFunctionsRecord {
reject: JsValue, reject: JsValue,
} }
#[derive(Debug, Trace, Finalize)]
struct RejectResolveCaptures {
promise: JsObject,
#[unsafe_ignore_trace]
already_resolved: Rc<Cell<bool>>,
}
impl Promise { impl Promise {
const LENGTH: usize = 1; const LENGTH: usize = 1;
@ -298,10 +293,10 @@ impl Promise {
}), }),
); );
// // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). // 8. Let resolvingFunctions be CreateResolvingFunctions(promise).
let resolving_functions = Self::create_resolving_functions(&promise, context); let resolving_functions = Self::create_resolving_functions(&promise, context);
// // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
let completion = context.call( let completion = context.call(
executor, executor,
&JsValue::Undefined, &JsValue::Undefined,
@ -331,6 +326,13 @@ impl Promise {
promise: &JsObject, promise: &JsObject,
context: &mut Context, context: &mut Context,
) -> ResolvingFunctionsRecord { ) -> ResolvingFunctionsRecord {
#[derive(Debug, Trace, Finalize)]
struct RejectResolveCaptures {
promise: JsObject,
#[unsafe_ignore_trace]
already_resolved: Rc<Cell<bool>>,
}
// 1. Let alreadyResolved be the Record { [[Value]]: false }. // 1. Let alreadyResolved be the Record { [[Value]]: false }.
let already_resolved = Rc::new(Cell::new(false)); let already_resolved = Rc::new(Cell::new(false));
@ -739,8 +741,8 @@ impl Promise {
next_promise.invoke( next_promise.invoke(
"then", "then",
&[ &[
result_capability.resolve.clone(), result_capability.resolve.clone().into(),
result_capability.reject.clone(), result_capability.reject.clone().into(),
], ],
context, context,
)?; )?;
@ -750,7 +752,7 @@ impl Promise {
iterator_record.set_done(true); iterator_record.set_done(true);
// ii. Return resultCapability.[[Promise]]. // ii. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone()); return Ok(result_capability.promise.clone().into());
} }
} }
} }
@ -774,13 +776,13 @@ impl Promise {
// 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
context.call( context.call(
&promise_capability.reject, &promise_capability.reject.clone().into(),
&JsValue::undefined(), &JsValue::undefined(),
&[r.clone()], &[r.clone()],
)?; )?;
// 4. Return promiseCapability.[[Promise]]. // 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone()) Ok(promise_capability.promise.clone().into())
} }
/// `Promise.resolve ( x )` /// `Promise.resolve ( x )`
@ -1131,7 +1133,7 @@ impl Promise {
// 14. Else, // 14. Else,
// a. Return resultCapability.[[Promise]]. // a. Return resultCapability.[[Promise]].
Some(result_capability) => result_capability.promise.clone(), Some(result_capability) => result_capability.promise.clone().into(),
} }
} }
@ -1160,10 +1162,14 @@ impl Promise {
let promise_capability = PromiseCapability::new(&c.into(), context)?; let promise_capability = PromiseCapability::new(&c.into(), context)?;
// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
context.call(&promise_capability.resolve, &JsValue::undefined(), &[x])?; context.call(
&promise_capability.resolve.clone().into(),
&JsValue::undefined(),
&[x],
)?;
// 4. Return promiseCapability.[[Promise]]. // 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone()) Ok(promise_capability.promise.clone().into())
} }
/// `GetPromiseResolve ( promiseConstructor )` /// `GetPromiseResolve ( promiseConstructor )`

12
boa_engine/src/builtins/promise/promise_job.rs

@ -1,4 +1,4 @@
use super::{Promise, PromiseCapability, ReactionJobCaptures}; use super::{Promise, PromiseCapability};
use crate::{ use crate::{
builtins::promise::{ReactionRecord, ReactionType}, builtins::promise::{ReactionRecord, ReactionType},
job::JobCallback, job::JobCallback,
@ -20,6 +20,12 @@ impl PromiseJob {
argument: JsValue, argument: JsValue,
context: &mut Context, context: &mut Context,
) -> JobCallback { ) -> JobCallback {
#[derive(Debug, Trace, Finalize)]
struct ReactionJobCaptures {
reaction: ReactionRecord,
argument: JsValue,
}
// 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called:
let job = FunctionBuilder::closure_with_captures( let job = FunctionBuilder::closure_with_captures(
context, context,
@ -77,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then // h. If handlerResult is an abrupt completion, then
Err(value) => { Err(value) => {
// i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
context.call(reject, &JsValue::Undefined, &[value]) context.call(&reject.clone().into(), &JsValue::Undefined, &[value])
} }
// i. Else, // i. Else,
Ok(value) => { Ok(value) => {
// i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
context.call(resolve, &JsValue::Undefined, &[value]) context.call(&resolve.clone().into(), &JsValue::Undefined, &[value])
} }
} }
} }

4
boa_engine/src/builtins/reflect/mod.rs

@ -126,7 +126,9 @@ impl Reflect {
.create_list_from_array_like(&[], context)?; .create_list_from_array_like(&[], context)?;
// 5. Return ? Construct(target, args, newTarget). // 5. Return ? Construct(target, args, newTarget).
target.construct(&args, Some(new_target), context) target
.construct(&args, Some(new_target), context)
.map(JsValue::from)
} }
/// Defines a property on an object. /// Defines a property on an object.

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

@ -1184,9 +1184,6 @@ impl RegExp {
// 6. Let matcher be ? Construct(C, « R, flags »). // 6. Let matcher be ? Construct(C, « R, flags »).
let matcher = c.construct(&[this.clone(), flags.clone().into()], Some(&c), context)?; let matcher = c.construct(&[this.clone(), flags.clone().into()], Some(&c), context)?;
let matcher = matcher
.as_object()
.expect("construct must always return an Object");
// 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
let last_index = regexp.get("lastIndex", context)?.to_length(context)?; let last_index = regexp.get("lastIndex", context)?.to_length(context)?;
@ -1579,11 +1576,6 @@ impl RegExp {
Some(&constructor), Some(&constructor),
context, context,
)?; )?;
let splitter = splitter
.as_object()
// TODO: remove when we handle realms on `get_prototype_from_constructor` and make
// `construct` always return a `JsObject`
.ok_or_else(|| context.construct_type_error("constructor did not return an object"))?;
// 11. Let A be ! ArrayCreate(0). // 11. Let A be ! ArrayCreate(0).
let a = Array::array_create(0, None, context).expect("this ArrayCreate call must not fail"); let a = Array::array_create(0, None, context).expect("this ArrayCreate call must not fail");
@ -1610,7 +1602,7 @@ impl RegExp {
// 16. If size is 0, then // 16. If size is 0, then
if size == 0 { if size == 0 {
// a. Let z be ? RegExpExec(splitter, S). // a. Let z be ? RegExpExec(splitter, S).
let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?;
// b. If z is not null, return A. // b. If z is not null, return A.
if result.is_some() { if result.is_some() {
@ -1636,7 +1628,7 @@ impl RegExp {
splitter.set("lastIndex", JsValue::new(q), true, context)?; splitter.set("lastIndex", JsValue::new(q), true, context)?;
// b. Let z be ? RegExpExec(splitter, S). // b. Let z be ? RegExpExec(splitter, S).
let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?;
// c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching). // c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching).
// d. Else, // d. Else,

7
boa_engine/src/builtins/typed_array/mod.rs

@ -2970,11 +2970,8 @@ impl TypedArray {
// 1. Let newTypedArray be ? Construct(constructor, argumentList). // 1. Let newTypedArray be ? Construct(constructor, argumentList).
let new_typed_array = constructor.construct(args, Some(constructor), context)?; let new_typed_array = constructor.construct(args, Some(constructor), context)?;
let obj_borrow = new_typed_array.borrow();
// 2. Perform ? ValidateTypedArray(newTypedArray). // 2. Perform ? ValidateTypedArray(newTypedArray).
let obj = new_typed_array
.as_object()
.ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?;
let obj_borrow = obj.borrow();
let o = obj_borrow let o = obj_borrow
.as_typed_array() .as_typed_array()
.ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?;
@ -2994,7 +2991,7 @@ impl TypedArray {
} }
// 4. Return newTypedArray. // 4. Return newTypedArray.
Ok(obj.clone()) Ok(new_typed_array.clone())
} }
/// <https://tc39.es/ecma262/#sec-allocatetypedarraybuffer> /// <https://tc39.es/ecma262/#sec-allocatetypedarraybuffer>

26
boa_engine/src/bytecompiler.rs

@ -1,5 +1,5 @@
use crate::{ use crate::{
builtins::function::ThisMode, builtins::function::{ConstructorKind, ThisMode},
environments::{BindingLocator, CompileTimeEnvironment}, environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{ syntax::ast::{
node::{ node::{
@ -81,7 +81,7 @@ impl<'b> ByteCompiler<'b> {
#[inline] #[inline]
pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self { pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self {
Self { Self {
code_block: CodeBlock::new(name, 0, strict, false), code_block: CodeBlock::new(name, 0, strict, None),
literals_map: FxHashMap::default(), literals_map: FxHashMap::default(),
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
@ -1927,15 +1927,20 @@ impl<'b> ByteCompiler<'b> {
) -> JsResult<Gc<CodeBlock>> { ) -> JsResult<Gc<CodeBlock>> {
let strict = strict || body.strict(); let strict = strict || body.strict();
let length = parameters.length(); let length = parameters.length();
let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true); let mut code = CodeBlock::new(
name.unwrap_or(Sym::EMPTY_STRING),
length,
strict,
Some(ConstructorKind::Base),
);
if let FunctionKind::Arrow = kind { if let FunctionKind::Arrow = kind {
code.constructor = false; code.constructor = None;
code.this_mode = ThisMode::Lexical; code.this_mode = ThisMode::Lexical;
} }
if generator { if generator {
code.constructor = false; code.constructor = None;
} }
let mut compiler = ByteCompiler { let mut compiler = ByteCompiler {
@ -2536,7 +2541,16 @@ impl<'b> ByteCompiler<'b> {
/// A class declaration binds the resulting class object to it's identifier. /// A class declaration binds the resulting class object to it's identifier.
/// A class expression leaves the resulting class object on the stack for following operations. /// A class expression leaves the resulting class object on the stack for following operations.
fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> { fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> {
let mut code = CodeBlock::new(class.name(), 0, true, true); let mut code = CodeBlock::new(
class.name(),
0,
true,
Some(if class.super_ref().is_some() {
ConstructorKind::Derived
} else {
ConstructorKind::Base
}),
);
code.computed_field_names = Some(gc::GcCell::new(vec![])); code.computed_field_names = Some(gc::GcCell::new(vec![]));
let mut compiler = ByteCompiler { let mut compiler = ByteCompiler {
code_block: code, code_block: code,

2
boa_engine/src/object/internal_methods/bound_function.rs

@ -70,7 +70,7 @@ fn bound_function_exotic_construct(
arguments_list: &[JsValue], arguments_list: &[JsValue],
new_target: &JsObject, new_target: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
let object = obj.borrow(); let object = obj.borrow();
let bound_function = object let bound_function = object
.as_bound_function() .as_bound_function()

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

@ -55,6 +55,6 @@ fn function_construct(
args: &[JsValue], args: &[JsValue],
new_target: &JsObject, new_target: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
obj.construct_internal(args, &new_target.clone().into(), context) obj.construct_internal(args, &new_target.clone().into(), context)
} }

4
boa_engine/src/object/internal_methods/mod.rs

@ -261,7 +261,7 @@ impl JsObject {
args: &[JsValue], args: &[JsValue],
new_target: &JsObject, new_target: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
let _timer = Profiler::global().start_event("Object::__construct__", "object"); let _timer = Profiler::global().start_event("Object::__construct__", "object");
let func = self.borrow().data.internal_methods.__construct__; let func = self.borrow().data.internal_methods.__construct__;
func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
@ -324,7 +324,7 @@ pub(crate) struct InternalObjectMethods {
pub(crate) __call__: pub(crate) __call__:
Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>>, Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>>,
pub(crate) __construct__: pub(crate) __construct__:
Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context) -> JsResult<JsValue>>, Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context) -> JsResult<JsObject>>,
} }
/// Abstract operation `OrdinaryGetPrototypeOf`. /// Abstract operation `OrdinaryGetPrototypeOf`.

8
boa_engine/src/object/internal_methods/proxy.rs

@ -946,7 +946,7 @@ fn proxy_exotic_construct(
args: &[JsValue], args: &[JsValue],
new_target: &JsObject, new_target: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
// 1. Let handler be O.[[ProxyHandler]]. // 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception. // 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object. // 3. Assert: Type(handler) is Object.
@ -984,9 +984,9 @@ fn proxy_exotic_construct(
)?; )?;
// 10. If Type(newObj) is not Object, throw a TypeError exception. // 10. If Type(newObj) is not Object, throw a TypeError exception.
if !new_obj.is_object() { let new_obj = new_obj.as_object().cloned().ok_or_else(|| {
return context.throw_type_error("Proxy trap constructor returned non-object value"); context.construct_type_error("Proxy trap constructor returned non-object value")
} })?;
// 11. Return newObj. // 11. Return newObj.
Ok(new_obj) Ok(new_obj)

14
boa_engine/src/object/jsfunction.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
object::{JsObject, JsObjectType}, object::{JsObject, JsObjectType},
Context, JsResult, JsValue, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use std::ops::Deref; use std::ops::Deref;
@ -17,16 +17,14 @@ impl JsFunction {
Self { inner: object } Self { inner: object }
} }
/// Create a [`JsFunction`] from a [`JsObject`], if the object is not a function throw a `TypeError`. /// Create a [`JsFunction`] from a [`JsObject`], or return `None` if the object is not a function.
/// ///
/// This does not clone the fields of the function, it only does a shallow clone of the object. /// This does not clone the fields of the function, it only does a shallow clone of the object.
#[inline] #[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { pub fn from_object(object: JsObject) -> Option<Self> {
if object.borrow().is_function() { object
Ok(Self::from_object_unchecked(object)) .is_callable()
} else { .then(|| Self::from_object_unchecked(object))
context.throw_type_error("object is not an Function")
}
} }
} }

19
boa_engine/src/object/mod.rs

@ -28,7 +28,8 @@ use crate::{
array_buffer::ArrayBuffer, array_buffer::ArrayBuffer,
function::arguments::Arguments, function::arguments::Arguments,
function::{ function::{
arguments::ParameterMap, BoundFunction, Captures, Function, NativeFunctionSignature, arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function,
NativeFunctionSignature,
}, },
generator::Generator, generator::Generator,
map::map_iterator::MapIterator, map::map_iterator::MapIterator,
@ -1460,7 +1461,7 @@ impl<'context> FunctionBuilder<'context> {
context, context,
function: Function::Native { function: Function::Native {
function, function,
constructor: false, constructor: None,
}, },
name: JsString::default(), name: JsString::default(),
length: 0, length: 0,
@ -1477,7 +1478,7 @@ impl<'context> FunctionBuilder<'context> {
context, context,
function: Function::Closure { function: Function::Closure {
function: Box::new(move |this, args, _, context| function(this, args, context)), function: Box::new(move |this, args, _, context| function(this, args, context)),
constructor: false, constructor: None,
captures: Captures::new(()), captures: Captures::new(()),
}, },
name: JsString::default(), name: JsString::default(),
@ -1511,7 +1512,7 @@ impl<'context> FunctionBuilder<'context> {
})?; })?;
function(this, args, captures, context) function(this, args, captures, context)
}), }),
constructor: false, constructor: None,
captures: Captures::new(captures), captures: Captures::new(captures),
}, },
name: JsString::default(), name: JsString::default(),
@ -1559,7 +1560,7 @@ impl<'context> FunctionBuilder<'context> {
ref mut constructor, ref mut constructor,
.. ..
} => { } => {
*constructor = yes; *constructor = yes.then(|| ConstructorKind::Base);
} }
Function::Ordinary { .. } | Function::Generator { .. } => { Function::Ordinary { .. } | Function::Generator { .. } => {
unreachable!("function must be native or closure"); unreachable!("function must be native or closure");
@ -1716,7 +1717,7 @@ pub struct ConstructorBuilder<'context> {
name: JsString, name: JsString,
length: usize, length: usize,
callable: bool, callable: bool,
constructor: bool, constructor: Option<ConstructorKind>,
inherit: Option<JsPrototype>, inherit: Option<JsPrototype>,
custom_prototype: Option<JsPrototype>, custom_prototype: Option<JsPrototype>,
} }
@ -1748,7 +1749,7 @@ impl<'context> ConstructorBuilder<'context> {
length: 0, length: 0,
name: JsString::default(), name: JsString::default(),
callable: true, callable: true,
constructor: true, constructor: Some(ConstructorKind::Base),
inherit: None, inherit: None,
custom_prototype: None, custom_prototype: None,
has_prototype_property: true, has_prototype_property: true,
@ -1770,7 +1771,7 @@ impl<'context> ConstructorBuilder<'context> {
length: 0, length: 0,
name: JsString::default(), name: JsString::default(),
callable: true, callable: true,
constructor: true, constructor: Some(ConstructorKind::Base),
inherit: None, inherit: None,
custom_prototype: None, custom_prototype: None,
} }
@ -1967,7 +1968,7 @@ impl<'context> ConstructorBuilder<'context> {
/// Default is `true` /// Default is `true`
#[inline] #[inline]
pub fn constructor(&mut self, constructor: bool) -> &mut Self { pub fn constructor(&mut self, constructor: bool) -> &mut Self {
self.constructor = constructor; self.constructor = constructor.then(|| ConstructorKind::Base);
self self
} }

2
boa_engine/src/object/operations.rs

@ -328,7 +328,7 @@ impl JsObject {
args: &[JsValue], args: &[JsValue],
new_target: Option<&JsObject>, new_target: Option<&JsObject>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
// 1. If newTarget is not present, set newTarget to F. // 1. If newTarget is not present, set newTarget to F.
let new_target = new_target.unwrap_or(self); let new_target = new_target.unwrap_or(self);
// 2. If argumentsList is not present, set argumentsList to a new empty List. // 2. If argumentsList is not present, set argumentsList to a new empty List.

84
boa_engine/src/vm/code_block.rs

@ -5,7 +5,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::{ function::{
arguments::Arguments, Captures, ClosureFunctionSignature, Function, arguments::Arguments, Captures, ClosureFunctionSignature, ConstructorKind, Function,
NativeFunctionSignature, ThisMode, NativeFunctionSignature, ThisMode,
}, },
generator::{Generator, GeneratorContext, GeneratorState}, generator::{Generator, GeneratorContext, GeneratorState},
@ -62,10 +62,11 @@ pub struct CodeBlock {
/// Is this function in strict mode. /// Is this function in strict mode.
pub(crate) strict: bool, pub(crate) strict: bool,
/// Is this function a constructor. /// Constructor type of this function, or `None` if the function is not constructable.
pub(crate) constructor: bool, #[unsafe_ignore_trace]
pub(crate) constructor: Option<ConstructorKind>,
/// [[ThisMode]] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
/// Parameters passed to this function. /// Parameters passed to this function.
@ -108,7 +109,7 @@ pub struct CodeBlock {
impl CodeBlock { impl CodeBlock {
/// Constructs a new `CodeBlock`. /// Constructs a new `CodeBlock`.
pub fn new(name: Sym, length: u32, strict: bool, constructor: bool) -> Self { pub fn new(name: Sym, length: u32, strict: bool, constructor: Option<ConstructorKind>) -> Self {
Self { Self {
code: Vec::new(), code: Vec::new(),
literals: Vec::new(), literals: Vec::new(),
@ -588,7 +589,7 @@ impl JsObject {
function, function,
constructor, constructor,
} => { } => {
if *constructor { if constructor.is_some() {
construct = true; construct = true;
} }
@ -868,17 +869,28 @@ impl JsObject {
args: &[JsValue], args: &[JsValue],
this_target: &JsValue, this_target: &JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
let this_function_object = self.clone(); let this_function_object = self.clone();
// let mut has_parameter_expressions = false; // let mut has_parameter_expressions = false;
let create_this = |context| {
let prototype =
get_prototype_from_constructor(this_target, StandardConstructors::object, context)?;
Ok(Self::from_proto_and_data(prototype, ObjectData::ordinary()))
};
if !self.is_constructor() { if !self.is_constructor() {
return context.throw_type_error("not a constructor function"); return context.throw_type_error("not a constructor function");
} }
let constructor;
let body = { let body = {
let object = self.borrow(); let object = self.borrow();
let function = object.as_function().expect("not a function"); let function = object.as_function().expect("not a function");
constructor = function
.constructor()
.expect("Already checked that the function was a constructor");
match function { match function {
Function::Native { function, .. } => FunctionBody::Native { Function::Native { function, .. } => FunctionBody::Native {
@ -901,9 +913,31 @@ impl JsObject {
}; };
match body { match body {
FunctionBody::Native { function, .. } => function(this_target, args, context), FunctionBody::Native { function, .. } => match function(this_target, args, context)? {
JsValue::Object(ref o) => Ok(o.clone()),
val => {
if constructor.is_base() || val.is_undefined() {
create_this(context)
} else {
context.throw_type_error(
"Derived constructor can only return an Object or undefined",
)
}
}
},
FunctionBody::Closure { function, captures } => { FunctionBody::Closure { function, captures } => {
(function)(this_target, args, captures, context) match (function)(this_target, args, captures, context)? {
JsValue::Object(ref o) => Ok(o.clone()),
val => {
if constructor.is_base() || val.is_undefined() {
create_this(context)
} else {
context.throw_type_error(
"Derived constructor can only return an Object or undefined",
)
}
}
}
} }
FunctionBody::Ordinary { FunctionBody::Ordinary {
code, code,
@ -911,17 +945,7 @@ impl JsObject {
} => { } => {
std::mem::swap(&mut environments, &mut context.realm.environments); std::mem::swap(&mut environments, &mut context.realm.environments);
let this = { let this = create_this(context)?;
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype = get_prototype_from_constructor(
this_target,
StandardConstructors::object,
context,
)?;
let this = Self::from_proto_and_data(prototype, ObjectData::ordinary());
// Set computed class field names if they exist. // Set computed class field names if they exist.
if let Some(fields) = &code.computed_field_names { if let Some(fields) = &code.computed_field_names {
@ -930,9 +954,6 @@ impl JsObject {
} }
} }
this
};
if code.params.has_expressions() { if code.params.has_expressions() {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
@ -1034,10 +1055,21 @@ impl JsObject {
let (result, _) = result?; let (result, _) = result?;
if result.is_object() { match result {
Ok(result) JsValue::Object(ref obj) => Ok(obj.clone()),
val => {
if constructor.is_base() || val.is_undefined() {
Ok(frame
.this
.as_object()
.cloned()
.expect("13. Assert: Type(thisBinding) is Object."))
} else { } else {
Ok(frame.this.clone()) context.throw_type_error(
"Derived constructor can only return an Object or undefined",
)
}
}
} }
} }
FunctionBody::Generator { .. } => { FunctionBody::Generator { .. } => {

4
boa_engine/src/vm/mod.rs

@ -1481,9 +1481,9 @@ impl Context {
[compile_environments_index as usize] [compile_environments_index as usize]
.clone(); .clone();
let is_constructor = self.vm.frame().code.constructor; let constructor = self.vm.frame().code.constructor;
let is_lexical = self.vm.frame().code.this_mode.is_lexical(); let is_lexical = self.vm.frame().code.this_mode.is_lexical();
let this = if is_constructor || !is_lexical { let this = if constructor.is_some() || !is_lexical {
self.vm.frame().this.clone() self.vm.frame().this.clone()
} else { } else {
JsValue::undefined() JsValue::undefined()

Loading…
Cancel
Save