From 13df9a1984d729328fda88ca9432a84eb4990d3b Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Thu, 30 Jun 2022 12:37:57 +0000 Subject: [PATCH] Implement `super` expressions (#2116) This Pull Request changes the following: - Implement `super` expression parsing / execution. - Implement early errors for `super` expressions. - Refactor / add internal slot representation for environment and function objects. --- boa_engine/src/builtins/eval/mod.rs | 12 +- boa_engine/src/builtins/function/mod.rs | 118 +++- boa_engine/src/bytecompiler.rs | 309 +++++----- boa_engine/src/context/mod.rs | 23 +- boa_engine/src/environments/mod.rs | 4 +- boa_engine/src/environments/runtime.rs | 255 ++++++++- boa_engine/src/object/jsobject.rs | 29 +- boa_engine/src/object/mod.rs | 5 +- .../src/syntax/ast/node/declaration/mod.rs | 119 +++- .../ast/node/field/get_super_field/mod.rs | 47 ++ boa_engine/src/syntax/ast/node/field/mod.rs | 6 +- boa_engine/src/syntax/ast/node/mod.rs | 411 +++++++++++++- boa_engine/src/syntax/ast/node/object/mod.rs | 39 +- .../src/syntax/ast/node/super_call/mod.rs | 46 ++ .../expression/left_hand_side/member.rs | 53 +- .../parser/expression/left_hand_side/mod.rs | 23 +- .../primary/async_function_expression/mod.rs | 12 +- .../primary/async_generator_expression/mod.rs | 12 +- .../primary/function_expression/mod.rs | 12 +- .../primary/generator_expression/mod.rs | 12 +- .../primary/object_initializer/mod.rs | 68 +++ boa_engine/src/syntax/parser/mod.rs | 226 +++++--- .../declaration/hoistable/class_decl/mod.rs | 161 ++++-- .../statement/declaration/hoistable/mod.rs | 9 +- boa_engine/src/vm/call_frame.rs | 4 +- boa_engine/src/vm/code_block.rs | 464 ++++++++------- boa_engine/src/vm/mod.rs | 537 ++++++++++++++++-- boa_engine/src/vm/opcode.rs | 138 ++++- 28 files changed, 2568 insertions(+), 586 deletions(-) create mode 100644 boa_engine/src/syntax/ast/node/field/get_super_field/mod.rs create mode 100644 boa_engine/src/syntax/ast/node/super_call/mod.rs diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 2fc58a692d..71b82951fa 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -79,16 +79,10 @@ impl Eval { // Because of implementation details the following code differs from the spec. - // Parse the script body (11.a - 11.d) - // TODO: Implement errors for 11.e - 11.h - let parse_result = if strict { - context.parse_strict(x.as_bytes()) - } else { - context.parse(x.as_bytes()) - }; - let body = match parse_result.map_err(|e| e.to_string()) { + // Parse the script body and handle early errors (6 - 11) + let body = match context.parse_eval(x.as_bytes(), direct, strict) { Ok(body) => body, - Err(e) => return context.throw_syntax_error(e), + Err(e) => return context.throw_syntax_error(e.to_string()), }; // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`. diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index f6c2957d58..70ad096839 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -19,7 +19,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, ObjectData, }, - object::{ConstructorBuilder, FunctionBuilder, Ref, RefMut}, + object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut}, property::{Attribute, PropertyDescriptor, PropertyKey}, symbol::WellKnownSymbols, syntax::{ast::node::FormalParameterList, Parser}, @@ -108,7 +108,13 @@ impl ThisMode { } } -#[derive(Debug, PartialEq, Clone, Copy)] +/// Represents the `[[ConstructorKind]]` internal slot of function objects. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConstructorKind { Base, Derived, @@ -126,6 +132,18 @@ impl ConstructorKind { } } +/// Record containing the field definition of classes. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type +#[derive(Clone, Debug, Trace, Finalize)] +pub enum ClassFieldDefinition { + Public(PropertyKey, JsFunction), + Private(Sym, JsFunction), +} + /// Wrapper for `Gc>` that allows passing additional /// captures through a `Copy` closure. /// @@ -191,6 +209,19 @@ pub enum Function { Ordinary { code: Gc, environments: DeclarativeEnvironmentStack, + + /// The `[[ConstructorKind]]` internal slot. + #[unsafe_ignore_trace] + constructor_kind: ConstructorKind, + + /// The `[[HomeObject]]` internal slot. + home_object: Option, + + /// The `[[Fields]]` internal slot. + fields: Vec, + + /// The `[[PrivateMethods]]` internal slot. + private_methods: Vec<(Sym, PrivateElement)>, }, Generator { code: Gc, @@ -207,14 +238,85 @@ 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() + match self { + Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { + constructor.is_some() + } + Self::Generator { .. } => false, + Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), + } } - /// Returns the constructor kind if the function is constructable, or `None` otherwise. - pub fn constructor(&self) -> Option { - match self { - Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, - Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor, + /// Returns true if the function object is a derived constructor. + pub(crate) fn is_derived_constructor(&self) -> bool { + if let Self::Ordinary { + constructor_kind, .. + } = self + { + constructor_kind.is_derived() + } else { + false + } + } + + /// Returns a reference to the function `[[HomeObject]]` slot if present. + pub(crate) fn get_home_object(&self) -> Option<&JsObject> { + if let Self::Ordinary { home_object, .. } = self { + home_object.as_ref() + } else { + None + } + } + + /// Sets the `[[HomeObject]]` slot if present. + pub(crate) fn set_home_object(&mut self, object: JsObject) { + if let Self::Ordinary { home_object, .. } = self { + *home_object = Some(object); + } + } + + /// Returns the values of the `[[Fields]]` internal slot. + pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] { + if let Self::Ordinary { fields, .. } = self { + fields + } else { + &[] + } + } + + /// Pushes a value to the `[[Fields]]` internal slot if present. + pub(crate) fn push_field(&mut self, key: PropertyKey, value: JsFunction) { + if let Self::Ordinary { fields, .. } = self { + fields.push(ClassFieldDefinition::Public(key, value)); + } + } + + /// Pushes a private value to the `[[Fields]]` internal slot if present. + pub(crate) fn push_field_private(&mut self, key: Sym, value: JsFunction) { + if let Self::Ordinary { fields, .. } = self { + fields.push(ClassFieldDefinition::Private(key, value)); + } + } + + /// Returns the values of the `[[PrivateMethods]]` internal slot. + pub(crate) fn get_private_methods(&self) -> &[(Sym, PrivateElement)] { + if let Self::Ordinary { + private_methods, .. + } = self + { + private_methods + } else { + &[] + } + } + + /// Pushes a private method to the `[[PrivateMethods]]` internal slot if present. + pub(crate) fn push_private_method(&mut self, name: Sym, method: PrivateElement) { + if let Self::Ordinary { + private_methods, .. + } = self + { + private_methods.push((name, method)); } } } diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index ee0c3c43d1..0e30741049 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::{ConstructorKind, ThisMode}, + builtins::function::ThisMode, environments::{BindingLocator, CompileTimeEnvironment}, syntax::ast::{ node::{ @@ -11,7 +11,8 @@ use crate::{ object::{MethodDefinition, PropertyDefinition, PropertyName}, operator::assign::AssignTarget, template::TemplateElement, - Class, Declaration, FormalParameterList, GetConstField, GetField, StatementList, + Class, Declaration, FormalParameterList, GetConstField, GetField, GetSuperField, + StatementList, }, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, @@ -81,7 +82,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, None), + code_block: CodeBlock::new(name, 0, strict), literals_map: FxHashMap::default(), names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), @@ -1003,7 +1004,7 @@ impl<'b> ByteCompiler<'b> { self.compile_expr(node.obj(), true)?; self.emit_opcode(Opcode::Swap); let index = self.get_or_insert_name(node.field()); - self.emit(Opcode::SetPrivateValue, &[index]); + self.emit(Opcode::AssignPrivateField, &[index]); } AssignTarget::GetConstField(node) => { self.access_set(Access::ByName { node }, Some(assign.rhs()), use_expr)?; @@ -1032,6 +1033,24 @@ impl<'b> ByteCompiler<'b> { let access = Access::ByValue { node }; self.access_get(access, use_expr)?; } + Node::GetSuperField(get_super_field) => match get_super_field { + GetSuperField::Const(field) => { + let index = self.get_or_insert_name(*field); + self.emit_opcode(Opcode::Super); + self.emit(Opcode::GetPropertyByName, &[index]); + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } + GetSuperField::Expr(expr) => { + self.compile_expr(expr, true)?; + self.emit_opcode(Opcode::Super); + self.emit_opcode(Opcode::GetPropertyByValue); + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } + }, Node::ConditionalOp(op) => { self.compile_expr(op.cond(), true)?; let jelse = self.jump_if_false(); @@ -1181,6 +1200,24 @@ impl<'b> ByteCompiler<'b> { self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]); } Node::ClassExpr(class) => self.class(class, true)?, + Node::SuperCall(super_call) => { + for arg in super_call.args() { + self.compile_expr(arg, true)?; + } + + let last_is_rest_parameter = + matches!(super_call.args().last(), Some(Node::Spread(_))); + + if last_is_rest_parameter { + self.emit(Opcode::SuperCallWithRest, &[super_call.args().len() as u32]); + } else { + self.emit(Opcode::SuperCall, &[super_call.args().len() as u32]); + } + + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } _ => unreachable!(), } Ok(()) @@ -1194,10 +1231,6 @@ impl<'b> ByteCompiler<'b> { match decl { Declaration::Identifier { ident, .. } => { let ident = ident.sym(); - if ident == Sym::ARGUMENTS { - self.code_block.lexical_name_argument = true; - } - if let Some(expr) = decl.init() { self.compile_expr(expr, true)?; self.emit_binding(BindingOpcode::InitVar, ident); @@ -1206,10 +1239,6 @@ impl<'b> ByteCompiler<'b> { } } Declaration::Pattern(pattern) => { - if pattern.idents().contains(&Sym::ARGUMENTS) { - self.code_block.lexical_name_argument = true; - } - if let Some(init) = decl.init() { self.compile_expr(init, true)?; } else { @@ -1225,10 +1254,6 @@ impl<'b> ByteCompiler<'b> { for decl in list.as_ref() { match decl { Declaration::Identifier { ident, .. } => { - if ident.sym() == Sym::ARGUMENTS { - self.code_block.lexical_name_argument = true; - } - if let Some(expr) = decl.init() { self.compile_expr(expr, true)?; self.emit_binding(BindingOpcode::InitLet, ident.sym()); @@ -1237,10 +1262,6 @@ impl<'b> ByteCompiler<'b> { } } Declaration::Pattern(pattern) => { - if pattern.idents().contains(&Sym::ARGUMENTS) { - self.code_block.lexical_name_argument = true; - } - if let Some(init) = decl.init() { self.compile_expr(init, true)?; } else { @@ -1256,9 +1277,6 @@ impl<'b> ByteCompiler<'b> { for decl in list.as_ref() { match decl { Declaration::Identifier { ident, .. } => { - if ident.sym() == Sym::ARGUMENTS { - self.code_block.lexical_name_argument = true; - } let init = decl .init() .expect("const declaration must have initializer"); @@ -1266,10 +1284,6 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitConst, ident.sym()); } Declaration::Pattern(pattern) => { - if pattern.idents().contains(&Sym::ARGUMENTS) { - self.code_block.lexical_name_argument = true; - } - if let Some(init) = decl.init() { self.compile_expr(init, true)?; } else { @@ -1927,22 +1941,12 @@ impl<'b> ByteCompiler<'b> { ) -> JsResult> { let strict = strict || body.strict(); let length = parameters.length(); - let mut code = CodeBlock::new( - name.unwrap_or(Sym::EMPTY_STRING), - length, - strict, - Some(ConstructorKind::Base), - ); + let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict); if let FunctionKind::Arrow = kind { - code.constructor = None; code.this_mode = ThisMode::Lexical; } - if generator { - code.constructor = None; - } - let mut compiler = ByteCompiler { code_block: code, literals_map: FxHashMap::default(), @@ -2541,17 +2545,7 @@ 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, - Some(if class.super_ref().is_some() { - ConstructorKind::Derived - } else { - ConstructorKind::Base - }), - ); - code.computed_field_names = Some(gc::GcCell::new(vec![])); + let code = CodeBlock::new(class.name(), 0, true); let mut compiler = ByteCompiler { code_block: code, literals_map: FxHashMap::default(), @@ -2562,47 +2556,6 @@ impl<'b> ByteCompiler<'b> { }; compiler.context.push_compile_time_environment(true); - for element in class.elements() { - match element { - ClassElement::FieldDefinition(name, field) => { - compiler.emit_opcode(Opcode::This); - match name { - PropertyName::Literal(name) => { - if let Some(node) = field { - compiler.compile_stmt(node, true)?; - } else { - compiler.emit_opcode(Opcode::PushUndefined); - } - compiler.emit_opcode(Opcode::Swap); - let index = compiler.get_or_insert_name(*name); - compiler.emit(Opcode::DefineOwnPropertyByName, &[index]); - } - PropertyName::Computed(_) => { - compiler.emit_opcode(Opcode::Swap); - compiler.emit_opcode(Opcode::ToPropertyKey); - if let Some(node) = field { - compiler.compile_stmt(node, true)?; - } else { - compiler.emit_opcode(Opcode::PushUndefined); - } - compiler.emit_opcode(Opcode::DefineOwnPropertyByValue); - } - } - } - ClassElement::PrivateFieldDefinition(name, field) => { - compiler.emit_opcode(Opcode::This); - if let Some(node) = field { - compiler.compile_stmt(node, true)?; - } else { - compiler.emit_opcode(Opcode::PushUndefined); - } - let index = compiler.get_or_insert_name(*name); - compiler.emit(Opcode::SetPrivateValue, &[index]); - } - _ => {} - } - } - if let Some(expr) = class.constructor() { compiler.code_block.length = expr.parameters().length(); compiler.code_block.params = expr.parameters().clone(); @@ -2667,8 +2620,12 @@ impl<'b> ByteCompiler<'b> { .compile_environments .push(compile_environment); compiler.code_block.num_bindings = num_bindings; + compiler.code_block.is_class_constructor = true; } } else { + if class.super_ref().is_some() { + compiler.emit_opcode(Opcode::SuperCallDerived); + } let (num_bindings, compile_environment) = compiler.context.pop_compile_time_environment(); compiler @@ -2676,6 +2633,7 @@ impl<'b> ByteCompiler<'b> { .compile_environments .push(compile_environment); compiler.code_block.num_bindings = num_bindings; + compiler.code_block.is_class_constructor = true; } compiler.emit_opcode(Opcode::PushUndefined); @@ -2686,6 +2644,16 @@ impl<'b> ByteCompiler<'b> { self.code_block.functions.push(code); self.emit(Opcode::GetFunction, &[index]); + self.emit_opcode(Opcode::Dup); + if let Some(node) = class.super_ref() { + self.compile_expr(node, true)?; + self.emit_opcode(Opcode::PushClassPrototype); + } else { + self.emit_opcode(Opcode::PushUndefined); + } + self.emit_opcode(Opcode::SetClassPrototype); + self.emit_opcode(Opcode::Swap); + for element in class.elements() { match element { ClassElement::StaticMethodDefinition(name, method_definition) => { @@ -2767,22 +2735,91 @@ impl<'b> ByteCompiler<'b> { MethodDefinition::Ordinary(expr) => { self.function(&expr.clone().into(), true)?; let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateValue, &[index]); + self.emit(Opcode::SetPrivateMethod, &[index]); } MethodDefinition::Generator(expr) => { self.function(&expr.clone().into(), true)?; let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateValue, &[index]); + self.emit(Opcode::SetPrivateMethod, &[index]); } // TODO: implement async MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} } } - ClassElement::FieldDefinition(PropertyName::Computed(name_node), _) => { + ClassElement::FieldDefinition(name, field) => { self.emit_opcode(Opcode::Dup); - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::Swap); - self.emit_opcode(Opcode::PushClassComputedFieldName); + match name { + PropertyName::Literal(name) => { + self.emit_push_literal(Literal::String( + self.interner().resolve_expect(*name).into(), + )); + } + PropertyName::Computed(name) => { + self.compile_expr(name, true)?; + } + } + let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true); + let mut field_compiler = ByteCompiler { + code_block: field_code, + literals_map: FxHashMap::default(), + names_map: FxHashMap::default(), + bindings_map: FxHashMap::default(), + jump_info: Vec::new(), + context: self.context, + }; + field_compiler.context.push_compile_time_environment(true); + if let Some(node) = field { + field_compiler.compile_stmt(node, true)?; + } else { + field_compiler.emit_opcode(Opcode::PushUndefined); + } + let (num_bindings, compile_environment) = + field_compiler.context.pop_compile_time_environment(); + field_compiler + .code_block + .compile_environments + .push(compile_environment); + field_compiler.code_block.num_bindings = num_bindings; + field_compiler.emit_opcode(Opcode::Return); + + let code = Gc::new(field_compiler.finish()); + let index = self.code_block.functions.len() as u32; + self.code_block.functions.push(code); + self.emit(Opcode::GetFunction, &[index]); + self.emit_opcode(Opcode::PushClassField); + } + ClassElement::PrivateFieldDefinition(name, field) => { + self.emit_opcode(Opcode::Dup); + let name_index = self.get_or_insert_name(*name); + let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true); + let mut field_compiler = ByteCompiler { + code_block: field_code, + literals_map: FxHashMap::default(), + names_map: FxHashMap::default(), + bindings_map: FxHashMap::default(), + jump_info: Vec::new(), + context: self.context, + }; + field_compiler.context.push_compile_time_environment(true); + if let Some(node) = field { + field_compiler.compile_stmt(node, true)?; + } else { + field_compiler.emit_opcode(Opcode::PushUndefined); + } + let (num_bindings, compile_environment) = + field_compiler.context.pop_compile_time_environment(); + field_compiler + .code_block + .compile_environments + .push(compile_environment); + field_compiler.code_block.num_bindings = num_bindings; + field_compiler.emit_opcode(Opcode::Return); + + let code = Gc::new(field_compiler.finish()); + let index = self.code_block.functions.len() as u32; + self.code_block.functions.push(code); + self.emit(Opcode::GetFunction, &[index]); + self.emit(Opcode::PushClassFieldPrivate, &[name_index]); } ClassElement::StaticFieldDefinition(name, field) => { self.emit_opcode(Opcode::Dup); @@ -2817,7 +2854,7 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::PushUndefined); } let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateValue, &[index]); + self.emit(Opcode::SetPrivateField, &[index]); } ClassElement::StaticBlock(statement_list) => { self.emit_opcode(Opcode::Dup); @@ -2837,24 +2874,42 @@ impl<'b> ByteCompiler<'b> { let index = self.code_block.functions.len() as u32; self.code_block.functions.push(code); self.emit(Opcode::GetFunction, &[index]); + self.emit_opcode(Opcode::SetHomeObject); self.emit(Opcode::Call, &[0]); self.emit_opcode(Opcode::Pop); } - ClassElement::MethodDefinition(..) - | ClassElement::PrivateMethodDefinition(..) - | ClassElement::PrivateFieldDefinition(..) - | ClassElement::FieldDefinition(..) => {} + ClassElement::PrivateMethodDefinition(name, method_definition) => { + self.emit_opcode(Opcode::Dup); + match method_definition { + MethodDefinition::Get(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::PushClassPrivateGetter, &[index]); + } + MethodDefinition::Set(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::PushClassPrivateSetter, &[index]); + } + MethodDefinition::Ordinary(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::PushClassPrivateMethod, &[index]); + } + MethodDefinition::Generator(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::PushClassPrivateMethod, &[index]); + } + // TODO: implement async + MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} + } + } + ClassElement::MethodDefinition(..) => {} } } - self.emit_opcode(Opcode::Dup); - - if let Some(node) = class.super_ref() { - self.compile_expr(node, true)?; - self.emit_opcode(Opcode::PushClassPrototype); - } else { - self.emit_opcode(Opcode::PushEmptyObject); - } + self.emit_opcode(Opcode::Swap); for element in class.elements() { match element { @@ -2918,37 +2973,13 @@ impl<'b> ByteCompiler<'b> { } }, // TODO: implement async - MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} - } - } - ClassElement::PrivateMethodDefinition(name, method_definition) => { - self.emit_opcode(Opcode::Dup); - match method_definition { - MethodDefinition::Get(expr) => { - self.function(&expr.clone().into(), true)?; - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateGetter, &[index]); - } - MethodDefinition::Set(expr) => { - self.function(&expr.clone().into(), true)?; - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateSetter, &[index]); - } - MethodDefinition::Ordinary(expr) => { - self.function(&expr.clone().into(), true)?; - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateValue, &[index]); - } - MethodDefinition::Generator(expr) => { - self.function(&expr.clone().into(), true)?; - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPrivateValue, &[index]); + MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => { + self.emit_opcode(Opcode::Pop); } - // TODO: implement async - MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} } } - ClassElement::PrivateFieldDefinition(..) + ClassElement::PrivateMethodDefinition(..) + | ClassElement::PrivateFieldDefinition(..) | ClassElement::StaticFieldDefinition(..) | ClassElement::PrivateStaticFieldDefinition(..) | ClassElement::StaticMethodDefinition(..) @@ -2958,9 +2989,7 @@ impl<'b> ByteCompiler<'b> { } } - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(Sym::PROTOTYPE); - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit_opcode(Opcode::Pop); if !expression { self.emit_binding(BindingOpcode::InitVar, class.name()); diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 3aa71d5a17..a0c07b3ec7 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -167,14 +167,21 @@ impl Context { parser.parse_all(self) } - /// Parse the given source text in strict mode. - pub(crate) fn parse_strict(&mut self, src: S) -> Result + /// Parse the given source text with eval specific handling. + pub(crate) fn parse_eval( + &mut self, + src: S, + direct: bool, + strict: bool, + ) -> Result where S: AsRef<[u8]>, { let mut parser = Parser::new(src.as_ref()); - parser.set_strict(); - parser.parse_all(self) + if strict { + parser.set_strict(); + } + parser.parse_eval(direct, self) } /// `Call ( F, V [ , argumentsList ] )` @@ -705,22 +712,20 @@ impl Context { #[inline] pub fn execute(&mut self, code_block: Gc) -> JsResult { let _timer = Profiler::global().start_event("Execution", "Main"); - let global_object = self.global_object().clone().into(); self.vm.push_frame(CallFrame { prev: None, code: code_block, - this: global_object, pc: 0, catch: Vec::new(), finally_return: FinallyReturn::None, finally_jump: Vec::new(), pop_on_return: 0, - loop_env_stack: vec![0], - try_env_stack: vec![crate::vm::TryStackEntry { + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { num_env: 0, num_loop_stack_entries: 0, - }], + }]), param_count: 0, arg_count: 0, generator_resume_kind: GeneratorResumeKind::Normal, diff --git a/boa_engine/src/environments/mod.rs b/boa_engine/src/environments/mod.rs index a1f88c3b42..d10b599c57 100644 --- a/boa_engine/src/environments/mod.rs +++ b/boa_engine/src/environments/mod.rs @@ -29,7 +29,9 @@ mod runtime; pub(crate) use { compile::CompileTimeEnvironment, - runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack}, + runtime::{ + BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, EnvironmentSlots, + }, }; #[cfg(test)] diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 8842386a7b..21ecf3aed0 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -1,4 +1,4 @@ -use crate::{environments::CompileTimeEnvironment, Context, JsResult, JsValue}; +use crate::{environments::CompileTimeEnvironment, object::JsObject, Context, JsResult, JsValue}; use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::Sym; use rustc_hash::FxHashSet; @@ -26,12 +26,161 @@ use rustc_hash::FxHashSet; #[derive(Debug, Trace, Finalize)] pub(crate) struct DeclarativeEnvironment { bindings: Cell>>, - this: Option, compile: Gc>, poisoned: Cell, + slots: Option, +} + +/// Describes the different types of internal slot data that an environment can hold. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) enum EnvironmentSlots { + Function(Cell), + Global, +} + +impl EnvironmentSlots { + /// Return the slots if they are part of a function environment. + pub(crate) fn as_function_slots(&self) -> Option<&Cell> { + if let Self::Function(env) = &self { + Some(env) + } else { + None + } + } +} + +/// Holds the internal slots of a function environment. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) struct FunctionSlots { + /// The `[[ThisValue]]` internal slot. + this: JsValue, + + /// The `[[ThisBindingStatus]]` internal slot. + #[unsafe_ignore_trace] + this_binding_status: ThisBindingStatus, + + /// The `[[FunctionObject]]` internal slot. + function_object: JsObject, + + /// The `[[NewTarget]]` internal slot. + new_target: Option, +} + +impl FunctionSlots { + /// Returns the value of the `[[FunctionObject]]` internal slot. + pub(crate) fn function_object(&self) -> &JsObject { + &self.function_object + } + + /// Returns the value of the `[[NewTarget]]` internal slot. + pub(crate) fn new_target(&self) -> Option<&JsObject> { + self.new_target.as_ref() + } + + /// `BindThisValue` + /// + /// Sets the given value as the `this` binding of the environment. + /// Returns `false` if the `this` binding has already been initialized. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue + pub(crate) fn bind_this_value(&mut self, this: &JsObject) -> bool { + // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. + debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical); + + // 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception. + if self.this_binding_status == ThisBindingStatus::Initialized { + return false; + } + + // 3. Set envRec.[[ThisValue]] to V. + self.this = this.clone().into(); + + // 4. Set envRec.[[ThisBindingStatus]] to initialized. + self.this_binding_status = ThisBindingStatus::Initialized; + + // 5. Return V. + true + } + + /// `HasThisBinding` + /// + /// Returns if the environment has a `this` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding + pub(crate) fn has_this_binding(&self) -> bool { + // 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true. + self.this_binding_status != ThisBindingStatus::Lexical + } + + /// `HasSuperBinding` + /// + /// Returns if the environment has a `super` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding + /// + /// # Panics + /// + /// Panics if the function object of the environment is not a function. + pub(crate) fn has_super_binding(&self) -> bool { + // 1.If envRec.[[ThisBindingStatus]] is lexical, return false. + if self.this_binding_status == ThisBindingStatus::Lexical { + return false; + } + + // 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true. + self.function_object + .borrow() + .as_function() + .expect("function object must be function") + .get_home_object() + .is_some() + } + + /// `GetThisBinding` + /// + /// Returns the `this` binding on the function environment. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> Option<&JsValue> { + // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. + debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical); + + // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. + if self.this_binding_status == ThisBindingStatus::Uninitialized { + return None; + } + + // 3. Return envRec.[[ThisValue]]. + Some(&self.this) + } +} + +/// Describes the status of a `this` binding in function environments. +#[derive(Clone, Copy, Debug, PartialEq)] +enum ThisBindingStatus { + Lexical, + Initialized, + Uninitialized, } impl DeclarativeEnvironment { + /// Returns the internal slot data of the current environment. + pub(crate) fn slots(&self) -> Option<&EnvironmentSlots> { + self.slots.as_ref() + } + /// Get the binding value from the environment by it's index. /// /// # Panics @@ -79,9 +228,9 @@ impl DeclarativeEnvironmentStack { Self { stack: vec![Gc::new(DeclarativeEnvironment { bindings: Cell::new(Vec::new()), - this: None, compile: global_compile_environment, poisoned: Cell::new(false), + slots: Some(EnvironmentSlots::Global), })], } } @@ -91,7 +240,7 @@ impl DeclarativeEnvironmentStack { /// This is only useful when compiled bindings are added after the initial compilation (eval). pub(crate) fn extend_outer_function_environment(&mut self) { for env in self.stack.iter().rev() { - if env.this.is_some() { + if let Some(EnvironmentSlots::Function(_)) = env.slots { let compile_bindings_number = env.compile.borrow().num_bindings(); let mut bindings_mut = env.bindings.borrow_mut(); @@ -163,15 +312,34 @@ impl DeclarativeEnvironmentStack { } } - /// Get the `this` value of the most outer function environment. + /// `GetThisEnvironment` + /// + /// Returns the environment that currently provides a `this` biding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getthisenvironment + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. #[inline] - pub(crate) fn get_last_this(&self) -> Option { + pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots { for env in self.stack.iter().rev() { - if let Some(this) = &env.this { - return Some(this.clone()); + if let Some(slots) = &env.slots { + match slots { + EnvironmentSlots::Function(function_env) => { + if function_env.borrow().has_this_binding() { + return slots; + } + } + EnvironmentSlots::Global => return slots, + } } } - None + + panic!("global environment must exist") } /// Push a declarative environment on the environments stack. @@ -195,9 +363,9 @@ impl DeclarativeEnvironmentStack { self.stack.push(Gc::new(DeclarativeEnvironment { bindings: Cell::new(vec![None; num_bindings]), - this: None, compile: compile_environment, poisoned: Cell::new(poisoned), + slots: None, })); } @@ -211,29 +379,78 @@ impl DeclarativeEnvironmentStack { &mut self, num_bindings: usize, compile_environment: Gc>, - this: JsValue, + this: Option, + function_object: JsObject, + new_target: Option, + lexical: bool, ) { - let poisoned = self + let outer = self .stack .last() - .expect("global environment must always exist") - .poisoned - .borrow() - .to_owned(); + .expect("global environment must always exist"); + + let poisoned = outer.poisoned.borrow().to_owned(); + + let this_binding_status = if lexical { + ThisBindingStatus::Lexical + } else if this.is_some() { + ThisBindingStatus::Initialized + } else { + ThisBindingStatus::Uninitialized + }; + + let this = if let Some(this) = this { + this + } else { + JsValue::Null + }; + + self.stack.push(Gc::new(DeclarativeEnvironment { + bindings: Cell::new(vec![None; num_bindings]), + compile: compile_environment, + poisoned: Cell::new(poisoned), + slots: Some(EnvironmentSlots::Function(Cell::new(FunctionSlots { + this, + this_binding_status, + function_object, + new_target, + }))), + })); + } + + /// Push a function environment that inherits it's internal slots from the outer environment. + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. + pub(crate) fn push_function_inherit( + &mut self, + num_bindings: usize, + compile_environment: Gc>, + ) { + let outer = self + .stack + .last() + .expect("global environment must always exist"); + + let poisoned = outer.poisoned.borrow().to_owned(); + let slots = outer.slots.clone(); self.stack.push(Gc::new(DeclarativeEnvironment { bindings: Cell::new(vec![None; num_bindings]), - this: Some(this), compile: compile_environment, poisoned: Cell::new(poisoned), + slots, })); } /// Pop environment from the environments stack. #[inline] - pub(crate) fn pop(&mut self) { + pub(crate) fn pop(&mut self) -> Gc { debug_assert!(self.stack.len() > 1); - self.stack.pop(); + self.stack + .pop() + .expect("environment stack is cannot be empty") } /// Get the most outer environment. diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index e144897504..b4f9fc313e 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -52,28 +52,13 @@ impl JsObject { /// internal slots from the `data` provided. #[inline] pub fn from_proto_and_data>>(prototype: O, data: ObjectData) -> Self { - let prototype: Option = prototype.into(); - if let Some(prototype) = prototype { - let private = { - let prototype_b = prototype.borrow(); - prototype_b.private_elements.clone() - }; - Self::from_object(Object { - data, - prototype: Some(prototype), - extensible: true, - properties: PropertyMap::default(), - private_elements: private, - }) - } else { - Self::from_object(Object { - data, - prototype: None, - extensible: true, - properties: PropertyMap::default(), - private_elements: FxHashMap::default(), - }) - } + Self::from_object(Object { + data, + prototype: prototype.into(), + extensible: true, + properties: PropertyMap::default(), + private_elements: FxHashMap::default(), + }) } /// Immutably borrows the `Object`. diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 31e10166a1..bb85953a4f 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -124,8 +124,9 @@ pub struct Object { /// The representation of private object elements. #[derive(Clone, Debug, Trace, Finalize)] -pub(crate) enum PrivateElement { - Value(JsValue), +pub enum PrivateElement { + Field(JsValue), + Method(JsObject), Accessor { getter: Option, setter: Option, diff --git a/boa_engine/src/syntax/ast/node/declaration/mod.rs b/boa_engine/src/syntax/ast/node/declaration/mod.rs index 91afb58388..22f8959527 100644 --- a/boa_engine/src/syntax/ast/node/declaration/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/mod.rs @@ -4,7 +4,7 @@ use crate::syntax::ast::node::{ join_nodes, object::PropertyName, statement_list::StatementList, - Identifier, Node, + ContainsSymbol, Identifier, Node, }; use boa_interner::{Interner, Sym, ToInternedString}; @@ -229,6 +229,30 @@ impl Declaration { Self::Pattern(pattern) => pattern.init(), } } + + /// Returns `true` if the node contains the given token. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains + pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { + match self { + Self::Identifier { init, .. } => { + if let Some(node) = init { + if node.contains(symbol) { + return true; + } + } + } + Self::Pattern(pattern) => { + if pattern.contains(symbol) { + return true; + } + } + } + false + } } /// `DeclarationPattern` represents an object or array binding pattern. @@ -383,6 +407,99 @@ impl DeclarationPattern { } false } + + /// Returns `true` if the node contains the given token. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains + pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { + match self { + DeclarationPattern::Object(object) => { + if let Some(node) = object.init() { + if node.contains(symbol) { + return true; + } + } + for binding in &object.bindings { + match binding { + BindingPatternTypeObject::SingleName { + default_init: Some(node), + .. + } => { + if node.contains(symbol) { + return true; + } + } + BindingPatternTypeObject::RestGetConstField { + get_const_field, .. + } => { + if get_const_field.obj().contains(symbol) { + return true; + } + } + BindingPatternTypeObject::BindingPattern { + pattern, + default_init, + .. + } => { + if let Some(node) = default_init { + if node.contains(symbol) { + return true; + } + } + if pattern.contains(symbol) { + return true; + } + } + _ => {} + } + } + } + DeclarationPattern::Array(array) => { + if let Some(node) = array.init() { + if node.contains(symbol) { + return true; + } + } + for binding in array.bindings() { + match binding { + BindingPatternTypeArray::SingleName { + default_init: Some(node), + .. + } => { + if node.contains(symbol) { + return true; + } + } + BindingPatternTypeArray::GetField { get_field } + | BindingPatternTypeArray::GetFieldRest { get_field } => { + if get_field.obj().contains(symbol) + || get_field.field().contains(symbol) + { + return true; + } + } + BindingPatternTypeArray::GetConstField { get_const_field } + | BindingPatternTypeArray::GetConstFieldRest { get_const_field } => { + if get_const_field.obj().contains(symbol) { + return true; + } + } + BindingPatternTypeArray::BindingPattern { pattern } + | BindingPatternTypeArray::BindingPatternRest { pattern } => { + if pattern.contains(symbol) { + return true; + } + } + _ => {} + } + } + } + } + false + } } /// `DeclarationPatternObject` represents an object binding pattern. diff --git a/boa_engine/src/syntax/ast/node/field/get_super_field/mod.rs b/boa_engine/src/syntax/ast/node/field/get_super_field/mod.rs new file mode 100644 index 0000000000..2d134e1dcb --- /dev/null +++ b/boa_engine/src/syntax/ast/node/field/get_super_field/mod.rs @@ -0,0 +1,47 @@ +use crate::syntax::ast::node::Node; +use boa_interner::{Interner, Sym, ToInternedString}; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The `super` keyword is used to access fields on an object's parent. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-SuperProperty +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq)] +pub enum GetSuperField { + Const(Sym), + Expr(Box), +} + +impl From for GetSuperField { + fn from(field: Sym) -> Self { + Self::Const(field) + } +} + +impl From for GetSuperField { + fn from(field: Node) -> Self { + Self::Expr(Box::new(field)) + } +} + +impl ToInternedString for GetSuperField { + fn to_interned_string(&self, interner: &Interner) -> String { + match self { + GetSuperField::Const(field) => format!("super.{}", interner.resolve_expect(*field)), + GetSuperField::Expr(field) => format!("super[{}]", field.to_interned_string(interner)), + } + } +} + +impl From for Node { + fn from(get_super_field: GetSuperField) -> Self { + Self::GetSuperField(get_super_field) + } +} diff --git a/boa_engine/src/syntax/ast/node/field/mod.rs b/boa_engine/src/syntax/ast/node/field/mod.rs index a42a8d0103..b6da8ed775 100644 --- a/boa_engine/src/syntax/ast/node/field/mod.rs +++ b/boa_engine/src/syntax/ast/node/field/mod.rs @@ -3,8 +3,12 @@ pub mod get_const_field; pub mod get_field; pub mod get_private_field; +pub mod get_super_field; -pub use self::{get_const_field::GetConstField, get_field::GetField}; +pub use self::{ + get_const_field::GetConstField, get_field::GetField, get_private_field::GetPrivateField, + get_super_field::GetSuperField, +}; #[cfg(test)] mod tests; diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index 58b22b1801..6e9c303251 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -17,6 +17,7 @@ pub mod operator; pub mod return_smt; pub mod spread; pub mod statement_list; +pub mod super_call; pub mod switch; pub mod template; pub mod throw; @@ -35,7 +36,7 @@ pub use self::{ ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, DeclarationPattern, FunctionDecl, FunctionExpr, }, - field::{get_private_field::GetPrivateField, GetConstField, GetField}, + field::{get_private_field::GetPrivateField, GetConstField, GetField, GetSuperField}, identifier::Identifier, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, new::New, @@ -46,6 +47,7 @@ pub use self::{ return_smt::Return, spread::Spread, statement_list::StatementList, + super_call::SuperCall, switch::{Case, Switch}, template::{TaggedTemplate, TemplateLit}, throw::Throw, @@ -55,6 +57,7 @@ use self::{ declaration::class_decl::ClassElement, iteration::IterableLoopInitializer, object::{MethodDefinition, PropertyDefinition}, + operator::assign::AssignTarget, }; pub(crate) use self::parameters::FormalParameterListFlags; @@ -146,6 +149,9 @@ pub enum Node { /// Provides access to object fields. [More information](./declaration/struct.GetField.html). GetField(GetField), + /// Provides access to super fields. [More information](./declaration/struct.GetSuperField.html). + GetSuperField(GetSuperField), + /// A `for` statement. [More information](./iteration/struct.ForLoop.html). ForLoop(ForLoop), @@ -240,6 +246,9 @@ pub enum Node { /// A class declaration. [More information](./declaration/struct.class_decl.Class.html). ClassExpr(Class), + + /// A call of the super constructor. [More information](./super_call/struct.SuperCall.html). + SuperCall(SuperCall), } impl From for Node { @@ -314,6 +323,9 @@ impl Node { get_private_field.to_interned_string(interner) } Self::GetField(ref get_field) => get_field.to_interned_string(interner), + Self::GetSuperField(ref get_super_field) => { + get_super_field.to_interned_string(interner) + } Self::WhileLoop(ref while_loop) => while_loop.to_indented_string(interner, indentation), Self::DoWhileLoop(ref do_while) => do_while.to_indented_string(interner, indentation), Self::If(ref if_smt) => if_smt.to_indented_string(interner, indentation), @@ -345,6 +357,7 @@ impl Node { Self::AsyncGeneratorDecl(ref decl) => decl.to_indented_string(interner, indentation), Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation), Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation), + Self::SuperCall(ref super_call) => super_call.to_interned_string(interner), } } @@ -880,6 +893,361 @@ impl Node { } false } + + /// Returns `true` if the node contains the given token. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains + pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool { + match self { + Node::ArrayDecl(array) => { + for node in array.as_ref() { + if node.contains(symbol) { + return true; + } + } + } + Node::Assign(assign) => { + match assign.lhs() { + AssignTarget::GetPrivateField(field) => { + if field.obj().contains(symbol) { + return true; + } + } + AssignTarget::GetConstField(field) => { + if field.obj().contains(symbol) { + return true; + } + } + AssignTarget::GetField(field) => { + if field.obj().contains(symbol) || field.field().contains(symbol) { + return true; + } + } + AssignTarget::DeclarationPattern(pattern) => { + if pattern.contains(symbol) { + return true; + } + } + AssignTarget::Identifier(_) => {} + } + if assign.rhs().contains(symbol) { + return true; + } + } + Node::AwaitExpr(expr) => { + if expr.expr().contains(symbol) { + return true; + } + } + Node::BinOp(bin_op) => { + if bin_op.lhs().contains(symbol) || bin_op.rhs().contains(symbol) { + return true; + } + } + Node::Block(block) => { + for node in block.items() { + if node.contains(symbol) { + return true; + } + } + } + Node::Call(call) => { + if call.expr().contains(symbol) { + return true; + } + for node in call.args() { + if node.contains(symbol) { + return true; + } + } + } + Node::ConditionalOp(conditional) => { + if conditional.cond().contains(symbol) + || conditional.if_true().contains(symbol) + || conditional.if_false().contains(symbol) + { + return true; + } + } + Node::ConstDeclList(decl_list) + | Node::LetDeclList(decl_list) + | Node::VarDeclList(decl_list) => match decl_list { + DeclarationList::Const(declarations) + | DeclarationList::Let(declarations) + | DeclarationList::Var(declarations) => { + for declaration in declarations.iter() { + if declaration.contains(symbol) { + return true; + } + } + } + }, + Node::DoWhileLoop(do_while_loop) => { + if do_while_loop.cond().contains(symbol) || do_while_loop.body().contains(symbol) { + return true; + } + } + Node::GetConstField(field) => { + if field.obj().contains(symbol) { + return true; + } + } + Node::GetPrivateField(field) => { + if field.obj().contains(symbol) { + return true; + } + } + Node::GetField(field) => { + if field.obj().contains(symbol) || field.field().contains(symbol) { + return true; + } + } + Node::ForLoop(for_loop) => { + if let Some(node) = for_loop.init() { + if node.contains(symbol) { + return true; + } + } + if let Some(node) = for_loop.condition() { + if node.contains(symbol) { + return true; + } + } + if let Some(node) = for_loop.final_expr() { + if node.contains(symbol) { + return true; + } + } + if for_loop.body().contains(symbol) { + return true; + } + } + Node::ForInLoop(for_in_loop) => { + match for_in_loop.init() { + IterableLoopInitializer::Var(declaration) + | IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => { + if declaration.contains(symbol) { + return true; + } + } + IterableLoopInitializer::DeclarationPattern(pattern) => { + if pattern.contains(symbol) { + return true; + } + } + IterableLoopInitializer::Identifier(_) => {} + } + if for_in_loop.body().contains(symbol) || for_in_loop.expr().contains(symbol) { + return true; + } + } + Node::ForOfLoop(for_of_loop) => { + match for_of_loop.init() { + IterableLoopInitializer::Var(declaration) + | IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => { + if declaration.contains(symbol) { + return true; + } + } + IterableLoopInitializer::DeclarationPattern(pattern) => { + if pattern.contains(symbol) { + return true; + } + } + IterableLoopInitializer::Identifier(_) => {} + } + if for_of_loop.body().contains(symbol) || for_of_loop.iterable().contains(symbol) { + return true; + } + } + Node::If(if_node) => { + if if_node.cond().contains(symbol) || if_node.body().contains(symbol) { + return true; + } + if let Some(node) = if_node.else_node() { + if node.contains(symbol) { + return true; + } + } + } + Node::New(new) => { + if new.call().expr().contains(symbol) { + return true; + } + for node in new.call().args() { + if node.contains(symbol) { + return true; + } + } + } + Node::Return(expr) => { + if let Some(expr) = expr.expr() { + if expr.contains(symbol) { + return true; + } + } + } + Node::Switch(switch) => { + if switch.val().contains(symbol) { + return true; + } + for case in switch.cases() { + if case.condition().contains(symbol) { + return true; + } + for node in case.body().items() { + if node.contains(symbol) { + return true; + } + } + } + if let Some(default) = switch.default() { + for node in default { + if node.contains(symbol) { + return true; + } + } + } + } + Node::Spread(spread) => { + if spread.val().contains(symbol) { + return true; + } + } + Node::TaggedTemplate(template) => { + if template.tag().contains(symbol) { + return true; + } + for node in template.exprs() { + if node.contains(symbol) { + return true; + } + } + } + Node::TemplateLit(template) => { + for element in template.elements() { + if let template::TemplateElement::Expr(node) = element { + if node.contains(symbol) { + return true; + } + } + } + } + Node::Throw(expr) => { + if expr.expr().contains(symbol) { + return true; + } + } + Node::Try(try_node) => { + for node in try_node.block().items() { + if node.contains(symbol) { + return true; + } + } + if let Some(catch) = try_node.catch() { + if let Some(declaration) = catch.parameter() { + if declaration.contains(symbol) { + return true; + } + } + for node in catch.block().items() { + if node.contains(symbol) { + return true; + } + } + } + } + Node::UnaryOp(unary) => { + if unary.target().contains(symbol) { + return true; + } + } + Node::WhileLoop(while_loop) => { + if while_loop.cond().contains(symbol) || while_loop.body().contains(symbol) { + return true; + } + } + Node::SuperCall(_) if symbol == ContainsSymbol::SuperCall => return true, + Node::GetSuperField(_) if symbol == ContainsSymbol::SuperProperty => return true, + Node::ArrowFunctionDecl(arrow) => { + for parameter in arrow.params().parameters.iter() { + if parameter.declaration().contains(symbol) { + return true; + } + } + for node in arrow.body().items() { + if node.contains(symbol) { + return true; + } + } + } + Node::Object(object) => { + for property in object.properties() { + match property { + PropertyDefinition::IdentifierReference(_) => {} + PropertyDefinition::Property(name, init) => { + if let Some(node) = name.computed() { + if node.contains(symbol) { + return true; + } + } + if init.contains(symbol) { + return true; + } + } + PropertyDefinition::SpreadObject(spread) => { + if spread.contains(symbol) { + return true; + } + } + PropertyDefinition::MethodDefinition(_, name) => { + if let Some(node) = name.computed() { + if node.contains(symbol) { + return true; + } + } + } + } + } + } + Node::ClassDecl(class) | Node::ClassExpr(class) => { + if let Some(node) = class.super_ref() { + if node.contains(symbol) { + return true; + } + } + for element in class.elements() { + match element { + ClassElement::MethodDefinition(name, _) + | ClassElement::StaticMethodDefinition(name, _) + | ClassElement::FieldDefinition(name, _) + | ClassElement::StaticFieldDefinition(name, _) => { + if let Some(node) = name.computed() { + if node.contains(symbol) { + return true; + } + } + } + _ => {} + } + } + } + _ => {} + } + false + } +} + +/// Represents the possible symbols that can be use the the `Node.contains` function. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum ContainsSymbol { + SuperProperty, + SuperCall, } impl ToInternedString for Node { @@ -946,3 +1314,44 @@ fn test_formatting(source: &'static str) { panic!("parsing test did not give the correct result (see above)"); } } + +/// Helper function to check if a function contains a super call or super property access. +pub(crate) fn function_contains_super( + body: &StatementList, + parameters: &FormalParameterList, +) -> bool { + for param in parameters.parameters.iter() { + if param.declaration().contains(ContainsSymbol::SuperCall) + || param.declaration().contains(ContainsSymbol::SuperProperty) + { + return true; + } + } + for node in body.items() { + if node.contains(ContainsSymbol::SuperCall) || node.contains(ContainsSymbol::SuperProperty) + { + return true; + } + } + false +} + +/// Returns `true` if the function parameters or body contain a direct `super` call. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper +pub(crate) fn has_direct_super(body: &StatementList, parameters: &FormalParameterList) -> bool { + for param in parameters.parameters.iter() { + if param.declaration().contains(ContainsSymbol::SuperCall) { + return true; + } + } + for node in body.items() { + if node.contains(ContainsSymbol::SuperCall) { + return true; + } + } + false +} diff --git a/boa_engine/src/syntax/ast/node/object/mod.rs b/boa_engine/src/syntax/ast/node/object/mod.rs index 5ff999c0ee..89f91e8da9 100644 --- a/boa_engine/src/syntax/ast/node/object/mod.rs +++ b/boa_engine/src/syntax/ast/node/object/mod.rs @@ -3,7 +3,7 @@ use crate::syntax::ast::{ node::{ declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr, - FunctionExpr, GeneratorExpr, Node, + FormalParameterList, FunctionExpr, GeneratorExpr, Node, StatementList, }, Const, }; @@ -321,6 +321,32 @@ pub enum MethodDefinition { Async(AsyncFunctionExpr), } +impl MethodDefinition { + /// Return the body of the method. + pub(crate) fn body(&self) -> &StatementList { + match self { + MethodDefinition::Get(expr) + | MethodDefinition::Set(expr) + | MethodDefinition::Ordinary(expr) => expr.body(), + MethodDefinition::Generator(expr) => expr.body(), + MethodDefinition::AsyncGenerator(expr) => expr.body(), + MethodDefinition::Async(expr) => expr.body(), + } + } + + /// Return the parameters of the method. + pub(crate) fn parameters(&self) -> &FormalParameterList { + match self { + MethodDefinition::Get(expr) + | MethodDefinition::Set(expr) + | MethodDefinition::Ordinary(expr) => expr.parameters(), + MethodDefinition::Generator(expr) => expr.parameters(), + MethodDefinition::AsyncGenerator(expr) => expr.parameters(), + MethodDefinition::Async(expr) => expr.parameters(), + } + } +} + /// `PropertyName` can be either a literal or computed. /// /// More information: @@ -348,6 +374,7 @@ pub enum PropertyName { } impl PropertyName { + /// Returns the literal property name if it exists. pub(in crate::syntax) fn literal(&self) -> Option { if let Self::Literal(sym) = self { Some(*sym) @@ -356,6 +383,16 @@ impl PropertyName { } } + /// Returns the expression node if the property name is computed. + pub(in crate::syntax) fn computed(&self) -> Option<&Node> { + if let Self::Computed(node) = self { + Some(node) + } else { + None + } + } + + /// Returns either the literal property name or the computed const string property name. pub(in crate::syntax) fn prop_name(&self) -> Option { match self { PropertyName::Literal(sym) diff --git a/boa_engine/src/syntax/ast/node/super_call/mod.rs b/boa_engine/src/syntax/ast/node/super_call/mod.rs new file mode 100644 index 0000000000..3167663bb1 --- /dev/null +++ b/boa_engine/src/syntax/ast/node/super_call/mod.rs @@ -0,0 +1,46 @@ +use crate::syntax::ast::node::{join_nodes, Node}; +use boa_interner::{Interner, ToInternedString}; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// The `super` keyword is used to access and call functions on an object's parent. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-SuperCall +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq)] +pub struct SuperCall { + args: Box<[Node]>, +} + +impl SuperCall { + /// Creates a new `SuperCall` AST node. + pub(crate) fn new(args: A) -> Self + where + A: Into>, + { + Self { args: args.into() } + } + + /// Retrieves the arguments of the super call. + pub(crate) fn args(&self) -> &[Node] { + &self.args + } +} + +impl ToInternedString for SuperCall { + fn to_interned_string(&self, interner: &Interner) -> String { + format!("super({})", join_nodes(interner, &self.args)) + } +} + +impl From for Node { + fn from(call: SuperCall) -> Self { + Self::SuperCall(call) + } +} diff --git a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs b/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs index 306ceee712..dd59bbb903 100644 --- a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs @@ -10,7 +10,7 @@ use crate::syntax::{ ast::{ node::{ field::{get_private_field::GetPrivateField, GetConstField, GetField}, - Call, New, Node, + Call, GetSuperField, New, Node, }, Keyword, Punctuator, }, @@ -66,7 +66,7 @@ where let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let mut lhs = match token.kind() { - TokenKind::Keyword((Keyword::New, true)) => { + TokenKind::Keyword((Keyword::New | Keyword::Super, true)) => { return Err(ParseError::general( "keyword must not contain escaped characters", token.span().start(), @@ -86,6 +86,55 @@ where Node::from(New::from(call_node)) } + TokenKind::Keyword((Keyword::Super, _)) => { + cursor.next(interner).expect("token disappeared"); + let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::Dot) => { + let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?; + let field = match token.kind() { + TokenKind::Identifier(name) => GetSuperField::from(*name), + TokenKind::Keyword((kw, _)) => GetSuperField::from(kw.to_sym(interner)), + TokenKind::BooleanLiteral(true) => { + GetSuperField::from(Keyword::True.to_sym(interner)) + } + TokenKind::BooleanLiteral(false) => { + GetSuperField::from(Keyword::False.to_sym(interner)) + } + TokenKind::NullLiteral => { + GetSuperField::from(Keyword::Null.to_sym(interner)) + } + TokenKind::PrivateIdentifier(_) => { + return Err(ParseError::general( + "unexpected private identifier", + token.span().start(), + )); + } + _ => { + return Err(ParseError::unexpected( + token.to_string(interner), + token.span(), + "expected super property", + )) + } + }; + field.into() + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let expr = Expression::new(None, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + cursor.expect(Punctuator::CloseBracket, "super property", interner)?; + GetSuperField::from(expr).into() + } + _ => { + return Err(ParseError::unexpected( + token.to_string(interner), + token.span(), + "expected super property", + )) + } + } + } _ => PrimaryExpression::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner)?, }; diff --git a/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs b/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs index 4bfa37e501..7d54bfc7f6 100644 --- a/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs +++ b/boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs @@ -12,11 +12,15 @@ mod call; mod member; mod template; -use self::{call::CallExpression, member::MemberExpression}; use crate::syntax::{ - ast::{Node, Punctuator}, + ast::{node::SuperCall, Keyword, Node, Punctuator}, lexer::{InputElement, TokenKind}, - parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser}, + parser::{ + expression::left_hand_side::{ + arguments::Arguments, call::CallExpression, member::MemberExpression, + }, + AllowAwait, AllowYield, Cursor, ParseResult, TokenParser, + }, }; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; @@ -64,6 +68,19 @@ where cursor.set_goal(InputElement::TemplateTail); + if let Some(next) = cursor.peek(0, interner)? { + if let TokenKind::Keyword((Keyword::Super, _)) = next.kind() { + if let Some(next) = cursor.peek(1, interner)? { + if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { + cursor.next(interner).expect("token disappeared"); + let args = Arguments::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + return Ok(SuperCall::new(args).into()); + } + } + } + } + // TODO: Implement NewExpression: new MemberExpression let lhs = MemberExpression::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner)?; diff --git a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs index e14924d67a..69baeae540 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs @@ -2,7 +2,10 @@ mod tests; use crate::syntax::{ - ast::{node::AsyncFunctionExpr, Keyword, Position, Punctuator}, + ast::{ + node::{function_contains_super, AsyncFunctionExpr}, + Keyword, Position, Punctuator, + }, lexer::{Error as LexError, TokenKind}, parser::{ expression::BindingIdentifier, @@ -132,6 +135,13 @@ where params_start_position, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + params_start_position, + ))); + } + Ok(AsyncFunctionExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs index ea1c63d569..ea76e44441 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs @@ -11,7 +11,10 @@ mod tests; use crate::syntax::{ - ast::{node::AsyncGeneratorExpr, Keyword, Position, Punctuator}, + ast::{ + node::{function_contains_super, AsyncGeneratorExpr}, + Keyword, Position, Punctuator, + }, lexer::{Error as LexError, TokenKind}, parser::{ expression::BindingIdentifier, @@ -148,6 +151,13 @@ where params_start_position, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + params_start_position, + ))); + } + //implement the below AsyncGeneratorExpr in ast::node Ok(AsyncGeneratorExpr::new(name, params, body)) } diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs index c2178aa16f..3769e68589 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -11,7 +11,10 @@ mod tests; use crate::syntax::{ - ast::{node::FunctionExpr, Keyword, Position, Punctuator}, + ast::{ + node::{function_contains_super, FunctionExpr}, + Keyword, Position, Punctuator, + }, lexer::{Error as LexError, TokenKind}, parser::{ expression::BindingIdentifier, @@ -124,6 +127,13 @@ where params_start_position, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + params_start_position, + ))); + } + Ok(FunctionExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs index 35b37fc4c3..3187dd7c3a 100644 --- a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs @@ -11,7 +11,10 @@ mod tests; use crate::syntax::{ - ast::{node::GeneratorExpr, Position, Punctuator}, + ast::{ + node::{function_contains_super, GeneratorExpr}, + Position, Punctuator, + }, lexer::{Error as LexError, TokenKind}, parser::{ expression::BindingIdentifier, @@ -128,6 +131,13 @@ where params_start_position, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + params_start_position, + ))); + } + Ok(GeneratorExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 3baef73835..2008e4a174 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -13,6 +13,7 @@ mod tests; use crate::syntax::{ ast::{ node::{ + function_contains_super, has_direct_super, object::{self, MethodDefinition}, AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr, GeneratorExpr, Node, Object, @@ -173,10 +174,18 @@ where cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let position = token.span().start(); + if let TokenKind::Punctuator(Punctuator::Mul) = token.kind() { let (property_name, method) = AsyncGeneratorMethod::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; + + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::general("invalid super usage", position)); + } + return Ok(object::PropertyDefinition::method_definition( method, property_name, @@ -184,6 +193,12 @@ where } let (property_name, method) = AsyncMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::general("invalid super usage", position)); + } + return Ok(object::PropertyDefinition::method_definition( method, property_name, @@ -206,6 +221,11 @@ where let (class_element_name, method) = GeneratorMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::general("invalid super usage", position)); + } + match class_element_name { object::ClassElementName::PropertyName(property_name) => { return Ok(object::PropertyDefinition::method_definition( @@ -241,6 +261,12 @@ where match property_name { // MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] } object::PropertyName::Literal(str) if str == Sym::GET && !ordinary_method => { + let position = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); + property_name = PropertyName::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; @@ -267,6 +293,11 @@ where interner, )?; + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(&body, &FormalParameterList::default()) { + return Err(ParseError::general("invalid super usage", position)); + } + Ok(object::PropertyDefinition::method_definition( MethodDefinition::Get(FunctionExpr::new( None, @@ -320,6 +351,14 @@ where ))); } + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(&body, ¶meters) { + return Err(ParseError::general( + "invalid super usage", + params_start_position, + )); + } + Ok(object::PropertyDefinition::method_definition( MethodDefinition::Set(FunctionExpr::new(None, parameters, body)), property_name, @@ -391,6 +430,14 @@ where } } + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(&body, ¶ms) { + return Err(ParseError::general( + "invalid super usage", + params_start_position, + )); + } + Ok(object::PropertyDefinition::method_definition( MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)), property_name, @@ -655,6 +702,13 @@ where body_start, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + body_start, + ))); + } + Ok(( class_element_name, MethodDefinition::Generator(GeneratorExpr::new(None, params, body)), @@ -742,6 +796,13 @@ where body_start, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + body_start, + ))); + } + Ok(( property_name, MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)), @@ -824,6 +885,13 @@ where body_start, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + body_start, + ))); + } + Ok(( property_name, MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)), diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index ad05094ce1..03091989b8 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/boa_engine/src/syntax/parser/mod.rs @@ -14,7 +14,7 @@ mod tests; use crate::{ syntax::{ ast::{ - node::{FormalParameterList, StatementList}, + node::{ContainsSymbol, FormalParameterList, StatementList}, Position, }, lexer::TokenKind, @@ -135,65 +135,65 @@ impl Parser { where R: Read, { - let statement_list = Script.parse(&mut self.cursor, context.interner_mut())?; - - // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. - // It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody. - let mut var_declared_names = FxHashSet::default(); - statement_list.var_declared_names_new(&mut var_declared_names); - let lexically_declared_names = statement_list.lexically_declared_names(); - let mut lexically_declared_names_map: FxHashMap = FxHashMap::default(); - for (name, is_function_declaration) in &lexically_declared_names { - if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) { - if !(*is_function_declaration && *existing_is_function_declaration) { - return Err(ParseError::general( - "lexical name declared multiple times", - Position::new(1, 1), - )); + Script::new(false).parse(&mut self.cursor, context) + } + + pub(crate) fn parse_eval( + &mut self, + direct: bool, + context: &mut Context, + ) -> Result + where + R: Read, + { + let (in_method, in_derived_constructor) = if let Some(function_env) = context + .realm + .environments + .get_this_environment() + .as_function_slots() + { + let function_env_borrow = function_env.borrow(); + let has_super_binding = function_env_borrow.has_super_binding(); + let function_object = function_env_borrow.function_object().borrow(); + ( + has_super_binding, + function_object + .as_function() + .expect("must be function object") + .is_derived_constructor(), + ) + } else { + (false, false) + }; + + let statement_list = Script::new(direct).parse(&mut self.cursor, context)?; + + let mut contains_super_property = false; + let mut contains_super_call = false; + if direct { + for node in statement_list.items() { + if !contains_super_property && node.contains(ContainsSymbol::SuperProperty) { + contains_super_property = true; } - } - lexically_declared_names_map.insert(*name, *is_function_declaration); - if !is_function_declaration && var_declared_names.contains(name) { - return Err(ParseError::general( - "lexical name declared in var names", - Position::new(1, 1), - )); - } - if context.has_binding(*name) { - return Err(ParseError::general( - "lexical name declared multiple times", - Position::new(1, 1), - )); - } - if !is_function_declaration { - let name_str = context.interner().resolve_expect(*name); - let desc = context - .realm - .global_property_map - .string_property_map() - .get(name_str); - let non_configurable_binding_exists = match desc { - Some(desc) => !matches!(desc.configurable(), Some(true)), - None => false, - }; - if non_configurable_binding_exists { - return Err(ParseError::general( - "lexical name declared in var names", - Position::new(1, 1), - )); + if !contains_super_call && node.contains(ContainsSymbol::SuperCall) { + contains_super_call = true; } } } - for name in var_declared_names { - if context.has_binding(name) { - return Err(ParseError::general( - "lexical name declared in var names", - Position::new(1, 1), - )); - } - } + if !in_method && contains_super_property { + return Err(ParseError::general( + "invalid super usage", + Position::new(1, 1), + )); + } + if !in_derived_constructor && contains_super_call { + return Err(ParseError::general( + "invalid super usage", + Position::new(1, 1), + )); + } Ok(statement_list) } @@ -235,34 +235,97 @@ impl Parser { /// /// [spec]: https://tc39.es/ecma262/#prod-Script #[derive(Debug, Clone, Copy)] -pub struct Script; +pub struct Script { + direct_eval: bool, +} -impl TokenParser for Script -where - R: Read, -{ - type Output = StatementList; +impl Script { + /// Create a new `Script` parser. + fn new(direct_eval: bool) -> Self { + Self { direct_eval } + } - fn parse( + fn parse( self, cursor: &mut Cursor, - interner: &mut Interner, - ) -> Result { + context: &mut Context, + ) -> Result { let mut strict = cursor.strict_mode(); - match cursor.peek(0, interner)? { + match cursor.peek(0, context.interner_mut())? { Some(tok) => { match tok.kind() { // Set the strict mode TokenKind::StringLiteral(string) - if interner.resolve_expect(*string) == "use strict" => + if context.interner_mut().resolve_expect(*string) == "use strict" => { cursor.set_strict_mode(true); strict = true; } _ => {} } - let mut statement_list = ScriptBody.parse(cursor, interner)?; + let mut statement_list = + ScriptBody::new(self.direct_eval).parse(cursor, context.interner_mut())?; statement_list.set_strict(strict); + + // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. + // It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody. + let mut var_declared_names = FxHashSet::default(); + statement_list.var_declared_names_new(&mut var_declared_names); + let lexically_declared_names = statement_list.lexically_declared_names(); + let mut lexically_declared_names_map: FxHashMap = FxHashMap::default(); + for (name, is_function_declaration) in &lexically_declared_names { + if let Some(existing_is_function_declaration) = + lexically_declared_names_map.get(name) + { + if !(*is_function_declaration && *existing_is_function_declaration) { + return Err(ParseError::general( + "lexical name declared multiple times", + Position::new(1, 1), + )); + } + } + lexically_declared_names_map.insert(*name, *is_function_declaration); + + if !is_function_declaration && var_declared_names.contains(name) { + return Err(ParseError::general( + "lexical name declared in var names", + Position::new(1, 1), + )); + } + if context.has_binding(*name) { + return Err(ParseError::general( + "lexical name declared multiple times", + Position::new(1, 1), + )); + } + if !is_function_declaration { + let name_str = context.interner().resolve_expect(*name); + let desc = context + .realm + .global_property_map + .string_property_map() + .get(name_str); + let non_configurable_binding_exists = match desc { + Some(desc) => !matches!(desc.configurable(), Some(true)), + None => false, + }; + if non_configurable_binding_exists { + return Err(ParseError::general( + "lexical name declared in var names", + Position::new(1, 1), + )); + } + } + } + for name in var_declared_names { + if context.has_binding(name) { + return Err(ParseError::general( + "lexical name declared in var names", + Position::new(1, 1), + )); + } + } + Ok(statement_list) } None => Ok(StatementList::from(Vec::new())), @@ -277,7 +340,16 @@ where /// /// [spec]: https://tc39.es/ecma262/#prod-ScriptBody #[derive(Debug, Clone, Copy)] -pub struct ScriptBody; +pub struct ScriptBody { + direct_eval: bool, +} + +impl ScriptBody { + /// Create a new `ScriptBody` parser. + fn new(direct_eval: bool) -> Self { + Self { direct_eval } + } +} impl TokenParser for ScriptBody where @@ -290,6 +362,24 @@ where cursor: &mut Cursor, interner: &mut Interner, ) -> Result { - self::statement::StatementList::new(false, false, false, &[]).parse(cursor, interner) + let body = self::statement::StatementList::new(false, false, false, &[]) + .parse(cursor, interner)?; + + // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval code that is being processed by a direct eval. + // Additional early error rules for super within direct eval are defined in 19.2.1.1. + if !self.direct_eval { + for node in body.items() { + if node.contains(ContainsSymbol::SuperCall) + || node.contains(ContainsSymbol::SuperProperty) + { + return Err(ParseError::general( + "invalid super usage", + Position::new(1, 1), + )); + } + } + } + + Ok(body) } } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs index 53e46c46ee..cbcd2c542c 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -3,8 +3,9 @@ use crate::syntax::{ node::{ self, declaration::class_decl::ClassElement as ClassElementNode, + function_contains_super, has_direct_super, object::{MethodDefinition, PropertyName::Literal}, - Class, FormalParameterList, FunctionExpr, + Class, ContainsSymbol, FormalParameterList, FunctionExpr, }, Keyword, Punctuator, }, @@ -165,10 +166,27 @@ where cursor.next(interner).expect("token disappeared"); Ok(Class::new(self.name, super_ref, None, vec![])) } else { + let body_start = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); let (constructor, elements) = ClassBody::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner)?; cursor.expect(Punctuator::CloseBlock, "class tail", interner)?; + + if super_ref.is_none() { + if let Some(constructor) = &constructor { + if function_contains_super(constructor.body(), constructor.parameters()) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + body_start, + ))); + } + } + } + Ok(Class::new(self.name, super_ref, constructor, elements)) } } @@ -295,57 +313,72 @@ where } (None, Some(element)) => { match &element { - ClassElementNode::PrivateMethodDefinition(name, method) => match method - { - MethodDefinition::Get(_) => { - match private_elements_names.get(name) { - Some(PrivateElement::Setter) => { - private_elements_names - .insert(*name, PrivateElement::Value); - } - Some(_) => { - return Err(ParseError::general( - "private identifier has already been declared", - position, - )); - } - None => { - private_elements_names - .insert(*name, PrivateElement::Getter); + ClassElementNode::PrivateMethodDefinition(name, method) => { + // It is a Syntax Error if PropName of MethodDefinition is not "constructor" and HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } + match method { + MethodDefinition::Get(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::Setter) => { + private_elements_names + .insert(*name, PrivateElement::Value); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::Getter); + } } } - } - MethodDefinition::Set(_) => { - match private_elements_names.get(name) { - Some(PrivateElement::Getter) => { - private_elements_names - .insert(*name, PrivateElement::Value); + MethodDefinition::Set(_) => { + match private_elements_names.get(name) { + Some(PrivateElement::Getter) => { + private_elements_names + .insert(*name, PrivateElement::Value); + } + Some(_) => { + return Err(ParseError::general( + "private identifier has already been declared", + position, + )); + } + None => { + private_elements_names + .insert(*name, PrivateElement::Setter); + } } - Some(_) => { + } + _ => { + if private_elements_names + .insert(*name, PrivateElement::Value) + .is_some() + { return Err(ParseError::general( "private identifier has already been declared", position, )); } - None => { - private_elements_names - .insert(*name, PrivateElement::Setter); - } - } - } - _ => { - if private_elements_names - .insert(*name, PrivateElement::Value) - .is_some() - { - return Err(ParseError::general( - "private identifier has already been declared", - position, - )); } } - }, + } ClassElementNode::PrivateStaticMethodDefinition(name, method) => { + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } match method { MethodDefinition::Get(_) => { match private_elements_names.get(name) { @@ -396,7 +429,15 @@ where } } } - ClassElementNode::PrivateFieldDefinition(name, _) => { + ClassElementNode::PrivateFieldDefinition(name, init) => { + if let Some(node) = init { + if node.contains(node::ContainsSymbol::SuperCall) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } + } if private_elements_names .insert(*name, PrivateElement::Value) .is_some() @@ -407,7 +448,15 @@ where )); } } - ClassElementNode::PrivateStaticFieldDefinition(name, _) => { + ClassElementNode::PrivateStaticFieldDefinition(name, init) => { + if let Some(node) = init { + if node.contains(node::ContainsSymbol::SuperCall) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } + } if private_elements_names .insert(*name, PrivateElement::StaticValue) .is_some() @@ -418,6 +467,28 @@ where )); } } + ClassElementNode::MethodDefinition(_, method) + | ClassElementNode::StaticMethodDefinition(_, method) => { + // ClassElement : MethodDefinition: + // It is a Syntax Error if PropName of MethodDefinition is not "constructor" and HasDirectSuper of MethodDefinition is true. + // ClassElement : static MethodDefinition: + // It is a Syntax Error if HasDirectSuper of MethodDefinition is true. + if has_direct_super(method.body(), method.parameters()) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } + } + ClassElementNode::FieldDefinition(_, Some(node)) + | ClassElementNode::StaticFieldDefinition(_, Some(node)) => { + if node.contains(node::ContainsSymbol::SuperCall) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + position, + ))); + } + } _ => {} } elements.push(element); @@ -1276,6 +1347,7 @@ where } // ClassStaticBlockBody : ClassStaticBlockStatementList // It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true. + // It is a Syntax Error if ClassStaticBlockStatementList Contains SuperCall is true. ClassElementNode::StaticBlock(block) => { for node in block.items() { if node.contains_arguments() { @@ -1284,6 +1356,9 @@ where position, )); } + if node.contains(ContainsSymbol::SuperCall) { + return Err(ParseError::general("invalid super usage", position)); + } } } _ => {} diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs index 8700afc544..ee9080c4a6 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -21,7 +21,7 @@ use self::{ }; use crate::syntax::{ ast::node::{FormalParameterList, StatementList}, - ast::{Keyword, Node, Position, Punctuator}, + ast::{node::function_contains_super, Keyword, Node, Position, Punctuator}, lexer::TokenKind, parser::{ expression::BindingIdentifier, @@ -213,5 +213,12 @@ fn parse_callable_declaration( params_start_position, )?; + if function_contains_super(&body, ¶ms) { + return Err(ParseError::lex(LexError::Syntax( + "invalid super usage".into(), + params_start_position, + ))); + } + Ok((name, params, body)) } diff --git a/boa_engine/src/vm/call_frame.rs b/boa_engine/src/vm/call_frame.rs index 89d02ab39b..c5ff205736 100644 --- a/boa_engine/src/vm/call_frame.rs +++ b/boa_engine/src/vm/call_frame.rs @@ -2,8 +2,7 @@ //! //! This module will provides everything needed to implement the `CallFrame` -use super::CodeBlock; -use crate::JsValue; +use crate::vm::CodeBlock; use boa_gc::{Finalize, Gc, Trace}; #[derive(Clone, Debug, Finalize, Trace)] @@ -11,7 +10,6 @@ pub struct CallFrame { pub(crate) prev: Option>, pub(crate) code: Gc, pub(crate) pc: usize, - pub(crate) this: JsValue, #[unsafe_ignore_trace] pub(crate) catch: Vec, #[unsafe_ignore_trace] diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 412d11ce74..43f57ddb44 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -5,15 +5,16 @@ use crate::{ builtins::{ function::{ - arguments::Arguments, Captures, ClosureFunctionSignature, ConstructorKind, Function, - NativeFunctionSignature, ThisMode, + arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode, }, generator::{Generator, GeneratorContext, GeneratorState}, }, context::intrinsics::StandardConstructors, - environments::{BindingLocator, CompileTimeEnvironment, DeclarativeEnvironmentStack}, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, - property::{PropertyDescriptor, PropertyKey}, + environments::{BindingLocator, CompileTimeEnvironment}, + object::{ + internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PrivateElement, + }, + property::PropertyDescriptor, syntax::ast::node::FormalParameterList, vm::call_frame::GeneratorResumeKind, vm::{call_frame::FinallyReturn, CallFrame, Opcode}, @@ -62,10 +63,6 @@ pub struct CodeBlock { /// Is this function in strict mode. pub(crate) strict: bool, - /// Constructor type of this function, or `None` if the function is not constructable. - #[unsafe_ignore_trace] - pub(crate) constructor: Option, - /// \[\[ThisMode\]\] pub(crate) this_mode: ThisMode, @@ -90,26 +87,22 @@ pub struct CodeBlock { pub(crate) num_bindings: usize, /// Functions inside this function - pub(crate) functions: Vec>, - - /// Indicates if the codeblock contains a lexical name `arguments` - pub(crate) lexical_name_argument: bool, + pub(crate) functions: Vec>, /// The `arguments` binding location of the function, if set. #[unsafe_ignore_trace] pub(crate) arguments_binding: Option, - /// Similar to the `[[ClassFieldInitializerName]]` slot in the spec. - /// Holds class field names that are computed at class declaration time. - pub(crate) computed_field_names: Option>>, - /// Compile time environments in this function. pub(crate) compile_environments: Vec>>, + + /// The `[[IsClassConstructor]]` internal slot. + pub(crate) is_class_constructor: bool, } impl CodeBlock { /// Constructs a new `CodeBlock`. - pub fn new(name: Sym, length: u32, strict: bool, constructor: Option) -> Self { + pub fn new(name: Sym, length: u32, strict: bool) -> Self { Self { code: Vec::new(), literals: Vec::new(), @@ -120,13 +113,11 @@ impl CodeBlock { name, length, strict, - constructor, this_mode: ThisMode::Global, params: FormalParameterList::default(), - lexical_name_argument: false, arguments_binding: None, - computed_field_names: None, compile_environments: Vec::new(), + is_class_constructor: false, } } @@ -202,6 +193,8 @@ impl CodeBlock { | Opcode::CallWithRest | Opcode::New | Opcode::NewWithRest + | Opcode::SuperCall + | Opcode::SuperCallWithRest | Opcode::ForInLoopInitIterator | Opcode::ForInLoopNext | Opcode::ConcatToString @@ -254,11 +247,17 @@ impl CodeBlock { | Opcode::DefineClassGetterByName | Opcode::SetPropertySetterByName | Opcode::DefineClassSetterByName - | Opcode::SetPrivateValue + | Opcode::AssignPrivateField + | Opcode::SetPrivateField + | Opcode::SetPrivateMethod | Opcode::SetPrivateSetter | Opcode::SetPrivateGetter | Opcode::GetPrivateField - | Opcode::DeletePropertyByName => { + | Opcode::DeletePropertyByName + | Opcode::PushClassFieldPrivate + | Opcode::PushClassPrivateGetter + | Opcode::PushClassPrivateSetter + | Opcode::PushClassPrivateMethod => { let operand = self.read::(*pc); *pc += size_of::(); format!( @@ -281,6 +280,8 @@ impl CodeBlock { | Opcode::PushUndefined | Opcode::PushEmptyObject | Opcode::PushClassPrototype + | Opcode::SetClassPrototype + | Opcode::SetHomeObject | Opcode::Add | Opcode::Sub | Opcode::Div @@ -331,6 +332,7 @@ impl CodeBlock { | Opcode::FinallyStart | Opcode::FinallyEnd | Opcode::This + | Opcode::Super | Opcode::Return | Opcode::PopEnvironment | Opcode::LoopStart @@ -352,7 +354,8 @@ impl CodeBlock { | Opcode::PopOnReturnSub | Opcode::Yield | Opcode::GeneratorNext - | Opcode::PushClassComputedFieldName + | Opcode::PushClassField + | Opcode::SuperCallDerived | Opcode::Nop => String::new(), } } @@ -453,6 +456,10 @@ pub(crate) fn create_function_object(code: Gc, context: &mut Context) let function = Function::Ordinary { code, environments: context.realm.environments.clone(), + constructor_kind: ConstructorKind::Base, + home_object: None, + fields: Vec::new(), + private_methods: Vec::new(), }; let constructor = @@ -547,24 +554,6 @@ pub(crate) fn create_generator_function_object( constructor } -pub(crate) enum FunctionBody { - Ordinary { - code: Gc, - environments: DeclarativeEnvironmentStack, - }, - Native { - function: NativeFunctionSignature, - }, - Closure { - function: Box, - captures: Captures, - }, - Generator { - code: Gc, - environments: DeclarativeEnvironmentStack, - }, -} - impl JsObject { pub(crate) fn call_internal( &self, @@ -578,85 +567,80 @@ impl JsObject { return context.throw_type_error("not a callable function"); } - let mut construct = false; - - let body = { - let object = self.borrow(); - let function = object.as_function().expect("not a function"); + let object = self.borrow(); + let function_object = object.as_function().expect("not a function"); - match function { - Function::Native { - function, - constructor, - } => { - if constructor.is_some() { - construct = true; - } + match function_object { + Function::Native { + function, + constructor, + } => { + let function = *function; + let constructor = *constructor; + drop(object); - FunctionBody::Native { - function: *function, - } + if constructor.is_some() { + function(&JsValue::undefined(), args, context) + } else { + function(this, args, context) } - Function::Closure { - function, captures, .. - } => FunctionBody::Closure { - function: function.clone(), - captures: captures.clone(), - }, - Function::Ordinary { code, environments } => FunctionBody::Ordinary { - code: code.clone(), - environments: environments.clone(), - }, - Function::Generator { code, environments } => FunctionBody::Generator { - code: code.clone(), - environments: environments.clone(), - }, } - }; + Function::Closure { + function, captures, .. + } => { + let function = function.clone(); + let captures = captures.clone(); + drop(object); - match body { - FunctionBody::Native { function } if construct => { - function(&JsValue::undefined(), args, context) - } - FunctionBody::Native { function } => function(this, args, context), - FunctionBody::Closure { function, captures } => { (function)(this, args, captures, context) } - FunctionBody::Ordinary { - code, - mut environments, + Function::Ordinary { + code, environments, .. } => { + let code = code.clone(); + let mut environments = environments.clone(); + drop(object); + + if code.is_class_constructor { + return context + .throw_type_error("Class constructor cannot be invoked without 'new'"); + } + std::mem::swap(&mut environments, &mut context.realm.environments); let lexical_this_mode = code.this_mode == ThisMode::Lexical; let this = if lexical_this_mode { - if let Some(this) = context.realm.environments.get_last_this() { - this - } else { - context.global_object().clone().into() - } + None } else if code.strict { - this.clone() + Some(this.clone()) } else if this.is_null_or_undefined() { - context.global_object().clone().into() + Some(context.global_object().clone().into()) } else { - this.to_object(context) - .expect("conversion cannot fail") - .into() + Some( + this.to_object(context) + .expect("conversion cannot fail") + .into(), + ) }; if code.params.has_expressions() { context.realm.environments.push_function( code.num_bindings, code.compile_environments[1].clone(), - this.clone(), + this, + self.clone(), + None, + lexical_this_mode, ); } else { context.realm.environments.push_function( code.num_bindings, code.compile_environments[0].clone(), - this.clone(), + this, + self.clone(), + None, + lexical_this_mode, ); } @@ -704,17 +688,16 @@ impl JsObject { context.vm.push_frame(CallFrame { prev: None, code, - this, pc: 0, catch: Vec::new(), finally_return: FinallyReturn::None, finally_jump: Vec::new(), pop_on_return: 0, - loop_env_stack: vec![0], - try_env_stack: vec![crate::vm::TryStackEntry { + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { num_env: 0, num_loop_stack_entries: 0, - }], + }]), param_count, arg_count, generator_resume_kind: GeneratorResumeKind::Normal, @@ -734,37 +717,46 @@ impl JsObject { let (result, _) = result?; Ok(result) } - FunctionBody::Generator { - code, - mut environments, - } => { + Function::Generator { code, environments } => { + let code = code.clone(); + let mut environments = environments.clone(); + drop(object); + std::mem::swap(&mut environments, &mut context.realm.environments); let lexical_this_mode = code.this_mode == ThisMode::Lexical; let this = if lexical_this_mode { - if let Some(this) = context.realm.environments.get_last_this() { - this - } else { - context.global_object().clone().into() - } - } else if !code.strict && this.is_null_or_undefined() { - context.global_object().clone().into() + None + } else if code.strict { + Some(this.clone()) + } else if this.is_null_or_undefined() { + Some(context.global_object().clone().into()) } else { - this.clone() + Some( + this.to_object(context) + .expect("conversion cannot fail") + .into(), + ) }; if code.params.has_expressions() { context.realm.environments.push_function( code.num_bindings, code.compile_environments[1].clone(), - this.clone(), + this, + self.clone(), + None, + lexical_this_mode, ); } else { context.realm.environments.push_function( code.num_bindings, code.compile_environments[0].clone(), - this.clone(), + this, + self.clone(), + None, + lexical_this_mode, ); } @@ -808,17 +800,16 @@ impl JsObject { let call_frame = CallFrame { prev: None, code, - this, pc: 0, catch: Vec::new(), finally_return: FinallyReturn::None, finally_jump: Vec::new(), pop_on_return: 0, - loop_env_stack: vec![0], - try_env_stack: vec![crate::vm::TryStackEntry { + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { num_env: 0, num_loop_stack_entries: 0, - }], + }]), param_count, arg_count, generator_resume_kind: GeneratorResumeKind::Normal, @@ -871,7 +862,6 @@ impl JsObject { context: &mut Context, ) -> JsResult { let this_function_object = self.clone(); - // let mut has_parameter_expressions = false; let create_this = |context| { let prototype = @@ -883,53 +873,47 @@ impl JsObject { 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 { - function: *function, - }, - Function::Closure { - function, captures, .. - } => FunctionBody::Closure { - function: function.clone(), - captures: captures.clone(), - }, - Function::Ordinary { code, environments } => FunctionBody::Ordinary { - code: code.clone(), - environments: environments.clone(), - }, - Function::Generator { .. } => { - unreachable!("generator function cannot be a constructor") - } - } - }; + let object = self.borrow(); + let function_object = object.as_function().expect("not a function"); - match body { - 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", - ) + match function_object { + Function::Native { + function, + constructor, + .. + } => { + let function = *function; + let constructor = *constructor; + drop(object); + + match function(this_target, args, context)? { + JsValue::Object(ref o) => Ok(o.clone()), + val => { + if constructor.expect("hmm").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::Closure { + function, + captures, + constructor, + .. + } => { + let function = function.clone(); + let captures = captures.clone(); + let constructor = *constructor; + drop(object); + match (function)(this_target, args, captures, context)? { JsValue::Object(ref o) => Ok(o.clone()), val => { - if constructor.is_base() || val.is_undefined() { + if constructor.expect("hmma").is_base() || val.is_undefined() { create_this(context) } else { context.throw_type_error( @@ -939,32 +923,57 @@ impl JsObject { } } } - FunctionBody::Ordinary { + Function::Ordinary { code, - mut environments, + environments, + constructor_kind, + .. } => { + let code = code.clone(); + let mut environments = environments.clone(); + let constructor_kind = *constructor_kind; + drop(object); + std::mem::swap(&mut environments, &mut context.realm.environments); - let this = create_this(context)?; + let this = if constructor_kind.is_base() { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let prototype = get_prototype_from_constructor( + this_target, + StandardConstructors::object, + context, + )?; + let this = Self::from_proto_and_data(prototype, ObjectData::ordinary()); + + initialize_instance_elements(&this, self, context)?; + + Some(this) + } else { + None + }; - // 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 new_target = this_target.as_object().expect("must be object"); if code.params.has_expressions() { context.realm.environments.push_function( code.num_bindings, code.compile_environments[1].clone(), - this.clone().into(), + this.clone().map(Into::into), + self.clone(), + Some(new_target.clone()), + false, ); } else { context.realm.environments.push_function( code.num_bindings, code.compile_environments[0].clone(), - this.clone().into(), + this.clone().map(Into::into), + self.clone(), + Some(new_target.clone()), + false, ); } @@ -1025,17 +1034,16 @@ impl JsObject { context.vm.push_frame(CallFrame { prev: None, code, - this: this.into(), pc: 0, catch: Vec::new(), finally_return: FinallyReturn::None, finally_jump: Vec::new(), pop_on_return: 0, - loop_env_stack: vec![0], - try_env_stack: vec![crate::vm::TryStackEntry { + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { num_env: 0, num_loop_stack_entries: 0, - }], + }]), param_count, arg_count, generator_resume_kind: GeneratorResumeKind::Normal, @@ -1044,37 +1052,111 @@ impl JsObject { let result = context.run(); - let frame = context.vm.pop_frame().expect("must have frame"); + context.vm.pop_frame(); - context.realm.environments.pop(); + let mut environment = context.realm.environments.pop(); if has_parameter_expressions { - context.realm.environments.pop(); + environment = context.realm.environments.pop(); } std::mem::swap(&mut environments, &mut context.realm.environments); let (result, _) = result?; - 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", - ) - } + if let Some(result) = result.as_object() { + Ok(result.clone()) + } else if let Some(this) = this { + Ok(this) + } else if !result.is_undefined() { + context.throw_type_error("Function constructor must not return non-object") + } else { + let function_env = environment + .slots() + .expect("must be function environment") + .as_function_slots() + .expect("must be function environment"); + if let Some(this_binding) = function_env.borrow().get_this_binding() { + Ok(this_binding + .as_object() + .expect("this binding must be object") + .clone()) + } else { + //context.throw_type_error("Function constructor must not return non-object") + context.throw_reference_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor") } } } - FunctionBody::Generator { .. } => { + Function::Generator { .. } => { unreachable!("generator function cannot be a constructor") } } } } + +/// `InitializeInstanceElements ( O, constructor )` +/// +/// Add private methods and fields from a class constructor to an object. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-initializeinstanceelements +pub(crate) fn initialize_instance_elements( + target: &JsObject, + constructor: &JsObject, + context: &mut Context, +) -> JsResult<()> { + let constructor_borrow = constructor.borrow(); + let constructor_function = constructor_borrow + .as_function() + .expect("class constructor must be function object"); + + for (name, private_method) in constructor_function.get_private_methods() { + match private_method { + PrivateElement::Method(_) => { + target + .borrow_mut() + .set_private_element(*name, private_method.clone()); + } + PrivateElement::Accessor { getter, setter } => { + if let Some(getter) = getter { + target + .borrow_mut() + .set_private_element_getter(*name, getter.clone()); + } + if let Some(setter) = setter { + target + .borrow_mut() + .set_private_element_setter(*name, setter.clone()); + } + } + PrivateElement::Field(_) => unreachable!(), + } + } + + for field in constructor_function.get_fields() { + match field { + ClassFieldDefinition::Public(name, function) => { + let value = function.call(&target.clone().into(), &[], context)?; + target.__define_own_property__( + name.clone(), + PropertyDescriptor::builder() + .value(value) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + } + ClassFieldDefinition::Private(name, function) => { + let value = function.call(&target.clone().into(), &[], context)?; + target + .borrow_mut() + .set_private_element(*name, PrivateElement::Field(value)); + } + } + } + + Ok(()) +} diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 84692c9185..1b52e3cf63 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -3,13 +3,18 @@ //! plus an interpreter to execute those instructions use crate::{ - builtins::{function::Function, iterable::IteratorRecord, Array, ForInIterator, Number}, - object::PrivateElement, - property::{DescriptorKind, PropertyDescriptor, PropertyKey}, + builtins::{ + function::{ConstructorKind, Function}, + iterable::IteratorRecord, + Array, ForInIterator, Number, + }, + environments::EnvironmentSlots, + object::{JsFunction, JsObject, ObjectData, PrivateElement}, + property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey}, value::Numeric, vm::{ call_frame::CatchAddresses, - code_block::{create_generator_function_object, Readable}, + code_block::{create_generator_function_object, initialize_instance_elements, Readable}, }, Context, JsBigInt, JsResult, JsString, JsValue, }; @@ -198,20 +203,103 @@ impl Context { Opcode::PushEmptyObject => self.vm.push(self.construct_object()), Opcode::PushClassPrototype => { let superclass = self.vm.pop(); - if superclass.is_null() { - self.vm.push(JsValue::Null); - } + if let Some(superclass) = superclass.as_constructor() { let proto = superclass.get("prototype", self)?; if !proto.is_object() && !proto.is_null() { return self .throw_type_error("superclass prototype must be an object or null"); } + + let class = self.vm.pop(); + { + let class_object = class.as_object().expect("class must be object"); + class_object.set_prototype(Some(superclass.clone())); + + let mut class_object_mut = class_object.borrow_mut(); + let class_function = class_object_mut + .as_function_mut() + .expect("class must be function object"); + if let Function::Ordinary { + constructor_kind, .. + } = class_function + { + *constructor_kind = ConstructorKind::Derived; + } + } + + self.vm.push(class); self.vm.push(proto); + } else if superclass.is_null() { + self.vm.push(JsValue::Null); } else { return self.throw_type_error("superclass must be a constructor"); } } + Opcode::SetClassPrototype => { + let prototype_value = self.vm.pop(); + let prototype = match &prototype_value { + JsValue::Object(proto) => Some(proto.clone()), + JsValue::Null => None, + JsValue::Undefined => { + Some(self.intrinsics().constructors().object().prototype.clone()) + } + _ => unreachable!(), + }; + + let proto = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); + let class = self.vm.pop(); + + { + let class_object = class.as_object().expect("class must be object"); + class_object + .define_property_or_throw( + "prototype", + PropertyDescriptorBuilder::new() + .value(proto.clone()) + .writable(false) + .enumerable(false) + .configurable(false), + self, + ) + .expect("cannot fail per spec"); + let mut class_object_mut = class_object.borrow_mut(); + let class_function = class_object_mut + .as_function_mut() + .expect("class must be function object"); + class_function.set_home_object(proto.clone()); + } + + proto + .__define_own_property__( + "constructor".into(), + PropertyDescriptorBuilder::new() + .value(class) + .writable(true) + .enumerable(false) + .configurable(true) + .build(), + self, + ) + .expect("cannot fail per spec"); + + self.vm.push(proto); + } + Opcode::SetHomeObject => { + let function = self.vm.pop(); + let function_object = function.as_object().expect("must be object"); + let home = self.vm.pop(); + let home_object = home.as_object().expect("must be object"); + + function_object + .borrow_mut() + .as_function_mut() + .expect("must be function object") + .set_home_object(home_object.clone()); + + self.vm.push(home); + self.vm.push(function); + } Opcode::PushNewArray => { let array = Array::array_create(0, None, self) .expect("Array creation with 0 length should never fail"); @@ -721,6 +809,13 @@ impl Context { } else { object.to_object(self)? }; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let name = self.vm.frame().code.names[index as usize]; let name = self.interner().resolve_expect(name); object.__define_own_property__( @@ -777,6 +872,13 @@ impl Context { } else { object.to_object(self)? }; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let key = key.to_property_key(self)?; object.__define_own_property__( key, @@ -817,6 +919,13 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = object.to_object(self)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let name = self.vm.frame().code.names[index as usize]; let name = self.interner().resolve_expect(name).into(); let set = object @@ -862,6 +971,13 @@ impl Context { let key = self.vm.pop(); let object = self.vm.pop(); let object = object.to_object(self)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let name = key.to_property_key(self)?; let set = object .__get_own_property__(&name, self)? @@ -907,6 +1023,13 @@ impl Context { let object = self.vm.pop(); let value = self.vm.pop(); let object = object.to_object(self)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let name = self.vm.frame().code.names[index as usize]; let name = self.interner().resolve_expect(name).into(); let get = object @@ -952,6 +1075,13 @@ impl Context { let key = self.vm.pop(); let object = self.vm.pop(); let object = object.to_object(self)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); let name = key.to_property_key(self)?; let get = object .__get_own_property__(&name, self)? @@ -969,7 +1099,41 @@ impl Context { self, )?; } - Opcode::SetPrivateValue => { + Opcode::AssignPrivateField => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let value = self.vm.pop(); + let object = self.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + match object_borrow_mut.get_private_element(name) { + Some(PrivateElement::Field(_)) => { + object_borrow_mut + .set_private_element(name, PrivateElement::Field(value)); + } + Some(PrivateElement::Method(_)) => { + return self.throw_type_error("private method is not writable"); + } + Some(PrivateElement::Accessor { + setter: Some(setter), + .. + }) => { + let setter = setter.clone(); + drop(object_borrow_mut); + setter.call(&object.clone().into(), &[value], self)?; + } + None => { + return self.throw_type_error("private field not defined"); + } + _ => { + return self.throw_type_error("private field defined without a setter"); + } + } + } else { + return self.throw_type_error("cannot set private property on non-object"); + } + } + Opcode::SetPrivateField => { let index = self.vm.read::(); let name = self.vm.frame().code.names[index as usize]; let value = self.vm.pop(); @@ -985,12 +1149,26 @@ impl Context { drop(object_borrow_mut); setter.call(&object.clone().into(), &[value], self)?; } else { - object_borrow_mut.set_private_element(name, PrivateElement::Value(value)); + object_borrow_mut.set_private_element(name, PrivateElement::Field(value)); } } else { return self.throw_type_error("cannot set private property on non-object"); } } + Opcode::SetPrivateMethod => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let value = self.vm.pop(); + let value = value.as_callable().expect("method must be callable"); + let object = self.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + object_borrow_mut + .set_private_element(name, PrivateElement::Method(value.clone())); + } else { + return self.throw_type_error("cannot set private setter on non-object"); + } + } Opcode::SetPrivateSetter => { let index = self.vm.read::(); let name = self.vm.frame().code.names[index as usize]; @@ -1025,7 +1203,8 @@ impl Context { let object_borrow_mut = object.borrow(); if let Some(element) = object_borrow_mut.get_private_element(name) { match element { - PrivateElement::Value(value) => self.vm.push(value), + PrivateElement::Field(value) => self.vm.push(value), + PrivateElement::Method(method) => self.vm.push(method.clone()), PrivateElement::Accessor { getter: Some(getter), setter: _, @@ -1046,25 +1225,111 @@ impl Context { return self.throw_type_error("cannot read private property from non-object"); } } - Opcode::PushClassComputedFieldName => { - let object = self.vm.pop(); - let value = self.vm.pop(); - let value = value.to_property_key(self)?; - let object_obj = object + Opcode::PushClassField => { + let field_function_value = self.vm.pop(); + let field_name_value = self.vm.pop(); + let class_value = self.vm.pop(); + + let field_name_key = field_name_value.to_property_key(self)?; + let field_function_object = field_function_value .as_object() - .expect("can only add field to function object"); - let object = object_obj.borrow(); - let function = object - .as_function() - .expect("can only add field to function object"); - if let Function::Ordinary { code, .. } = function { - let code_b = code - .computed_field_names - .as_ref() - .expect("class constructor must have fields"); - let mut fields_mut = code_b.borrow_mut(); - fields_mut.push(value); - } + .expect("field value must be function object"); + let mut field_function_object_borrow = field_function_object.borrow_mut(); + let field_function = field_function_object_borrow + .as_function_mut() + .expect("field value must be function object"); + let class_object = class_value + .as_object() + .expect("class must be function object"); + field_function.set_home_object(class_object.clone()); + class_object + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_field( + field_name_key, + JsFunction::from_object_unchecked(field_function_object.clone()), + ); + } + Opcode::PushClassFieldPrivate => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let field_function_value = self.vm.pop(); + let class_value = self.vm.pop(); + + let field_function_object = field_function_value + .as_object() + .expect("field value must be function object"); + let mut field_function_object_borrow = field_function_object.borrow_mut(); + let field_function = field_function_object_borrow + .as_function_mut() + .expect("field value must be function object"); + let class_object = class_value + .as_object() + .expect("class must be function object"); + field_function.set_home_object(class_object.clone()); + class_object + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_field_private( + name, + JsFunction::from_object_unchecked(field_function_object.clone()), + ); + } + Opcode::PushClassPrivateGetter => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let getter = self.vm.pop(); + let getter_object = getter.as_callable().expect("getter must be callable"); + let class = self.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method( + name, + PrivateElement::Accessor { + getter: Some(getter_object.clone()), + setter: None, + }, + ); + } + Opcode::PushClassPrivateSetter => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let setter = self.vm.pop(); + let setter_object = setter.as_callable().expect("getter must be callable"); + let class = self.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method( + name, + PrivateElement::Accessor { + getter: None, + setter: Some(setter_object.clone()), + }, + ); + } + Opcode::PushClassPrivateMethod => { + let index = self.vm.read::(); + let name = self.vm.frame().code.names[index as usize]; + let method = self.vm.pop(); + let method_object = method.as_callable().expect("method must be callable"); + let class = self.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method(name, PrivateElement::Method(method_object.clone())); } Opcode::DeletePropertyByName => { let index = self.vm.read::(); @@ -1204,8 +1469,203 @@ impl Context { .expect("finally jump must exist here") = Some(address); } Opcode::This => { - let this = self.vm.frame().this.clone(); - self.vm.push(this); + let env = self.realm.environments.get_this_environment(); + match env { + EnvironmentSlots::Function(env) => { + let env_b = env.borrow(); + if let Some(this) = env_b.get_this_binding() { + self.vm.push(this); + } else { + drop(env_b); + return self.throw_reference_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor"); + } + } + EnvironmentSlots::Global => { + let this = self.realm.global_object(); + self.vm.push(this.clone()); + } + } + } + Opcode::Super => { + let env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super access must be in a function environment"); + + let home = if env.borrow().get_this_binding().is_some() { + let env = env.borrow(); + let function_object = env.function_object().borrow(); + let function = function_object + .as_function() + .expect("must be function object"); + function.get_home_object().cloned() + } else { + return self.throw_range_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor"); + }; + + if let Some(home) = home { + if let Some(proto) = home.__get_prototype_of__(self)? { + self.vm.push(JsValue::from(proto)); + } else { + self.vm.push(JsValue::Null); + } + } else { + self.vm.push(JsValue::Null); + }; + } + Opcode::SuperCall => { + let argument_count = self.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(self.vm.pop()); + } + arguments.reverse(); + + let (new_target, active_function) = { + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(self) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return self.throw_type_error("super constructor object must be constructor"); + } + + let result = super_constructor.__construct__(&arguments, &new_target, self)?; + + initialize_instance_elements(&result, &active_function, self)?; + + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + + if !this_env.borrow_mut().bind_this_value(&result) { + return self.throw_reference_error("this already initialized"); + } + self.vm.push(result); + } + Opcode::SuperCallWithRest => { + let argument_count = self.vm.read::(); + let rest_argument = self.vm.pop(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..(argument_count - 1) { + arguments.push(self.vm.pop()); + } + arguments.reverse(); + + let iterator_record = rest_argument.get_iterator(self, None, None)?; + let mut rest_arguments = Vec::new(); + while let Some(next) = iterator_record.step(self)? { + rest_arguments.push(next.value(self)?); + } + arguments.append(&mut rest_arguments); + + let (new_target, active_function) = { + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(self) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return self.throw_type_error("super constructor object must be constructor"); + } + + let result = super_constructor.__construct__(&arguments, &new_target, self)?; + + initialize_instance_elements(&result, &active_function, self)?; + + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + + if !this_env.borrow_mut().bind_this_value(&result) { + return self.throw_reference_error("this already initialized"); + } + self.vm.push(result); + } + Opcode::SuperCallDerived => { + let argument_count = self.vm.frame().arg_count; + let mut arguments = Vec::with_capacity(argument_count); + for _ in 0..argument_count { + arguments.push(self.vm.pop()); + } + arguments.reverse(); + + let (new_target, active_function) = { + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(self) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return self.throw_type_error("super constructor object must be constructor"); + } + + let result = super_constructor.__construct__(&arguments, &new_target, self)?; + + initialize_instance_elements(&result, &active_function, self)?; + + let this_env = self + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + if !this_env.borrow_mut().bind_this_value(&result) { + return self.throw_reference_error("this already initialized"); + } + + self.vm.push(result); } Opcode::Case => { let address = self.vm.read::(); @@ -1480,20 +1940,9 @@ impl Context { let compile_environment = self.vm.frame().code.compile_environments [compile_environments_index as usize] .clone(); - - let constructor = self.vm.frame().code.constructor; - let is_lexical = self.vm.frame().code.this_mode.is_lexical(); - let this = if constructor.is_some() || !is_lexical { - self.vm.frame().this.clone() - } else { - JsValue::undefined() - }; - - self.realm.environments.push_function( - num_bindings as usize, - compile_environment, - this, - ); + self.realm + .environments + .push_function_inherit(num_bindings as usize, compile_environment); } Opcode::PopEnvironment => { self.realm.environments.pop(); diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index 917386b390..005ee91818 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -142,9 +142,23 @@ pub enum Opcode { /// /// Operands: /// - /// Stack: superclass **=>** superclass.prototype + /// Stack: class, superclass **=>** class, superclass.prototype PushClassPrototype, + /// Set the prototype of a class object. + /// + /// Operands: + /// + /// Stack: class, prototype **=>** class.prototype + SetClassPrototype, + + /// Set home object internal slot of a function object. + /// + /// Operands: + /// + /// Stack: home, function **=>** home, function + SetHomeObject, + /// Push an empty array value on the stack. /// /// Operands: @@ -624,16 +638,34 @@ pub enum Opcode { /// Stack: object, key, value **=>** DefineClassSetterByValue, - /// Set a private property by name from an object. + /// Assign the value of a private property of an object by it's name. + /// + /// Like `obj.#name = value` + /// + /// Operands: name_index: `u32` + /// + /// Stack: object, value **=>** + AssignPrivateField, + + /// Set a private property of a class constructor by it's name. /// /// Like `#name = value` /// /// Operands: name_index: `u32` /// /// Stack: object, value **=>** - SetPrivateValue, + SetPrivateField, - /// Set a private setter property by name from an object. + /// Set a private method of a class constructor by it's name. + /// + /// Like `#name() {}` + /// + /// Operands: name_index: `u32` + /// + /// Stack: object, value **=>** + SetPrivateMethod, + + /// Set a private setter property of a class constructor by it's name. /// /// Like `set #name() {}` /// @@ -642,7 +674,7 @@ pub enum Opcode { /// Stack: object, value **=>** SetPrivateSetter, - /// Set a private getter property by name from an object. + /// Set a private getter property of a class constructor by it's name. /// /// Like `get #name() {}` /// @@ -660,12 +692,40 @@ pub enum Opcode { /// Stack: object **=>** value GetPrivateField, - /// Push a computed class field name to a class constructor object. + /// Push a field to a class. /// /// Operands: /// - /// Stack: value, object **=>** - PushClassComputedFieldName, + /// Stack: class, field_name, field_function **=>** + PushClassField, + + /// Push a private field to the class. + /// + /// Operands: name_index: `u32` + /// + /// Stack: class, field_function **=>** + PushClassFieldPrivate, + + /// Push a private getter to the class. + /// + /// Operands: name_index: `u32` + /// + /// Stack: class, getter **=>** + PushClassPrivateGetter, + + /// Push a private setter to the class. + /// + /// Operands: name_index: `u32` + /// + /// Stack: class, setter **=>** + PushClassPrivateSetter, + + /// Push a private method to the class. + /// + /// Operands: name_index: `u32` + /// + /// Stack: class, method **=>** + PushClassPrivateMethod, /// Deletes a property by name of an object. /// @@ -803,6 +863,34 @@ pub enum Opcode { /// Stack: **=>** this This, + /// Pushes the current `super` value to the stack. + /// + /// Operands: + /// + /// Stack: **=>** super + Super, + + /// Execute the `super()` method. + /// + /// Operands: argument_count: `u32` + /// + /// Stack: argument_1, ... argument_n **=>** + SuperCall, + + /// Execute the `super()` method where the last argument is a rest parameter. + /// + /// Operands: argument_count: `u32` + /// + /// Stack: argument_1, ... argument_n **=>** + SuperCallWithRest, + + /// Execute the `super()` method when no constructor of the class is defined. + /// + /// Operands: + /// + /// Stack: argument_1, ... argument_n **=>** + SuperCallDerived, + /// Pop the two values of the stack, strict equal compares the two values, /// if true jumps to address, otherwise push the second pop'ed value. /// @@ -1079,6 +1167,8 @@ impl Opcode { Self::PushLiteral => "PushLiteral", Self::PushEmptyObject => "PushEmptyObject", Self::PushClassPrototype => "PushClassPrototype", + Self::SetClassPrototype => "SetClassPrototype", + Self::SetHomeObject => "SetHomeObject", Self::PushNewArray => "PushNewArray", Self::PushValueToArray => "PushValueToArray", Self::PushElisionToArray => "PushElisionToArray", @@ -1143,11 +1233,17 @@ impl Opcode { Self::DefineClassSetterByName => "DefineClassSetterByName", Self::SetPropertySetterByValue => "SetPropertySetterByValue", Self::DefineClassSetterByValue => "DefineClassSetterByValue", - Self::SetPrivateValue => "SetPrivateValue", + Self::AssignPrivateField => "AssignPrivateField", + Self::SetPrivateField => "SetPrivateValue", + Self::SetPrivateMethod => "SetPrivateMethod", Self::SetPrivateSetter => "SetPrivateSetter", Self::SetPrivateGetter => "SetPrivateGetter", - Self::GetPrivateField => "GetPrivateByName", - Self::PushClassComputedFieldName => "PushClassComputedFieldName", + Self::GetPrivateField => "GetPrivateField", + Self::PushClassField => "PushClassField", + Self::PushClassFieldPrivate => "PushClassFieldPrivate", + Self::PushClassPrivateGetter => "PushClassPrivateGetter", + Self::PushClassPrivateSetter => "PushClassPrivateSetter", + Self::PushClassPrivateMethod => "PushClassPrivateMethod", Self::DeletePropertyByName => "DeletePropertyByName", Self::DeletePropertyByValue => "DeletePropertyByValue", Self::CopyDataProperties => "CopyDataProperties", @@ -1166,6 +1262,10 @@ impl Opcode { Self::FinallySetJump => "FinallySetJump", Self::ToBoolean => "ToBoolean", Self::This => "This", + Self::Super => "Super", + Self::SuperCall => "SuperCall", + Self::SuperCallWithRest => "SuperCallWithRest", + Self::SuperCallDerived => "SuperCallDerived", Self::Case => "Case", Self::Default => "Default", Self::GetFunction => "GetFunction", @@ -1300,6 +1400,10 @@ impl Opcode { Self::FinallySetJump => "INST - FinallySetJump", Self::ToBoolean => "INST - ToBoolean", Self::This => "INST - This", + Self::Super => "INST - Super", + Self::SuperCall => "INST - SuperCall", + Self::SuperCallWithRest => "INST - SuperCallWithRest", + Self::SuperCallDerived => "INST - SuperCallDerived", Self::Case => "INST - Case", Self::Default => "INST - Default", Self::GetFunction => "INST - GetFunction", @@ -1335,17 +1439,25 @@ impl Opcode { Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", Self::Nop => "INST - Nop", Self::PushClassPrototype => "INST - PushClassPrototype", + Self::SetClassPrototype => "INST - SetClassPrototype", + Self::SetHomeObject => "INST - SetHomeObject", Self::DefineClassMethodByName => "INST - DefineClassMethodByName", Self::DefineClassMethodByValue => "INST - DefineClassMethodByValue", Self::DefineClassGetterByName => "INST - DefineClassGetterByName", Self::DefineClassGetterByValue => "INST - DefineClassGetterByValue", Self::DefineClassSetterByName => "INST - DefineClassSetterByName", Self::DefineClassSetterByValue => "INST - DefineClassSetterByValue", - Self::SetPrivateValue => "INST - SetPrivateValue", + Self::AssignPrivateField => "INST - AssignPrivateField", + Self::SetPrivateField => "INST - SetPrivateValue", + Self::SetPrivateMethod => "INST - SetPrivateMethod", Self::SetPrivateSetter => "INST - SetPrivateSetter", Self::SetPrivateGetter => "INST - SetPrivateGetter", Self::GetPrivateField => "INST - GetPrivateField", - Self::PushClassComputedFieldName => "INST - PushClassComputedFieldName", + Self::PushClassField => "INST - PushClassField", + Self::PushClassFieldPrivate => "INST - PushClassFieldPrivate", + Self::PushClassPrivateGetter => "INST - PushClassPrivateGetter", + Self::PushClassPrivateSetter => "INST - PushClassPrivateSetter", + Self::PushClassPrivateMethod => "INST - PushClassPrivateMethod", Self::ToPropertyKey => "INST - ToPropertyKey", } }