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

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

@ -243,13 +243,9 @@ impl ArrayBuffer {
// 16. Let new be ? Construct(ctor, « 𝔽(newLen) »).
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(|| {
context.construct_type_error("ArrayBuffer constructor returned invalid object")
})?;
@ -264,44 +260,50 @@ impl ArrayBuffer {
}
}
// 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");
}
let mut new_obj_borrow = new_obj.borrow_mut();
let new_array_buffer = new_obj_borrow
.as_array_buffer_mut()
.expect("Already checked that `new_obj` was an `ArrayBuffer`");
{
let mut new_obj_borrow = new.borrow_mut();
let new_array_buffer = new_obj_borrow
.as_array_buffer_mut()
.expect("Already checked that `new_obj` was an `ArrayBuffer`");
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
if new_array_buffer.array_buffer_byte_length < new_len {
return context.throw_type_error("New ArrayBuffer length too small");
}
// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
if new_array_buffer.array_buffer_byte_length < new_len {
return context.throw_type_error("New ArrayBuffer length too small");
}
// 22. NOTE: Side-effects of the above steps may have detached O.
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
if Self::is_detached_buffer(o) {
return context
.throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running");
}
// 22. NOTE: Side-effects of the above steps may have detached O.
// 23. If IsDetachedBuffer(O) is true, throw a TypeError exception.
if Self::is_detached_buffer(o) {
return context
.throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running");
}
// 24. Let fromBuf be O.[[ArrayBufferData]].
let from_buf = o
.array_buffer_data
.as_ref()
.expect("ArrayBuffer cannot be detached here");
// 24. Let fromBuf be O.[[ArrayBufferData]].
let from_buf = o
.array_buffer_data
.as_ref()
.expect("ArrayBuffer cannot be detached here");
// 25. Let toBuf be new.[[ArrayBufferData]].
let to_buf = new_array_buffer
.array_buffer_data
.as_mut()
.expect("ArrayBuffer cannot be detached here");
// 25. Let toBuf be new.[[ArrayBufferData]].
let to_buf = new_array_buffer
.array_buffer_data
.as_mut()
.expect("ArrayBuffer cannot be detached here");
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len);
// 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len);
}
// 27. Return new.
Ok(new)
Ok(new.into())
}
/// `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(),
ObjectData::function(Function::Native {
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 {
Base,
Derived,
@ -178,12 +178,14 @@ pub enum Function {
Native {
#[unsafe_ignore_trace]
function: NativeFunctionSignature,
constructor: bool,
#[unsafe_ignore_trace]
constructor: Option<ConstructorKind>,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>,
constructor: bool,
#[unsafe_ignore_trace]
constructor: Option<ConstructorKind>,
captures: Captures,
},
Ordinary {
@ -205,6 +207,11 @@ impl fmt::Debug for Function {
impl Function {
/// Returns true if the function object is a constructor.
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 {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor,
Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor,
@ -251,7 +258,7 @@ pub(crate) fn make_builtin_fn<N>(
.prototype(),
ObjectData::function(Function::Native {
function,
constructor: false,
constructor: None,
}),
);
let attribute = PropertyDescriptor::builder()
@ -417,7 +424,7 @@ impl BuiltInFunctionObject {
prototype,
ObjectData::function(Function::Native {
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 super::function::ConstructorKind;
/// The internal representation on a `Generator` object.
#[derive(Debug, Clone, Copy)]
pub struct GeneratorFunction;
@ -71,7 +73,7 @@ impl BuiltIn for GeneratorFunction {
constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor,
constructor: true,
constructor: Some(ConstructorKind::Base),
});
prototype.set_prototype(Some(
@ -124,7 +126,7 @@ impl GeneratorFunction {
prototype,
ObjectData::function(Function::Native {
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,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
JsFunction, JsObject, ObjectData,
},
property::Attribute,
symbol::WellKnownSymbols,
@ -39,10 +39,14 @@ macro_rules! if_abrupt_reject_promise {
// 1. If value is an abrupt completion, then
Err(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]].
return Ok($capability.promise.clone());
return Ok($capability.promise.clone().into());
}
// 2. Else if value is a Completion Record, set value to value.[[Value]].
Ok(value) => value,
@ -83,20 +87,9 @@ enum ReactionType {
#[derive(Debug, Clone, Trace, Finalize)]
struct PromiseCapability {
promise: JsValue,
resolve: JsValue,
reject: JsValue,
}
#[derive(Debug, Trace, Finalize)]
struct PromiseCapabilityCaptures {
promise_capability: Gc<boa_gc::Cell<PromiseCapability>>,
}
#[derive(Debug, Trace, Finalize)]
struct ReactionJobCaptures {
reaction: ReactionRecord,
argument: JsValue,
promise: JsObject,
resolve: JsFunction,
reject: JsFunction,
}
impl PromiseCapability {
@ -107,6 +100,12 @@ impl PromiseCapability {
///
/// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability
fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
#[derive(Debug, Clone, Trace, Finalize)]
struct RejectResolve {
reject: JsValue,
resolve: JsValue,
}
match c.as_constructor() {
// 1. If IsConstructor(C) is false, throw a TypeError exception.
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).
// 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }.
let promise_capability = Gc::new(boa_gc::Cell::new(Self {
promise: JsValue::Undefined,
reject: JsValue::Undefined,
resolve: JsValue::Undefined,
let promise_capability = Gc::new(boa_gc::Cell::new(RejectResolve {
reject: 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:
// 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
let executor = FunctionBuilder::closure_with_captures(
context,
|_this, args: &[JsValue], captures: &mut PromiseCapabilityCaptures, context| {
let promise_capability: &mut Self =
&mut captures.promise_capability.try_borrow_mut().expect("msg");
|_this, args: &[JsValue], captures, context| {
let mut promise_capability = captures.borrow_mut();
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
if !promise_capability.resolve.is_undefined() {
return context.throw_type_error(
@ -154,9 +150,7 @@ impl PromiseCapability {
// e. Return undefined.
Ok(JsValue::Undefined)
},
PromiseCapabilityCaptures {
promise_capability: promise_capability.clone(),
},
promise_capability.clone(),
)
.name("")
.length(2)
@ -166,29 +160,37 @@ impl PromiseCapability {
// 6. Let promise be ? Construct(C, « executor »).
let promise = c.construct(&[executor], Some(&c), context)?;
let promise_capability: &mut Self =
&mut promise_capability.try_borrow_mut().expect("msg");
let promise_capability = promise_capability.borrow();
let resolve = promise_capability.resolve.clone();
let reject = promise_capability.reject.clone();
// 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
if !resolve.is_callable() {
return context
.throw_type_error("promiseCapability.[[Resolve]] is not callable");
}
let resolve = resolve
.as_object()
.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.
if !reject.is_callable() {
return context
.throw_type_error("promiseCapability.[[Reject]] is not callable");
}
let reject = reject
.as_object()
.cloned()
.and_then(JsFunction::from_object)
.ok_or_else(|| {
context.construct_type_error("promiseCapability.[[Reject]] is not callable")
})?;
// 9. Set promiseCapability.[[Promise]] to promise.
promise_capability.reject = promise;
// 10. Return promiseCapability.
Ok(promise_capability.clone())
Ok(PromiseCapability {
promise,
resolve,
reject,
})
}
}
}
@ -246,13 +248,6 @@ struct ResolvingFunctionsRecord {
reject: JsValue,
}
#[derive(Debug, Trace, Finalize)]
struct RejectResolveCaptures {
promise: JsObject,
#[unsafe_ignore_trace]
already_resolved: Rc<Cell<bool>>,
}
impl Promise {
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);
// // 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(
executor,
&JsValue::Undefined,
@ -331,6 +326,13 @@ impl Promise {
promise: &JsObject,
context: &mut Context,
) -> 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 }.
let already_resolved = Rc::new(Cell::new(false));
@ -739,8 +741,8 @@ impl Promise {
next_promise.invoke(
"then",
&[
result_capability.resolve.clone(),
result_capability.reject.clone(),
result_capability.resolve.clone().into(),
result_capability.reject.clone().into(),
],
context,
)?;
@ -750,7 +752,7 @@ impl Promise {
iterator_record.set_done(true);
// 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 »).
context.call(
&promise_capability.reject,
&promise_capability.reject.clone().into(),
&JsValue::undefined(),
&[r.clone()],
)?;
// 4. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone())
Ok(promise_capability.promise.clone().into())
}
/// `Promise.resolve ( x )`
@ -1131,7 +1133,7 @@ impl Promise {
// 14. Else,
// 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)?;
// 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]].
Ok(promise_capability.promise.clone())
Ok(promise_capability.promise.clone().into())
}
/// `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::{
builtins::promise::{ReactionRecord, ReactionType},
job::JobCallback,
@ -20,6 +20,12 @@ impl PromiseJob {
argument: JsValue,
context: &mut Context,
) -> 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:
let job = FunctionBuilder::closure_with_captures(
context,
@ -77,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then
Err(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,
Ok(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)?;
// 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.

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

@ -1184,9 +1184,6 @@ impl RegExp {
// 6. Let matcher be ? Construct(C, « R, flags »).
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")).
let last_index = regexp.get("lastIndex", context)?.to_length(context)?;
@ -1579,11 +1576,6 @@ impl RegExp {
Some(&constructor),
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).
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
if size == 0 {
// 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.
if result.is_some() {
@ -1636,7 +1628,7 @@ impl RegExp {
splitter.set("lastIndex", JsValue::new(q), true, context)?;
// 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).
// d. Else,

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

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

26
boa_engine/src/bytecompiler.rs

@ -1,5 +1,5 @@
use crate::{
builtins::function::ThisMode,
builtins::function::{ConstructorKind, ThisMode},
environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{
node::{
@ -81,7 +81,7 @@ impl<'b> ByteCompiler<'b> {
#[inline]
pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self {
Self {
code_block: CodeBlock::new(name, 0, strict, false),
code_block: CodeBlock::new(name, 0, strict, None),
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
@ -1927,15 +1927,20 @@ impl<'b> ByteCompiler<'b> {
) -> JsResult<Gc<CodeBlock>> {
let strict = strict || body.strict();
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 {
code.constructor = false;
code.constructor = None;
code.this_mode = ThisMode::Lexical;
}
if generator {
code.constructor = false;
code.constructor = None;
}
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 expression leaves the resulting class object on the stack for following operations.
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![]));
let mut compiler = ByteCompiler {
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],
new_target: &JsObject,
context: &mut Context,
) -> JsResult<JsValue> {
) -> JsResult<JsObject> {
let object = obj.borrow();
let bound_function = object
.as_bound_function()

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

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

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

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

14
boa_engine/src/object/jsfunction.rs

@ -1,6 +1,6 @@
use crate::{
object::{JsObject, JsObjectType},
Context, JsResult, JsValue,
JsValue,
};
use boa_gc::{Finalize, Trace};
use std::ops::Deref;
@ -17,16 +17,14 @@ impl JsFunction {
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.
#[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_function() {
Ok(Self::from_object_unchecked(object))
} else {
context.throw_type_error("object is not an Function")
}
pub fn from_object(object: JsObject) -> Option<Self> {
object
.is_callable()
.then(|| Self::from_object_unchecked(object))
}
}

19
boa_engine/src/object/mod.rs

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

2
boa_engine/src/object/operations.rs

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

98
boa_engine/src/vm/code_block.rs

@ -5,7 +5,7 @@
use crate::{
builtins::{
function::{
arguments::Arguments, Captures, ClosureFunctionSignature, Function,
arguments::Arguments, Captures, ClosureFunctionSignature, ConstructorKind, Function,
NativeFunctionSignature, ThisMode,
},
generator::{Generator, GeneratorContext, GeneratorState},
@ -62,10 +62,11 @@ pub struct CodeBlock {
/// Is this function in strict mode.
pub(crate) strict: bool,
/// Is this function a constructor.
pub(crate) constructor: bool,
/// Constructor type of this function, or `None` if the function is not constructable.
#[unsafe_ignore_trace]
pub(crate) constructor: Option<ConstructorKind>,
/// [[ThisMode]]
/// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode,
/// Parameters passed to this function.
@ -108,7 +109,7 @@ pub struct CodeBlock {
impl 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 {
code: Vec::new(),
literals: Vec::new(),
@ -588,7 +589,7 @@ impl JsObject {
function,
constructor,
} => {
if *constructor {
if constructor.is_some() {
construct = true;
}
@ -868,17 +869,28 @@ impl JsObject {
args: &[JsValue],
this_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
) -> JsResult<JsObject> {
let this_function_object = self.clone();
// 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() {
return context.throw_type_error("not a constructor function");
}
let constructor;
let body = {
let object = self.borrow();
let function = object.as_function().expect("not a function");
constructor = function
.constructor()
.expect("Already checked that the function was a constructor");
match function {
Function::Native { function, .. } => FunctionBody::Native {
@ -901,9 +913,31 @@ impl JsObject {
};
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 } => {
(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 {
code,
@ -911,27 +945,14 @@ impl JsObject {
} => {
std::mem::swap(&mut environments, &mut context.realm.environments);
let this = {
// 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.
if let Some(fields) = &code.computed_field_names {
for key in fields.borrow().iter().rev() {
context.vm.push(key);
}
}
let this = create_this(context)?;
this
};
// Set computed class field names if they exist.
if let Some(fields) = &code.computed_field_names {
for key in fields.borrow().iter().rev() {
context.vm.push(key);
}
}
if code.params.has_expressions() {
context.realm.environments.push_function(
@ -1034,10 +1055,21 @@ impl JsObject {
let (result, _) = result?;
if result.is_object() {
Ok(result)
} else {
Ok(frame.this.clone())
match 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 {
context.throw_type_error(
"Derived constructor can only return an Object or undefined",
)
}
}
}
}
FunctionBody::Generator { .. } => {

4
boa_engine/src/vm/mod.rs

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

Loading…
Cancel
Save