Browse Source

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.
pull/2154/head
raskad 2 years ago
parent
commit
13df9a1984
  1. 12
      boa_engine/src/builtins/eval/mod.rs
  2. 118
      boa_engine/src/builtins/function/mod.rs
  3. 309
      boa_engine/src/bytecompiler.rs
  4. 23
      boa_engine/src/context/mod.rs
  5. 4
      boa_engine/src/environments/mod.rs
  6. 255
      boa_engine/src/environments/runtime.rs
  7. 29
      boa_engine/src/object/jsobject.rs
  8. 5
      boa_engine/src/object/mod.rs
  9. 119
      boa_engine/src/syntax/ast/node/declaration/mod.rs
  10. 47
      boa_engine/src/syntax/ast/node/field/get_super_field/mod.rs
  11. 6
      boa_engine/src/syntax/ast/node/field/mod.rs
  12. 411
      boa_engine/src/syntax/ast/node/mod.rs
  13. 39
      boa_engine/src/syntax/ast/node/object/mod.rs
  14. 46
      boa_engine/src/syntax/ast/node/super_call/mod.rs
  15. 53
      boa_engine/src/syntax/parser/expression/left_hand_side/member.rs
  16. 23
      boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs
  17. 12
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs
  18. 12
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs
  19. 12
      boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs
  20. 12
      boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs
  21. 68
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  22. 226
      boa_engine/src/syntax/parser/mod.rs
  23. 161
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  24. 9
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  25. 4
      boa_engine/src/vm/call_frame.rs
  26. 464
      boa_engine/src/vm/code_block.rs
  27. 537
      boa_engine/src/vm/mod.rs
  28. 138
      boa_engine/src/vm/opcode.rs

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

@ -79,16 +79,10 @@ impl Eval {
// Because of implementation details the following code differs from the spec. // Because of implementation details the following code differs from the spec.
// Parse the script body (11.a - 11.d) // Parse the script body and handle early errors (6 - 11)
// TODO: Implement errors for 11.e - 11.h let body = match context.parse_eval(x.as_bytes(), direct, strict) {
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()) {
Ok(body) => body, 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`. // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.

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

@ -19,7 +19,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object,
ObjectData, ObjectData,
}, },
object::{ConstructorBuilder, FunctionBuilder, Ref, RefMut}, object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser}, 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 { pub enum ConstructorKind {
Base, Base,
Derived, 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<GcCell<dyn NativeObject>>` that allows passing additional /// Wrapper for `Gc<GcCell<dyn NativeObject>>` that allows passing additional
/// captures through a `Copy` closure. /// captures through a `Copy` closure.
/// ///
@ -191,6 +209,19 @@ pub enum Function {
Ordinary { Ordinary {
code: Gc<crate::vm::CodeBlock>, code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack, environments: DeclarativeEnvironmentStack,
/// The `[[ConstructorKind]]` internal slot.
#[unsafe_ignore_trace]
constructor_kind: ConstructorKind,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The `[[Fields]]` internal slot.
fields: Vec<ClassFieldDefinition>,
/// The `[[PrivateMethods]]` internal slot.
private_methods: Vec<(Sym, PrivateElement)>,
}, },
Generator { Generator {
code: Gc<crate::vm::CodeBlock>, code: Gc<crate::vm::CodeBlock>,
@ -207,14 +238,85 @@ impl fmt::Debug for Function {
impl Function { impl Function {
/// Returns true if the function object is a constructor. /// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
self.constructor().is_some() 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. /// Returns true if the function object is a derived constructor.
pub fn constructor(&self) -> Option<ConstructorKind> { pub(crate) fn is_derived_constructor(&self) -> bool {
match self { if let Self::Ordinary {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, constructor_kind, ..
Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor, } = 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));
} }
} }
} }

309
boa_engine/src/bytecompiler.rs

@ -1,5 +1,5 @@
use crate::{ use crate::{
builtins::function::{ConstructorKind, ThisMode}, builtins::function::ThisMode,
environments::{BindingLocator, CompileTimeEnvironment}, environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{ syntax::ast::{
node::{ node::{
@ -11,7 +11,8 @@ use crate::{
object::{MethodDefinition, PropertyDefinition, PropertyName}, object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget, operator::assign::AssignTarget,
template::TemplateElement, template::TemplateElement,
Class, Declaration, FormalParameterList, GetConstField, GetField, StatementList, Class, Declaration, FormalParameterList, GetConstField, GetField, GetSuperField,
StatementList,
}, },
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node, Const, Node,
@ -81,7 +82,7 @@ impl<'b> ByteCompiler<'b> {
#[inline] #[inline]
pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self { pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self {
Self { Self {
code_block: CodeBlock::new(name, 0, strict, None), code_block: CodeBlock::new(name, 0, strict),
literals_map: FxHashMap::default(), literals_map: FxHashMap::default(),
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
@ -1003,7 +1004,7 @@ impl<'b> ByteCompiler<'b> {
self.compile_expr(node.obj(), true)?; self.compile_expr(node.obj(), true)?;
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(node.field()); let index = self.get_or_insert_name(node.field());
self.emit(Opcode::SetPrivateValue, &[index]); self.emit(Opcode::AssignPrivateField, &[index]);
} }
AssignTarget::GetConstField(node) => { AssignTarget::GetConstField(node) => {
self.access_set(Access::ByName { node }, Some(assign.rhs()), use_expr)?; self.access_set(Access::ByName { node }, Some(assign.rhs()), use_expr)?;
@ -1032,6 +1033,24 @@ impl<'b> ByteCompiler<'b> {
let access = Access::ByValue { node }; let access = Access::ByValue { node };
self.access_get(access, use_expr)?; 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) => { Node::ConditionalOp(op) => {
self.compile_expr(op.cond(), true)?; self.compile_expr(op.cond(), true)?;
let jelse = self.jump_if_false(); let jelse = self.jump_if_false();
@ -1181,6 +1200,24 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]); self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
} }
Node::ClassExpr(class) => self.class(class, true)?, 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!(), _ => unreachable!(),
} }
Ok(()) Ok(())
@ -1194,10 +1231,6 @@ impl<'b> ByteCompiler<'b> {
match decl { match decl {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let ident = ident.sym(); let ident = ident.sym();
if ident == Sym::ARGUMENTS {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() { if let Some(expr) = decl.init() {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit_binding(BindingOpcode::InitVar, ident); self.emit_binding(BindingOpcode::InitVar, ident);
@ -1206,10 +1239,6 @@ impl<'b> ByteCompiler<'b> {
} }
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
if pattern.idents().contains(&Sym::ARGUMENTS) {
self.code_block.lexical_name_argument = true;
}
if let Some(init) = decl.init() { if let Some(init) = decl.init() {
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
} else { } else {
@ -1225,10 +1254,6 @@ impl<'b> ByteCompiler<'b> {
for decl in list.as_ref() { for decl in list.as_ref() {
match decl { match decl {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
if ident.sym() == Sym::ARGUMENTS {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() { if let Some(expr) = decl.init() {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit_binding(BindingOpcode::InitLet, ident.sym()); self.emit_binding(BindingOpcode::InitLet, ident.sym());
@ -1237,10 +1262,6 @@ impl<'b> ByteCompiler<'b> {
} }
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
if pattern.idents().contains(&Sym::ARGUMENTS) {
self.code_block.lexical_name_argument = true;
}
if let Some(init) = decl.init() { if let Some(init) = decl.init() {
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
} else { } else {
@ -1256,9 +1277,6 @@ impl<'b> ByteCompiler<'b> {
for decl in list.as_ref() { for decl in list.as_ref() {
match decl { match decl {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
if ident.sym() == Sym::ARGUMENTS {
self.code_block.lexical_name_argument = true;
}
let init = decl let init = decl
.init() .init()
.expect("const declaration must have initializer"); .expect("const declaration must have initializer");
@ -1266,10 +1284,6 @@ impl<'b> ByteCompiler<'b> {
self.emit_binding(BindingOpcode::InitConst, ident.sym()); self.emit_binding(BindingOpcode::InitConst, ident.sym());
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
if pattern.idents().contains(&Sym::ARGUMENTS) {
self.code_block.lexical_name_argument = true;
}
if let Some(init) = decl.init() { if let Some(init) = decl.init() {
self.compile_expr(init, true)?; self.compile_expr(init, true)?;
} else { } else {
@ -1927,22 +1941,12 @@ impl<'b> ByteCompiler<'b> {
) -> JsResult<Gc<CodeBlock>> { ) -> JsResult<Gc<CodeBlock>> {
let strict = strict || body.strict(); let strict = strict || body.strict();
let length = parameters.length(); let length = parameters.length();
let mut code = CodeBlock::new( let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict);
name.unwrap_or(Sym::EMPTY_STRING),
length,
strict,
Some(ConstructorKind::Base),
);
if let FunctionKind::Arrow = kind { if let FunctionKind::Arrow = kind {
code.constructor = None;
code.this_mode = ThisMode::Lexical; code.this_mode = ThisMode::Lexical;
} }
if generator {
code.constructor = None;
}
let mut compiler = ByteCompiler { let mut compiler = ByteCompiler {
code_block: code, code_block: code,
literals_map: FxHashMap::default(), 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 declaration binds the resulting class object to it's identifier.
/// A class expression leaves the resulting class object on the stack for following operations. /// A class expression leaves the resulting class object on the stack for following operations.
fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> { fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> {
let mut code = CodeBlock::new( let code = CodeBlock::new(class.name(), 0, true);
class.name(),
0,
true,
Some(if class.super_ref().is_some() {
ConstructorKind::Derived
} else {
ConstructorKind::Base
}),
);
code.computed_field_names = Some(gc::GcCell::new(vec![]));
let mut compiler = ByteCompiler { let mut compiler = ByteCompiler {
code_block: code, code_block: code,
literals_map: FxHashMap::default(), literals_map: FxHashMap::default(),
@ -2562,47 +2556,6 @@ impl<'b> ByteCompiler<'b> {
}; };
compiler.context.push_compile_time_environment(true); 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() { if let Some(expr) = class.constructor() {
compiler.code_block.length = expr.parameters().length(); compiler.code_block.length = expr.parameters().length();
compiler.code_block.params = expr.parameters().clone(); compiler.code_block.params = expr.parameters().clone();
@ -2667,8 +2620,12 @@ impl<'b> ByteCompiler<'b> {
.compile_environments .compile_environments
.push(compile_environment); .push(compile_environment);
compiler.code_block.num_bindings = num_bindings; compiler.code_block.num_bindings = num_bindings;
compiler.code_block.is_class_constructor = true;
} }
} else { } else {
if class.super_ref().is_some() {
compiler.emit_opcode(Opcode::SuperCallDerived);
}
let (num_bindings, compile_environment) = let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment(); compiler.context.pop_compile_time_environment();
compiler compiler
@ -2676,6 +2633,7 @@ impl<'b> ByteCompiler<'b> {
.compile_environments .compile_environments
.push(compile_environment); .push(compile_environment);
compiler.code_block.num_bindings = num_bindings; compiler.code_block.num_bindings = num_bindings;
compiler.code_block.is_class_constructor = true;
} }
compiler.emit_opcode(Opcode::PushUndefined); compiler.emit_opcode(Opcode::PushUndefined);
@ -2686,6 +2644,16 @@ impl<'b> ByteCompiler<'b> {
self.code_block.functions.push(code); self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]); 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() { for element in class.elements() {
match element { match element {
ClassElement::StaticMethodDefinition(name, method_definition) => { ClassElement::StaticMethodDefinition(name, method_definition) => {
@ -2767,22 +2735,91 @@ impl<'b> ByteCompiler<'b> {
MethodDefinition::Ordinary(expr) => { MethodDefinition::Ordinary(expr) => {
self.function(&expr.clone().into(), true)?; self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]); self.emit(Opcode::SetPrivateMethod, &[index]);
} }
MethodDefinition::Generator(expr) => { MethodDefinition::Generator(expr) => {
self.function(&expr.clone().into(), true)?; self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]); self.emit(Opcode::SetPrivateMethod, &[index]);
} }
// TODO: implement async // TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
} }
} }
ClassElement::FieldDefinition(PropertyName::Computed(name_node), _) => { ClassElement::FieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
self.compile_stmt(name_node, true)?; match name {
self.emit_opcode(Opcode::Swap); PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushClassComputedFieldName); 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) => { ClassElement::StaticFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
@ -2817,7 +2854,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::PushUndefined);
} }
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateValue, &[index]); self.emit(Opcode::SetPrivateField, &[index]);
} }
ClassElement::StaticBlock(statement_list) => { ClassElement::StaticBlock(statement_list) => {
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
@ -2837,24 +2874,42 @@ impl<'b> ByteCompiler<'b> {
let index = self.code_block.functions.len() as u32; let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code); self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]); self.emit(Opcode::GetFunction, &[index]);
self.emit_opcode(Opcode::SetHomeObject);
self.emit(Opcode::Call, &[0]); self.emit(Opcode::Call, &[0]);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
ClassElement::MethodDefinition(..) ClassElement::PrivateMethodDefinition(name, method_definition) => {
| ClassElement::PrivateMethodDefinition(..) self.emit_opcode(Opcode::Dup);
| ClassElement::PrivateFieldDefinition(..) match method_definition {
| ClassElement::FieldDefinition(..) => {} 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); self.emit_opcode(Opcode::Swap);
if let Some(node) = class.super_ref() {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::PushClassPrototype);
} else {
self.emit_opcode(Opcode::PushEmptyObject);
}
for element in class.elements() { for element in class.elements() {
match element { match element {
@ -2918,37 +2973,13 @@ impl<'b> ByteCompiler<'b> {
} }
}, },
// TODO: implement async // TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {} MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {
} self.emit_opcode(Opcode::Pop);
}
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]);
} }
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
} }
} }
ClassElement::PrivateFieldDefinition(..) ClassElement::PrivateMethodDefinition(..)
| ClassElement::PrivateFieldDefinition(..)
| ClassElement::StaticFieldDefinition(..) | ClassElement::StaticFieldDefinition(..)
| ClassElement::PrivateStaticFieldDefinition(..) | ClassElement::PrivateStaticFieldDefinition(..)
| ClassElement::StaticMethodDefinition(..) | ClassElement::StaticMethodDefinition(..)
@ -2958,9 +2989,7 @@ impl<'b> ByteCompiler<'b> {
} }
} }
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Pop);
let index = self.get_or_insert_name(Sym::PROTOTYPE);
self.emit(Opcode::SetPropertyByName, &[index]);
if !expression { if !expression {
self.emit_binding(BindingOpcode::InitVar, class.name()); self.emit_binding(BindingOpcode::InitVar, class.name());

23
boa_engine/src/context/mod.rs

@ -167,14 +167,21 @@ impl Context {
parser.parse_all(self) parser.parse_all(self)
} }
/// Parse the given source text in strict mode. /// Parse the given source text with eval specific handling.
pub(crate) fn parse_strict<S>(&mut self, src: S) -> Result<StatementList, ParseError> pub(crate) fn parse_eval<S>(
&mut self,
src: S,
direct: bool,
strict: bool,
) -> Result<StatementList, ParseError>
where where
S: AsRef<[u8]>, S: AsRef<[u8]>,
{ {
let mut parser = Parser::new(src.as_ref()); let mut parser = Parser::new(src.as_ref());
parser.set_strict(); if strict {
parser.parse_all(self) parser.set_strict();
}
parser.parse_eval(direct, self)
} }
/// `Call ( F, V [ , argumentsList ] )` /// `Call ( F, V [ , argumentsList ] )`
@ -705,22 +712,20 @@ impl Context {
#[inline] #[inline]
pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> { pub fn execute(&mut self, code_block: Gc<CodeBlock>) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Execution", "Main"); let _timer = Profiler::global().start_event("Execution", "Main");
let global_object = self.global_object().clone().into();
self.vm.push_frame(CallFrame { self.vm.push_frame(CallFrame {
prev: None, prev: None,
code: code_block, code: code_block,
this: global_object,
pc: 0, pc: 0,
catch: Vec::new(), catch: Vec::new(),
finally_return: FinallyReturn::None, finally_return: FinallyReturn::None,
finally_jump: Vec::new(), finally_jump: Vec::new(),
pop_on_return: 0, pop_on_return: 0,
loop_env_stack: vec![0], loop_env_stack: Vec::from([0]),
try_env_stack: vec![crate::vm::TryStackEntry { try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0, num_env: 0,
num_loop_stack_entries: 0, num_loop_stack_entries: 0,
}], }]),
param_count: 0, param_count: 0,
arg_count: 0, arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,

4
boa_engine/src/environments/mod.rs

@ -29,7 +29,9 @@ mod runtime;
pub(crate) use { pub(crate) use {
compile::CompileTimeEnvironment, compile::CompileTimeEnvironment,
runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack}, runtime::{
BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, EnvironmentSlots,
},
}; };
#[cfg(test)] #[cfg(test)]

255
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_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@ -26,12 +26,161 @@ use rustc_hash::FxHashSet;
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment { pub(crate) struct DeclarativeEnvironment {
bindings: Cell<Vec<Option<JsValue>>>, bindings: Cell<Vec<Option<JsValue>>>,
this: Option<JsValue>,
compile: Gc<Cell<CompileTimeEnvironment>>, compile: Gc<Cell<CompileTimeEnvironment>>,
poisoned: Cell<bool>, poisoned: Cell<bool>,
slots: Option<EnvironmentSlots>,
}
/// Describes the different types of internal slot data that an environment can hold.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum EnvironmentSlots {
Function(Cell<FunctionSlots>),
Global,
}
impl EnvironmentSlots {
/// Return the slots if they are part of a function environment.
pub(crate) fn as_function_slots(&self) -> Option<&Cell<FunctionSlots>> {
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<JsObject>,
}
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 { 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. /// Get the binding value from the environment by it's index.
/// ///
/// # Panics /// # Panics
@ -79,9 +228,9 @@ impl DeclarativeEnvironmentStack {
Self { Self {
stack: vec![Gc::new(DeclarativeEnvironment { stack: vec![Gc::new(DeclarativeEnvironment {
bindings: Cell::new(Vec::new()), bindings: Cell::new(Vec::new()),
this: None,
compile: global_compile_environment, compile: global_compile_environment,
poisoned: Cell::new(false), 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). /// This is only useful when compiled bindings are added after the initial compilation (eval).
pub(crate) fn extend_outer_function_environment(&mut self) { pub(crate) fn extend_outer_function_environment(&mut self) {
for env in self.stack.iter().rev() { 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 compile_bindings_number = env.compile.borrow().num_bindings();
let mut bindings_mut = env.bindings.borrow_mut(); 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] #[inline]
pub(crate) fn get_last_this(&self) -> Option<JsValue> { pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots {
for env in self.stack.iter().rev() { for env in self.stack.iter().rev() {
if let Some(this) = &env.this { if let Some(slots) = &env.slots {
return Some(this.clone()); 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. /// Push a declarative environment on the environments stack.
@ -195,9 +363,9 @@ impl DeclarativeEnvironmentStack {
self.stack.push(Gc::new(DeclarativeEnvironment { self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]), bindings: Cell::new(vec![None; num_bindings]),
this: None,
compile: compile_environment, compile: compile_environment,
poisoned: Cell::new(poisoned), poisoned: Cell::new(poisoned),
slots: None,
})); }));
} }
@ -211,29 +379,78 @@ impl DeclarativeEnvironmentStack {
&mut self, &mut self,
num_bindings: usize, num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>, compile_environment: Gc<Cell<CompileTimeEnvironment>>,
this: JsValue, this: Option<JsValue>,
function_object: JsObject,
new_target: Option<JsObject>,
lexical: bool,
) { ) {
let poisoned = self let outer = self
.stack .stack
.last() .last()
.expect("global environment must always exist") .expect("global environment must always exist");
.poisoned
.borrow() let poisoned = outer.poisoned.borrow().to_owned();
.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<Cell<CompileTimeEnvironment>>,
) {
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 { self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]), bindings: Cell::new(vec![None; num_bindings]),
this: Some(this),
compile: compile_environment, compile: compile_environment,
poisoned: Cell::new(poisoned), poisoned: Cell::new(poisoned),
slots,
})); }));
} }
/// Pop environment from the environments stack. /// Pop environment from the environments stack.
#[inline] #[inline]
pub(crate) fn pop(&mut self) { pub(crate) fn pop(&mut self) -> Gc<DeclarativeEnvironment> {
debug_assert!(self.stack.len() > 1); debug_assert!(self.stack.len() > 1);
self.stack.pop(); self.stack
.pop()
.expect("environment stack is cannot be empty")
} }
/// Get the most outer environment. /// Get the most outer environment.

29
boa_engine/src/object/jsobject.rs

@ -52,28 +52,13 @@ impl JsObject {
/// internal slots from the `data` provided. /// internal slots from the `data` provided.
#[inline] #[inline]
pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self { pub fn from_proto_and_data<O: Into<Option<Self>>>(prototype: O, data: ObjectData) -> Self {
let prototype: Option<Self> = prototype.into(); Self::from_object(Object {
if let Some(prototype) = prototype { data,
let private = { prototype: prototype.into(),
let prototype_b = prototype.borrow(); extensible: true,
prototype_b.private_elements.clone() properties: PropertyMap::default(),
}; private_elements: FxHashMap::default(),
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(),
})
}
} }
/// Immutably borrows the `Object`. /// Immutably borrows the `Object`.

5
boa_engine/src/object/mod.rs

@ -124,8 +124,9 @@ pub struct Object {
/// The representation of private object elements. /// The representation of private object elements.
#[derive(Clone, Debug, Trace, Finalize)] #[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum PrivateElement { pub enum PrivateElement {
Value(JsValue), Field(JsValue),
Method(JsObject),
Accessor { Accessor {
getter: Option<JsObject>, getter: Option<JsObject>,
setter: Option<JsObject>, setter: Option<JsObject>,

119
boa_engine/src/syntax/ast/node/declaration/mod.rs

@ -4,7 +4,7 @@ use crate::syntax::ast::node::{
join_nodes, join_nodes,
object::PropertyName, object::PropertyName,
statement_list::StatementList, statement_list::StatementList,
Identifier, Node, ContainsSymbol, Identifier, Node,
}; };
use boa_interner::{Interner, Sym, ToInternedString}; use boa_interner::{Interner, Sym, ToInternedString};
@ -229,6 +229,30 @@ impl Declaration {
Self::Pattern(pattern) => pattern.init(), 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. /// `DeclarationPattern` represents an object or array binding pattern.
@ -383,6 +407,99 @@ impl DeclarationPattern {
} }
false 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. /// `DeclarationPatternObject` represents an object binding pattern.

47
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<Node>),
}
impl From<Sym> for GetSuperField {
fn from(field: Sym) -> Self {
Self::Const(field)
}
}
impl From<Node> 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<GetSuperField> for Node {
fn from(get_super_field: GetSuperField) -> Self {
Self::GetSuperField(get_super_field)
}
}

6
boa_engine/src/syntax/ast/node/field/mod.rs

@ -3,8 +3,12 @@
pub mod get_const_field; pub mod get_const_field;
pub mod get_field; pub mod get_field;
pub mod get_private_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)] #[cfg(test)]
mod tests; mod tests;

411
boa_engine/src/syntax/ast/node/mod.rs

@ -17,6 +17,7 @@ pub mod operator;
pub mod return_smt; pub mod return_smt;
pub mod spread; pub mod spread;
pub mod statement_list; pub mod statement_list;
pub mod super_call;
pub mod switch; pub mod switch;
pub mod template; pub mod template;
pub mod throw; pub mod throw;
@ -35,7 +36,7 @@ pub use self::{
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList,
DeclarationPattern, FunctionDecl, FunctionExpr, DeclarationPattern, FunctionDecl, FunctionExpr,
}, },
field::{get_private_field::GetPrivateField, GetConstField, GetField}, field::{get_private_field::GetPrivateField, GetConstField, GetField, GetSuperField},
identifier::Identifier, identifier::Identifier,
iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
new::New, new::New,
@ -46,6 +47,7 @@ pub use self::{
return_smt::Return, return_smt::Return,
spread::Spread, spread::Spread,
statement_list::StatementList, statement_list::StatementList,
super_call::SuperCall,
switch::{Case, Switch}, switch::{Case, Switch},
template::{TaggedTemplate, TemplateLit}, template::{TaggedTemplate, TemplateLit},
throw::Throw, throw::Throw,
@ -55,6 +57,7 @@ use self::{
declaration::class_decl::ClassElement, declaration::class_decl::ClassElement,
iteration::IterableLoopInitializer, iteration::IterableLoopInitializer,
object::{MethodDefinition, PropertyDefinition}, object::{MethodDefinition, PropertyDefinition},
operator::assign::AssignTarget,
}; };
pub(crate) use self::parameters::FormalParameterListFlags; pub(crate) use self::parameters::FormalParameterListFlags;
@ -146,6 +149,9 @@ pub enum Node {
/// Provides access to object fields. [More information](./declaration/struct.GetField.html). /// Provides access to object fields. [More information](./declaration/struct.GetField.html).
GetField(GetField), GetField(GetField),
/// Provides access to super fields. [More information](./declaration/struct.GetSuperField.html).
GetSuperField(GetSuperField),
/// A `for` statement. [More information](./iteration/struct.ForLoop.html). /// A `for` statement. [More information](./iteration/struct.ForLoop.html).
ForLoop(ForLoop), ForLoop(ForLoop),
@ -240,6 +246,9 @@ pub enum Node {
/// A class declaration. [More information](./declaration/struct.class_decl.Class.html). /// A class declaration. [More information](./declaration/struct.class_decl.Class.html).
ClassExpr(Class), ClassExpr(Class),
/// A call of the super constructor. [More information](./super_call/struct.SuperCall.html).
SuperCall(SuperCall),
} }
impl From<Const> for Node { impl From<Const> for Node {
@ -314,6 +323,9 @@ impl Node {
get_private_field.to_interned_string(interner) get_private_field.to_interned_string(interner)
} }
Self::GetField(ref get_field) => get_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::WhileLoop(ref while_loop) => while_loop.to_indented_string(interner, indentation),
Self::DoWhileLoop(ref do_while) => do_while.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), 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::AsyncGeneratorDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassDecl(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::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 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 { 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)"); 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
}

39
boa_engine/src/syntax/ast/node/object/mod.rs

@ -3,7 +3,7 @@
use crate::syntax::ast::{ use crate::syntax::ast::{
node::{ node::{
declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr, declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr,
FunctionExpr, GeneratorExpr, Node, FormalParameterList, FunctionExpr, GeneratorExpr, Node, StatementList,
}, },
Const, Const,
}; };
@ -321,6 +321,32 @@ pub enum MethodDefinition {
Async(AsyncFunctionExpr), 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. /// `PropertyName` can be either a literal or computed.
/// ///
/// More information: /// More information:
@ -348,6 +374,7 @@ pub enum PropertyName {
} }
impl PropertyName { impl PropertyName {
/// Returns the literal property name if it exists.
pub(in crate::syntax) fn literal(&self) -> Option<Sym> { pub(in crate::syntax) fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self { if let Self::Literal(sym) = self {
Some(*sym) 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<Sym> { pub(in crate::syntax) fn prop_name(&self) -> Option<Sym> {
match self { match self {
PropertyName::Literal(sym) PropertyName::Literal(sym)

46
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<A>(args: A) -> Self
where
A: Into<Box<[Node]>>,
{
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<SuperCall> for Node {
fn from(call: SuperCall) -> Self {
Self::SuperCall(call)
}
}

53
boa_engine/src/syntax/parser/expression/left_hand_side/member.rs

@ -10,7 +10,7 @@ use crate::syntax::{
ast::{ ast::{
node::{ node::{
field::{get_private_field::GetPrivateField, GetConstField, GetField}, field::{get_private_field::GetPrivateField, GetConstField, GetField},
Call, New, Node, Call, GetSuperField, New, Node,
}, },
Keyword, Punctuator, Keyword, Punctuator,
}, },
@ -66,7 +66,7 @@ where
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let mut lhs = match token.kind() { let mut lhs = match token.kind() {
TokenKind::Keyword((Keyword::New, true)) => { TokenKind::Keyword((Keyword::New | Keyword::Super, true)) => {
return Err(ParseError::general( return Err(ParseError::general(
"keyword must not contain escaped characters", "keyword must not contain escaped characters",
token.span().start(), token.span().start(),
@ -86,6 +86,55 @@ where
Node::from(New::from(call_node)) 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) _ => PrimaryExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?, .parse(cursor, interner)?,
}; };

23
boa_engine/src/syntax/parser/expression/left_hand_side/mod.rs

@ -12,11 +12,15 @@ mod call;
mod member; mod member;
mod template; mod template;
use self::{call::CallExpression, member::MemberExpression};
use crate::syntax::{ use crate::syntax::{
ast::{Node, Punctuator}, ast::{node::SuperCall, Keyword, Node, Punctuator},
lexer::{InputElement, TokenKind}, 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_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -64,6 +68,19 @@ where
cursor.set_goal(InputElement::TemplateTail); 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 // TODO: Implement NewExpression: new MemberExpression
let lhs = MemberExpression::new(self.name, self.allow_yield, self.allow_await) let lhs = MemberExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .parse(cursor, interner)?;

12
boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs

@ -2,7 +2,10 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::AsyncFunctionExpr, Keyword, Position, Punctuator}, ast::{
node::{function_contains_super, AsyncFunctionExpr},
Keyword, Position, Punctuator,
},
lexer::{Error as LexError, TokenKind}, lexer::{Error as LexError, TokenKind},
parser::{ parser::{
expression::BindingIdentifier, expression::BindingIdentifier,
@ -132,6 +135,13 @@ where
params_start_position, params_start_position,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}
Ok(AsyncFunctionExpr::new(name, params, body)) Ok(AsyncFunctionExpr::new(name, params, body))
} }
} }

12
boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs

@ -11,7 +11,10 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::AsyncGeneratorExpr, Keyword, Position, Punctuator}, ast::{
node::{function_contains_super, AsyncGeneratorExpr},
Keyword, Position, Punctuator,
},
lexer::{Error as LexError, TokenKind}, lexer::{Error as LexError, TokenKind},
parser::{ parser::{
expression::BindingIdentifier, expression::BindingIdentifier,
@ -148,6 +151,13 @@ where
params_start_position, params_start_position,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}
//implement the below AsyncGeneratorExpr in ast::node //implement the below AsyncGeneratorExpr in ast::node
Ok(AsyncGeneratorExpr::new(name, params, body)) Ok(AsyncGeneratorExpr::new(name, params, body))
} }

12
boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs

@ -11,7 +11,10 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::FunctionExpr, Keyword, Position, Punctuator}, ast::{
node::{function_contains_super, FunctionExpr},
Keyword, Position, Punctuator,
},
lexer::{Error as LexError, TokenKind}, lexer::{Error as LexError, TokenKind},
parser::{ parser::{
expression::BindingIdentifier, expression::BindingIdentifier,
@ -124,6 +127,13 @@ where
params_start_position, params_start_position,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}
Ok(FunctionExpr::new(name, params, body)) Ok(FunctionExpr::new(name, params, body))
} }
} }

12
boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs

@ -11,7 +11,10 @@
mod tests; mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{node::GeneratorExpr, Position, Punctuator}, ast::{
node::{function_contains_super, GeneratorExpr},
Position, Punctuator,
},
lexer::{Error as LexError, TokenKind}, lexer::{Error as LexError, TokenKind},
parser::{ parser::{
expression::BindingIdentifier, expression::BindingIdentifier,
@ -128,6 +131,13 @@ where
params_start_position, params_start_position,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}
Ok(GeneratorExpr::new(name, params, body)) Ok(GeneratorExpr::new(name, params, body))
} }
} }

68
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -13,6 +13,7 @@ mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{ ast::{
node::{ node::{
function_contains_super, has_direct_super,
object::{self, MethodDefinition}, object::{self, MethodDefinition},
AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr, AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr,
GeneratorExpr, Node, Object, GeneratorExpr, Node, Object,
@ -173,10 +174,18 @@ where
cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?;
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let position = token.span().start();
if let TokenKind::Punctuator(Punctuator::Mul) = token.kind() { if let TokenKind::Punctuator(Punctuator::Mul) = token.kind() {
let (property_name, method) = let (property_name, method) =
AsyncGeneratorMethod::new(self.allow_yield, self.allow_await) AsyncGeneratorMethod::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .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( return Ok(object::PropertyDefinition::method_definition(
method, method,
property_name, property_name,
@ -184,6 +193,12 @@ where
} }
let (property_name, method) = let (property_name, method) =
AsyncMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; 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( return Ok(object::PropertyDefinition::method_definition(
method, method,
property_name, property_name,
@ -206,6 +221,11 @@ where
let (class_element_name, method) = let (class_element_name, method) =
GeneratorMethod::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; 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 { match class_element_name {
object::ClassElementName::PropertyName(property_name) => { object::ClassElementName::PropertyName(property_name) => {
return Ok(object::PropertyDefinition::method_definition( return Ok(object::PropertyDefinition::method_definition(
@ -241,6 +261,12 @@ where
match property_name { match property_name {
// MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] } // MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] }
object::PropertyName::Literal(str) if str == Sym::GET && !ordinary_method => { 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) property_name = PropertyName::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .parse(cursor, interner)?;
@ -267,6 +293,11 @@ where
interner, 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( Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Get(FunctionExpr::new( MethodDefinition::Get(FunctionExpr::new(
None, None,
@ -320,6 +351,14 @@ where
))); )));
} }
// It is a Syntax Error if HasDirectSuper of MethodDefinition is true.
if has_direct_super(&body, &parameters) {
return Err(ParseError::general(
"invalid super usage",
params_start_position,
));
}
Ok(object::PropertyDefinition::method_definition( Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Set(FunctionExpr::new(None, parameters, body)), MethodDefinition::Set(FunctionExpr::new(None, parameters, body)),
property_name, property_name,
@ -391,6 +430,14 @@ where
} }
} }
// It is a Syntax Error if HasDirectSuper of MethodDefinition is true.
if has_direct_super(&body, &params) {
return Err(ParseError::general(
"invalid super usage",
params_start_position,
));
}
Ok(object::PropertyDefinition::method_definition( Ok(object::PropertyDefinition::method_definition(
MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)), MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)),
property_name, property_name,
@ -655,6 +702,13 @@ where
body_start, body_start,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
body_start,
)));
}
Ok(( Ok((
class_element_name, class_element_name,
MethodDefinition::Generator(GeneratorExpr::new(None, params, body)), MethodDefinition::Generator(GeneratorExpr::new(None, params, body)),
@ -742,6 +796,13 @@ where
body_start, body_start,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
body_start,
)));
}
Ok(( Ok((
property_name, property_name,
MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)), MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)),
@ -824,6 +885,13 @@ where
body_start, body_start,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
body_start,
)));
}
Ok(( Ok((
property_name, property_name,
MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)), MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)),

226
boa_engine/src/syntax/parser/mod.rs

@ -14,7 +14,7 @@ mod tests;
use crate::{ use crate::{
syntax::{ syntax::{
ast::{ ast::{
node::{FormalParameterList, StatementList}, node::{ContainsSymbol, FormalParameterList, StatementList},
Position, Position,
}, },
lexer::TokenKind, lexer::TokenKind,
@ -135,65 +135,65 @@ impl<R> Parser<R> {
where where
R: Read, R: Read,
{ {
let statement_list = Script.parse(&mut self.cursor, context.interner_mut())?; Script::new(false).parse(&mut self.cursor, context)
}
// 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. pub(crate) fn parse_eval(
let mut var_declared_names = FxHashSet::default(); &mut self,
statement_list.var_declared_names_new(&mut var_declared_names); direct: bool,
let lexically_declared_names = statement_list.lexically_declared_names(); context: &mut Context,
let mut lexically_declared_names_map: FxHashMap<Sym, bool> = FxHashMap::default(); ) -> Result<StatementList, ParseError>
for (name, is_function_declaration) in &lexically_declared_names { where
if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) { R: Read,
if !(*is_function_declaration && *existing_is_function_declaration) { {
return Err(ParseError::general( let (in_method, in_derived_constructor) = if let Some(function_env) = context
"lexical name declared multiple times", .realm
Position::new(1, 1), .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) { if !contains_super_call && node.contains(ContainsSymbol::SuperCall) {
return Err(ParseError::general( contains_super_call = true;
"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),
));
}
}
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) Ok(statement_list)
} }
@ -235,34 +235,97 @@ impl<R> Parser<R> {
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-Script /// [spec]: https://tc39.es/ecma262/#prod-Script
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Script; pub struct Script {
direct_eval: bool,
}
impl<R> TokenParser<R> for Script impl Script {
where /// Create a new `Script` parser.
R: Read, fn new(direct_eval: bool) -> Self {
{ Self { direct_eval }
type Output = StatementList; }
fn parse( fn parse<R: Read>(
self, self,
cursor: &mut Cursor<R>, cursor: &mut Cursor<R>,
interner: &mut Interner, context: &mut Context,
) -> Result<Self::Output, ParseError> { ) -> Result<StatementList, ParseError> {
let mut strict = cursor.strict_mode(); let mut strict = cursor.strict_mode();
match cursor.peek(0, interner)? { match cursor.peek(0, context.interner_mut())? {
Some(tok) => { Some(tok) => {
match tok.kind() { match tok.kind() {
// Set the strict mode // Set the strict mode
TokenKind::StringLiteral(string) TokenKind::StringLiteral(string)
if interner.resolve_expect(*string) == "use strict" => if context.interner_mut().resolve_expect(*string) == "use strict" =>
{ {
cursor.set_strict_mode(true); cursor.set_strict_mode(true);
strict = 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); 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<Sym, bool> = 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) Ok(statement_list)
} }
None => Ok(StatementList::from(Vec::new())), None => Ok(StatementList::from(Vec::new())),
@ -277,7 +340,16 @@ where
/// ///
/// [spec]: https://tc39.es/ecma262/#prod-ScriptBody /// [spec]: https://tc39.es/ecma262/#prod-ScriptBody
#[derive(Debug, Clone, Copy)] #[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<R> TokenParser<R> for ScriptBody impl<R> TokenParser<R> for ScriptBody
where where
@ -290,6 +362,24 @@ where
cursor: &mut Cursor<R>, cursor: &mut Cursor<R>,
interner: &mut Interner, interner: &mut Interner,
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
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)
} }
} }

161
boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs

@ -3,8 +3,9 @@ use crate::syntax::{
node::{ node::{
self, self,
declaration::class_decl::ClassElement as ClassElementNode, declaration::class_decl::ClassElement as ClassElementNode,
function_contains_super, has_direct_super,
object::{MethodDefinition, PropertyName::Literal}, object::{MethodDefinition, PropertyName::Literal},
Class, FormalParameterList, FunctionExpr, Class, ContainsSymbol, FormalParameterList, FunctionExpr,
}, },
Keyword, Punctuator, Keyword, Punctuator,
}, },
@ -165,10 +166,27 @@ where
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
Ok(Class::new(self.name, super_ref, None, vec![])) Ok(Class::new(self.name, super_ref, None, vec![]))
} else { } else {
let body_start = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let (constructor, elements) = let (constructor, elements) =
ClassBody::new(self.name, self.allow_yield, self.allow_await) ClassBody::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBlock, "class tail", 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)) Ok(Class::new(self.name, super_ref, constructor, elements))
} }
} }
@ -295,57 +313,72 @@ where
} }
(None, Some(element)) => { (None, Some(element)) => {
match &element { match &element {
ClassElementNode::PrivateMethodDefinition(name, method) => match method ClassElementNode::PrivateMethodDefinition(name, method) => {
{ // It is a Syntax Error if PropName of MethodDefinition is not "constructor" and HasDirectSuper of MethodDefinition is true.
MethodDefinition::Get(_) => { if has_direct_super(method.body(), method.parameters()) {
match private_elements_names.get(name) { return Err(ParseError::lex(LexError::Syntax(
Some(PrivateElement::Setter) => { "invalid super usage".into(),
private_elements_names position,
.insert(*name, PrivateElement::Value); )));
} }
Some(_) => { match method {
return Err(ParseError::general( MethodDefinition::Get(_) => {
"private identifier has already been declared", match private_elements_names.get(name) {
position, Some(PrivateElement::Setter) => {
)); private_elements_names
} .insert(*name, PrivateElement::Value);
None => { }
private_elements_names Some(_) => {
.insert(*name, PrivateElement::Getter); return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
None => {
private_elements_names
.insert(*name, PrivateElement::Getter);
}
} }
} }
} MethodDefinition::Set(_) => {
MethodDefinition::Set(_) => { match private_elements_names.get(name) {
match private_elements_names.get(name) { Some(PrivateElement::Getter) => {
Some(PrivateElement::Getter) => { private_elements_names
private_elements_names .insert(*name, PrivateElement::Value);
.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( return Err(ParseError::general(
"private identifier has already been declared", "private identifier has already been declared",
position, 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) => { 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 { match method {
MethodDefinition::Get(_) => { MethodDefinition::Get(_) => {
match private_elements_names.get(name) { 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 if private_elements_names
.insert(*name, PrivateElement::Value) .insert(*name, PrivateElement::Value)
.is_some() .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 if private_elements_names
.insert(*name, PrivateElement::StaticValue) .insert(*name, PrivateElement::StaticValue)
.is_some() .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); elements.push(element);
@ -1276,6 +1347,7 @@ where
} }
// ClassStaticBlockBody : ClassStaticBlockStatementList // ClassStaticBlockBody : ClassStaticBlockStatementList
// It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true. // 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) => { ClassElementNode::StaticBlock(block) => {
for node in block.items() { for node in block.items() {
if node.contains_arguments() { if node.contains_arguments() {
@ -1284,6 +1356,9 @@ where
position, position,
)); ));
} }
if node.contains(ContainsSymbol::SuperCall) {
return Err(ParseError::general("invalid super usage", position));
}
} }
} }
_ => {} _ => {}

9
boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs

@ -21,7 +21,7 @@ use self::{
}; };
use crate::syntax::{ use crate::syntax::{
ast::node::{FormalParameterList, StatementList}, ast::node::{FormalParameterList, StatementList},
ast::{Keyword, Node, Position, Punctuator}, ast::{node::function_contains_super, Keyword, Node, Position, Punctuator},
lexer::TokenKind, lexer::TokenKind,
parser::{ parser::{
expression::BindingIdentifier, expression::BindingIdentifier,
@ -213,5 +213,12 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
params_start_position, params_start_position,
)?; )?;
if function_contains_super(&body, &params) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}
Ok((name, params, body)) Ok((name, params, body))
} }

4
boa_engine/src/vm/call_frame.rs

@ -2,8 +2,7 @@
//! //!
//! This module will provides everything needed to implement the `CallFrame` //! This module will provides everything needed to implement the `CallFrame`
use super::CodeBlock; use crate::vm::CodeBlock;
use crate::JsValue;
use boa_gc::{Finalize, Gc, Trace}; use boa_gc::{Finalize, Gc, Trace};
#[derive(Clone, Debug, Finalize, Trace)] #[derive(Clone, Debug, Finalize, Trace)]
@ -11,7 +10,6 @@ pub struct CallFrame {
pub(crate) prev: Option<Box<Self>>, pub(crate) prev: Option<Box<Self>>,
pub(crate) code: Gc<CodeBlock>, pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize, pub(crate) pc: usize,
pub(crate) this: JsValue,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) catch: Vec<CatchAddresses>, pub(crate) catch: Vec<CatchAddresses>,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]

464
boa_engine/src/vm/code_block.rs

@ -5,15 +5,16 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::{ function::{
arguments::Arguments, Captures, ClosureFunctionSignature, ConstructorKind, Function, arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode,
NativeFunctionSignature, ThisMode,
}, },
generator::{Generator, GeneratorContext, GeneratorState}, generator::{Generator, GeneratorContext, GeneratorState},
}, },
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment, DeclarativeEnvironmentStack}, environments::{BindingLocator, CompileTimeEnvironment},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, object::{
property::{PropertyDescriptor, PropertyKey}, internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PrivateElement,
},
property::PropertyDescriptor,
syntax::ast::node::FormalParameterList, syntax::ast::node::FormalParameterList,
vm::call_frame::GeneratorResumeKind, vm::call_frame::GeneratorResumeKind,
vm::{call_frame::FinallyReturn, CallFrame, Opcode}, vm::{call_frame::FinallyReturn, CallFrame, Opcode},
@ -62,10 +63,6 @@ pub struct CodeBlock {
/// Is this function in strict mode. /// Is this function in strict mode.
pub(crate) strict: bool, pub(crate) strict: bool,
/// Constructor type of this function, or `None` if the function is not constructable.
#[unsafe_ignore_trace]
pub(crate) constructor: Option<ConstructorKind>,
/// \[\[ThisMode\]\] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
@ -90,26 +87,22 @@ pub struct CodeBlock {
pub(crate) num_bindings: usize, pub(crate) num_bindings: usize,
/// Functions inside this function /// Functions inside this function
pub(crate) functions: Vec<Gc<CodeBlock>>, pub(crate) functions: Vec<Gc<Self>>,
/// Indicates if the codeblock contains a lexical name `arguments`
pub(crate) lexical_name_argument: bool,
/// The `arguments` binding location of the function, if set. /// The `arguments` binding location of the function, if set.
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) arguments_binding: Option<BindingLocator>, pub(crate) arguments_binding: Option<BindingLocator>,
/// 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<Cell<Vec<PropertyKey>>>,
/// Compile time environments in this function. /// Compile time environments in this function.
pub(crate) compile_environments: Vec<Gc<Cell<CompileTimeEnvironment>>>, pub(crate) compile_environments: Vec<Gc<Cell<CompileTimeEnvironment>>>,
/// The `[[IsClassConstructor]]` internal slot.
pub(crate) is_class_constructor: bool,
} }
impl CodeBlock { impl CodeBlock {
/// Constructs a new `CodeBlock`. /// Constructs a new `CodeBlock`.
pub fn new(name: Sym, length: u32, strict: bool, constructor: Option<ConstructorKind>) -> Self { pub fn new(name: Sym, length: u32, strict: bool) -> Self {
Self { Self {
code: Vec::new(), code: Vec::new(),
literals: Vec::new(), literals: Vec::new(),
@ -120,13 +113,11 @@ impl CodeBlock {
name, name,
length, length,
strict, strict,
constructor,
this_mode: ThisMode::Global, this_mode: ThisMode::Global,
params: FormalParameterList::default(), params: FormalParameterList::default(),
lexical_name_argument: false,
arguments_binding: None, arguments_binding: None,
computed_field_names: None,
compile_environments: Vec::new(), compile_environments: Vec::new(),
is_class_constructor: false,
} }
} }
@ -202,6 +193,8 @@ impl CodeBlock {
| Opcode::CallWithRest | Opcode::CallWithRest
| Opcode::New | Opcode::New
| Opcode::NewWithRest | Opcode::NewWithRest
| Opcode::SuperCall
| Opcode::SuperCallWithRest
| Opcode::ForInLoopInitIterator | Opcode::ForInLoopInitIterator
| Opcode::ForInLoopNext | Opcode::ForInLoopNext
| Opcode::ConcatToString | Opcode::ConcatToString
@ -254,11 +247,17 @@ impl CodeBlock {
| Opcode::DefineClassGetterByName | Opcode::DefineClassGetterByName
| Opcode::SetPropertySetterByName | Opcode::SetPropertySetterByName
| Opcode::DefineClassSetterByName | Opcode::DefineClassSetterByName
| Opcode::SetPrivateValue | Opcode::AssignPrivateField
| Opcode::SetPrivateField
| Opcode::SetPrivateMethod
| Opcode::SetPrivateSetter | Opcode::SetPrivateSetter
| Opcode::SetPrivateGetter | Opcode::SetPrivateGetter
| Opcode::GetPrivateField | Opcode::GetPrivateField
| Opcode::DeletePropertyByName => { | Opcode::DeletePropertyByName
| Opcode::PushClassFieldPrivate
| Opcode::PushClassPrivateGetter
| Opcode::PushClassPrivateSetter
| Opcode::PushClassPrivateMethod => {
let operand = self.read::<u32>(*pc); let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
format!( format!(
@ -281,6 +280,8 @@ impl CodeBlock {
| Opcode::PushUndefined | Opcode::PushUndefined
| Opcode::PushEmptyObject | Opcode::PushEmptyObject
| Opcode::PushClassPrototype | Opcode::PushClassPrototype
| Opcode::SetClassPrototype
| Opcode::SetHomeObject
| Opcode::Add | Opcode::Add
| Opcode::Sub | Opcode::Sub
| Opcode::Div | Opcode::Div
@ -331,6 +332,7 @@ impl CodeBlock {
| Opcode::FinallyStart | Opcode::FinallyStart
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Super
| Opcode::Return | Opcode::Return
| Opcode::PopEnvironment | Opcode::PopEnvironment
| Opcode::LoopStart | Opcode::LoopStart
@ -352,7 +354,8 @@ impl CodeBlock {
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::Yield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::PushClassComputedFieldName | Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Nop => String::new(), | Opcode::Nop => String::new(),
} }
} }
@ -453,6 +456,10 @@ pub(crate) fn create_function_object(code: Gc<CodeBlock>, context: &mut Context)
let function = Function::Ordinary { let function = Function::Ordinary {
code, code,
environments: context.realm.environments.clone(), environments: context.realm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: Vec::new(),
private_methods: Vec::new(),
}; };
let constructor = let constructor =
@ -547,24 +554,6 @@ pub(crate) fn create_generator_function_object(
constructor constructor
} }
pub(crate) enum FunctionBody {
Ordinary {
code: Gc<CodeBlock>,
environments: DeclarativeEnvironmentStack,
},
Native {
function: NativeFunctionSignature,
},
Closure {
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
Generator {
code: Gc<CodeBlock>,
environments: DeclarativeEnvironmentStack,
},
}
impl JsObject { impl JsObject {
pub(crate) fn call_internal( pub(crate) fn call_internal(
&self, &self,
@ -578,85 +567,80 @@ impl JsObject {
return context.throw_type_error("not a callable function"); return context.throw_type_error("not a callable function");
} }
let mut construct = false; let object = self.borrow();
let function_object = object.as_function().expect("not a function");
let body = {
let object = self.borrow();
let function = object.as_function().expect("not a function");
match function { match function_object {
Function::Native { Function::Native {
function, function,
constructor, constructor,
} => { } => {
if constructor.is_some() { let function = *function;
construct = true; let constructor = *constructor;
} drop(object);
FunctionBody::Native { if constructor.is_some() {
function: *function, 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) (function)(this, args, captures, context)
} }
FunctionBody::Ordinary { Function::Ordinary {
code, code, environments, ..
mut 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); std::mem::swap(&mut environments, &mut context.realm.environments);
let lexical_this_mode = code.this_mode == ThisMode::Lexical; let lexical_this_mode = code.this_mode == ThisMode::Lexical;
let this = if lexical_this_mode { let this = if lexical_this_mode {
if let Some(this) = context.realm.environments.get_last_this() { None
this
} else {
context.global_object().clone().into()
}
} else if code.strict { } else if code.strict {
this.clone() Some(this.clone())
} else if this.is_null_or_undefined() { } else if this.is_null_or_undefined() {
context.global_object().clone().into() Some(context.global_object().clone().into())
} else { } else {
this.to_object(context) Some(
.expect("conversion cannot fail") this.to_object(context)
.into() .expect("conversion cannot fail")
.into(),
)
}; };
if code.params.has_expressions() { if code.params.has_expressions() {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[1].clone(), code.compile_environments[1].clone(),
this.clone(), this,
self.clone(),
None,
lexical_this_mode,
); );
} else { } else {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[0].clone(), 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 { context.vm.push_frame(CallFrame {
prev: None, prev: None,
code, code,
this,
pc: 0, pc: 0,
catch: Vec::new(), catch: Vec::new(),
finally_return: FinallyReturn::None, finally_return: FinallyReturn::None,
finally_jump: Vec::new(), finally_jump: Vec::new(),
pop_on_return: 0, pop_on_return: 0,
loop_env_stack: vec![0], loop_env_stack: Vec::from([0]),
try_env_stack: vec![crate::vm::TryStackEntry { try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0, num_env: 0,
num_loop_stack_entries: 0, num_loop_stack_entries: 0,
}], }]),
param_count, param_count,
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
@ -734,37 +717,46 @@ impl JsObject {
let (result, _) = result?; let (result, _) = result?;
Ok(result) Ok(result)
} }
FunctionBody::Generator { Function::Generator { code, environments } => {
code, let code = code.clone();
mut environments, let mut environments = environments.clone();
} => { drop(object);
std::mem::swap(&mut environments, &mut context.realm.environments); std::mem::swap(&mut environments, &mut context.realm.environments);
let lexical_this_mode = code.this_mode == ThisMode::Lexical; let lexical_this_mode = code.this_mode == ThisMode::Lexical;
let this = if lexical_this_mode { let this = if lexical_this_mode {
if let Some(this) = context.realm.environments.get_last_this() { None
this } else if code.strict {
} else { Some(this.clone())
context.global_object().clone().into() } else if this.is_null_or_undefined() {
} Some(context.global_object().clone().into())
} else if !code.strict && this.is_null_or_undefined() {
context.global_object().clone().into()
} else { } else {
this.clone() Some(
this.to_object(context)
.expect("conversion cannot fail")
.into(),
)
}; };
if code.params.has_expressions() { if code.params.has_expressions() {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[1].clone(), code.compile_environments[1].clone(),
this.clone(), this,
self.clone(),
None,
lexical_this_mode,
); );
} else { } else {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[0].clone(), code.compile_environments[0].clone(),
this.clone(), this,
self.clone(),
None,
lexical_this_mode,
); );
} }
@ -808,17 +800,16 @@ impl JsObject {
let call_frame = CallFrame { let call_frame = CallFrame {
prev: None, prev: None,
code, code,
this,
pc: 0, pc: 0,
catch: Vec::new(), catch: Vec::new(),
finally_return: FinallyReturn::None, finally_return: FinallyReturn::None,
finally_jump: Vec::new(), finally_jump: Vec::new(),
pop_on_return: 0, pop_on_return: 0,
loop_env_stack: vec![0], loop_env_stack: Vec::from([0]),
try_env_stack: vec![crate::vm::TryStackEntry { try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0, num_env: 0,
num_loop_stack_entries: 0, num_loop_stack_entries: 0,
}], }]),
param_count, param_count,
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
@ -871,7 +862,6 @@ impl JsObject {
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
let this_function_object = self.clone(); let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
let create_this = |context| { let create_this = |context| {
let prototype = let prototype =
@ -883,53 +873,47 @@ impl JsObject {
return context.throw_type_error("not a constructor function"); return context.throw_type_error("not a constructor function");
} }
let constructor; let object = self.borrow();
let function_object = object.as_function().expect("not a function");
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")
}
}
};
match body { match function_object {
FunctionBody::Native { function, .. } => match function(this_target, args, context)? { Function::Native {
JsValue::Object(ref o) => Ok(o.clone()), function,
val => { constructor,
if constructor.is_base() || val.is_undefined() { ..
create_this(context) } => {
} else { let function = *function;
context.throw_type_error( let constructor = *constructor;
"Derived constructor can only return an Object or undefined", 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)? { match (function)(this_target, args, captures, context)? {
JsValue::Object(ref o) => Ok(o.clone()), JsValue::Object(ref o) => Ok(o.clone()),
val => { val => {
if constructor.is_base() || val.is_undefined() { if constructor.expect("hmma").is_base() || val.is_undefined() {
create_this(context) create_this(context)
} else { } else {
context.throw_type_error( context.throw_type_error(
@ -939,32 +923,57 @@ impl JsObject {
} }
} }
} }
FunctionBody::Ordinary { Function::Ordinary {
code, 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); 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 <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype = get_prototype_from_constructor(
this_target,
StandardConstructors::object,
context,
)?;
let this = Self::from_proto_and_data(prototype, ObjectData::ordinary());
initialize_instance_elements(&this, self, context)?;
Some(this)
} else {
None
};
// Set computed class field names if they exist. let new_target = this_target.as_object().expect("must be object");
if let Some(fields) = &code.computed_field_names {
for key in fields.borrow().iter().rev() {
context.vm.push(key);
}
}
if code.params.has_expressions() { if code.params.has_expressions() {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[1].clone(), code.compile_environments[1].clone(),
this.clone().into(), this.clone().map(Into::into),
self.clone(),
Some(new_target.clone()),
false,
); );
} else { } else {
context.realm.environments.push_function( context.realm.environments.push_function(
code.num_bindings, code.num_bindings,
code.compile_environments[0].clone(), 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 { context.vm.push_frame(CallFrame {
prev: None, prev: None,
code, code,
this: this.into(),
pc: 0, pc: 0,
catch: Vec::new(), catch: Vec::new(),
finally_return: FinallyReturn::None, finally_return: FinallyReturn::None,
finally_jump: Vec::new(), finally_jump: Vec::new(),
pop_on_return: 0, pop_on_return: 0,
loop_env_stack: vec![0], loop_env_stack: Vec::from([0]),
try_env_stack: vec![crate::vm::TryStackEntry { try_env_stack: Vec::from([crate::vm::TryStackEntry {
num_env: 0, num_env: 0,
num_loop_stack_entries: 0, num_loop_stack_entries: 0,
}], }]),
param_count, param_count,
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
@ -1044,37 +1052,111 @@ impl JsObject {
let result = context.run(); 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 { if has_parameter_expressions {
context.realm.environments.pop(); environment = context.realm.environments.pop();
} }
std::mem::swap(&mut environments, &mut context.realm.environments); std::mem::swap(&mut environments, &mut context.realm.environments);
let (result, _) = result?; let (result, _) = result?;
match result { if let Some(result) = result.as_object() {
JsValue::Object(ref obj) => Ok(obj.clone()), Ok(result.clone())
val => { } else if let Some(this) = this {
if constructor.is_base() || val.is_undefined() { Ok(this)
Ok(frame } else if !result.is_undefined() {
.this context.throw_type_error("Function constructor must not return non-object")
.as_object() } else {
.cloned() let function_env = environment
.expect("13. Assert: Type(thisBinding) is Object.")) .slots()
} else { .expect("must be function environment")
context.throw_type_error( .as_function_slots()
"Derived constructor can only return an Object or undefined", .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") 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(())
}

537
boa_engine/src/vm/mod.rs

@ -3,13 +3,18 @@
//! plus an interpreter to execute those instructions //! plus an interpreter to execute those instructions
use crate::{ use crate::{
builtins::{function::Function, iterable::IteratorRecord, Array, ForInIterator, Number}, builtins::{
object::PrivateElement, function::{ConstructorKind, Function},
property::{DescriptorKind, PropertyDescriptor, PropertyKey}, iterable::IteratorRecord,
Array, ForInIterator, Number,
},
environments::EnvironmentSlots,
object::{JsFunction, JsObject, ObjectData, PrivateElement},
property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey},
value::Numeric, value::Numeric,
vm::{ vm::{
call_frame::CatchAddresses, 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, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
@ -198,20 +203,103 @@ impl Context {
Opcode::PushEmptyObject => self.vm.push(self.construct_object()), Opcode::PushEmptyObject => self.vm.push(self.construct_object()),
Opcode::PushClassPrototype => { Opcode::PushClassPrototype => {
let superclass = self.vm.pop(); let superclass = self.vm.pop();
if superclass.is_null() {
self.vm.push(JsValue::Null);
}
if let Some(superclass) = superclass.as_constructor() { if let Some(superclass) = superclass.as_constructor() {
let proto = superclass.get("prototype", self)?; let proto = superclass.get("prototype", self)?;
if !proto.is_object() && !proto.is_null() { if !proto.is_object() && !proto.is_null() {
return self return self
.throw_type_error("superclass prototype must be an object or null"); .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); self.vm.push(proto);
} else if superclass.is_null() {
self.vm.push(JsValue::Null);
} else { } else {
return self.throw_type_error("superclass must be a constructor"); 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 => { Opcode::PushNewArray => {
let array = Array::array_create(0, None, self) let array = Array::array_create(0, None, self)
.expect("Array creation with 0 length should never fail"); .expect("Array creation with 0 length should never fail");
@ -721,6 +809,13 @@ impl Context {
} else { } else {
object.to_object(self)? 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.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name); let name = self.interner().resolve_expect(name);
object.__define_own_property__( object.__define_own_property__(
@ -777,6 +872,13 @@ impl Context {
} else { } else {
object.to_object(self)? 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)?; let key = key.to_property_key(self)?;
object.__define_own_property__( object.__define_own_property__(
key, key,
@ -817,6 +919,13 @@ impl Context {
let object = self.vm.pop(); let object = self.vm.pop();
let value = self.vm.pop(); let value = self.vm.pop();
let object = object.to_object(self)?; 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.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into(); let name = self.interner().resolve_expect(name).into();
let set = object let set = object
@ -862,6 +971,13 @@ impl Context {
let key = self.vm.pop(); let key = self.vm.pop();
let object = self.vm.pop(); let object = self.vm.pop();
let object = object.to_object(self)?; 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 name = key.to_property_key(self)?;
let set = object let set = object
.__get_own_property__(&name, self)? .__get_own_property__(&name, self)?
@ -907,6 +1023,13 @@ impl Context {
let object = self.vm.pop(); let object = self.vm.pop();
let value = self.vm.pop(); let value = self.vm.pop();
let object = object.to_object(self)?; 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.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into(); let name = self.interner().resolve_expect(name).into();
let get = object let get = object
@ -952,6 +1075,13 @@ impl Context {
let key = self.vm.pop(); let key = self.vm.pop();
let object = self.vm.pop(); let object = self.vm.pop();
let object = object.to_object(self)?; 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 name = key.to_property_key(self)?;
let get = object let get = object
.__get_own_property__(&name, self)? .__get_own_property__(&name, self)?
@ -969,7 +1099,41 @@ impl Context {
self, self,
)?; )?;
} }
Opcode::SetPrivateValue => { Opcode::AssignPrivateField => {
let index = self.vm.read::<u32>();
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::<u32>(); let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize]; let name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop(); let value = self.vm.pop();
@ -985,12 +1149,26 @@ impl Context {
drop(object_borrow_mut); drop(object_borrow_mut);
setter.call(&object.clone().into(), &[value], self)?; setter.call(&object.clone().into(), &[value], self)?;
} else { } else {
object_borrow_mut.set_private_element(name, PrivateElement::Value(value)); object_borrow_mut.set_private_element(name, PrivateElement::Field(value));
} }
} else { } else {
return self.throw_type_error("cannot set private property on non-object"); return self.throw_type_error("cannot set private property on non-object");
} }
} }
Opcode::SetPrivateMethod => {
let index = self.vm.read::<u32>();
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 => { Opcode::SetPrivateSetter => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize]; let name = self.vm.frame().code.names[index as usize];
@ -1025,7 +1203,8 @@ impl Context {
let object_borrow_mut = object.borrow(); let object_borrow_mut = object.borrow();
if let Some(element) = object_borrow_mut.get_private_element(name) { if let Some(element) = object_borrow_mut.get_private_element(name) {
match element { 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 { PrivateElement::Accessor {
getter: Some(getter), getter: Some(getter),
setter: _, setter: _,
@ -1046,25 +1225,111 @@ impl Context {
return self.throw_type_error("cannot read private property from non-object"); return self.throw_type_error("cannot read private property from non-object");
} }
} }
Opcode::PushClassComputedFieldName => { Opcode::PushClassField => {
let object = self.vm.pop(); let field_function_value = self.vm.pop();
let value = self.vm.pop(); let field_name_value = self.vm.pop();
let value = value.to_property_key(self)?; let class_value = self.vm.pop();
let object_obj = object
let field_name_key = field_name_value.to_property_key(self)?;
let field_function_object = field_function_value
.as_object() .as_object()
.expect("can only add field to function object"); .expect("field value must be function object");
let object = object_obj.borrow(); let mut field_function_object_borrow = field_function_object.borrow_mut();
let function = object let field_function = field_function_object_borrow
.as_function() .as_function_mut()
.expect("can only add field to function object"); .expect("field value must be function object");
if let Function::Ordinary { code, .. } = function { let class_object = class_value
let code_b = code .as_object()
.computed_field_names .expect("class must be function object");
.as_ref() field_function.set_home_object(class_object.clone());
.expect("class constructor must have fields"); class_object
let mut fields_mut = code_b.borrow_mut(); .borrow_mut()
fields_mut.push(value); .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::<u32>();
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::<u32>();
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::<u32>();
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::<u32>();
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 => { Opcode::DeletePropertyByName => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
@ -1204,8 +1469,203 @@ impl Context {
.expect("finally jump must exist here") = Some(address); .expect("finally jump must exist here") = Some(address);
} }
Opcode::This => { Opcode::This => {
let this = self.vm.frame().this.clone(); let env = self.realm.environments.get_this_environment();
self.vm.push(this); 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::<u32>();
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::<u32>();
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 => { Opcode::Case => {
let address = self.vm.read::<u32>(); let address = self.vm.read::<u32>();
@ -1480,20 +1940,9 @@ impl Context {
let compile_environment = self.vm.frame().code.compile_environments let compile_environment = self.vm.frame().code.compile_environments
[compile_environments_index as usize] [compile_environments_index as usize]
.clone(); .clone();
self.realm
let constructor = self.vm.frame().code.constructor; .environments
let is_lexical = self.vm.frame().code.this_mode.is_lexical(); .push_function_inherit(num_bindings as usize, compile_environment);
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,
);
} }
Opcode::PopEnvironment => { Opcode::PopEnvironment => {
self.realm.environments.pop(); self.realm.environments.pop();

138
boa_engine/src/vm/opcode.rs

@ -142,9 +142,23 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: superclass **=>** superclass.prototype /// Stack: class, superclass **=>** class, superclass.prototype
PushClassPrototype, 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. /// Push an empty array value on the stack.
/// ///
/// Operands: /// Operands:
@ -624,16 +638,34 @@ pub enum Opcode {
/// Stack: object, key, value **=>** /// Stack: object, key, value **=>**
DefineClassSetterByValue, 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` /// Like `#name = value`
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
/// ///
/// Stack: object, value **=>** /// 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() {}` /// Like `set #name() {}`
/// ///
@ -642,7 +674,7 @@ pub enum Opcode {
/// Stack: object, value **=>** /// Stack: object, value **=>**
SetPrivateSetter, 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() {}` /// Like `get #name() {}`
/// ///
@ -660,12 +692,40 @@ pub enum Opcode {
/// Stack: object **=>** value /// Stack: object **=>** value
GetPrivateField, GetPrivateField,
/// Push a computed class field name to a class constructor object. /// Push a field to a class.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: value, object **=>** /// Stack: class, field_name, field_function **=>**
PushClassComputedFieldName, 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. /// Deletes a property by name of an object.
/// ///
@ -803,6 +863,34 @@ pub enum Opcode {
/// Stack: **=>** this /// Stack: **=>** this
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, /// 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. /// if true jumps to address, otherwise push the second pop'ed value.
/// ///
@ -1079,6 +1167,8 @@ impl Opcode {
Self::PushLiteral => "PushLiteral", Self::PushLiteral => "PushLiteral",
Self::PushEmptyObject => "PushEmptyObject", Self::PushEmptyObject => "PushEmptyObject",
Self::PushClassPrototype => "PushClassPrototype", Self::PushClassPrototype => "PushClassPrototype",
Self::SetClassPrototype => "SetClassPrototype",
Self::SetHomeObject => "SetHomeObject",
Self::PushNewArray => "PushNewArray", Self::PushNewArray => "PushNewArray",
Self::PushValueToArray => "PushValueToArray", Self::PushValueToArray => "PushValueToArray",
Self::PushElisionToArray => "PushElisionToArray", Self::PushElisionToArray => "PushElisionToArray",
@ -1143,11 +1233,17 @@ impl Opcode {
Self::DefineClassSetterByName => "DefineClassSetterByName", Self::DefineClassSetterByName => "DefineClassSetterByName",
Self::SetPropertySetterByValue => "SetPropertySetterByValue", Self::SetPropertySetterByValue => "SetPropertySetterByValue",
Self::DefineClassSetterByValue => "DefineClassSetterByValue", Self::DefineClassSetterByValue => "DefineClassSetterByValue",
Self::SetPrivateValue => "SetPrivateValue", Self::AssignPrivateField => "AssignPrivateField",
Self::SetPrivateField => "SetPrivateValue",
Self::SetPrivateMethod => "SetPrivateMethod",
Self::SetPrivateSetter => "SetPrivateSetter", Self::SetPrivateSetter => "SetPrivateSetter",
Self::SetPrivateGetter => "SetPrivateGetter", Self::SetPrivateGetter => "SetPrivateGetter",
Self::GetPrivateField => "GetPrivateByName", Self::GetPrivateField => "GetPrivateField",
Self::PushClassComputedFieldName => "PushClassComputedFieldName", Self::PushClassField => "PushClassField",
Self::PushClassFieldPrivate => "PushClassFieldPrivate",
Self::PushClassPrivateGetter => "PushClassPrivateGetter",
Self::PushClassPrivateSetter => "PushClassPrivateSetter",
Self::PushClassPrivateMethod => "PushClassPrivateMethod",
Self::DeletePropertyByName => "DeletePropertyByName", Self::DeletePropertyByName => "DeletePropertyByName",
Self::DeletePropertyByValue => "DeletePropertyByValue", Self::DeletePropertyByValue => "DeletePropertyByValue",
Self::CopyDataProperties => "CopyDataProperties", Self::CopyDataProperties => "CopyDataProperties",
@ -1166,6 +1262,10 @@ impl Opcode {
Self::FinallySetJump => "FinallySetJump", Self::FinallySetJump => "FinallySetJump",
Self::ToBoolean => "ToBoolean", Self::ToBoolean => "ToBoolean",
Self::This => "This", Self::This => "This",
Self::Super => "Super",
Self::SuperCall => "SuperCall",
Self::SuperCallWithRest => "SuperCallWithRest",
Self::SuperCallDerived => "SuperCallDerived",
Self::Case => "Case", Self::Case => "Case",
Self::Default => "Default", Self::Default => "Default",
Self::GetFunction => "GetFunction", Self::GetFunction => "GetFunction",
@ -1300,6 +1400,10 @@ impl Opcode {
Self::FinallySetJump => "INST - FinallySetJump", Self::FinallySetJump => "INST - FinallySetJump",
Self::ToBoolean => "INST - ToBoolean", Self::ToBoolean => "INST - ToBoolean",
Self::This => "INST - This", 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::Case => "INST - Case",
Self::Default => "INST - Default", Self::Default => "INST - Default",
Self::GetFunction => "INST - GetFunction", Self::GetFunction => "INST - GetFunction",
@ -1335,17 +1439,25 @@ impl Opcode {
Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Self::Nop => "INST - Nop", Self::Nop => "INST - Nop",
Self::PushClassPrototype => "INST - PushClassPrototype", Self::PushClassPrototype => "INST - PushClassPrototype",
Self::SetClassPrototype => "INST - SetClassPrototype",
Self::SetHomeObject => "INST - SetHomeObject",
Self::DefineClassMethodByName => "INST - DefineClassMethodByName", Self::DefineClassMethodByName => "INST - DefineClassMethodByName",
Self::DefineClassMethodByValue => "INST - DefineClassMethodByValue", Self::DefineClassMethodByValue => "INST - DefineClassMethodByValue",
Self::DefineClassGetterByName => "INST - DefineClassGetterByName", Self::DefineClassGetterByName => "INST - DefineClassGetterByName",
Self::DefineClassGetterByValue => "INST - DefineClassGetterByValue", Self::DefineClassGetterByValue => "INST - DefineClassGetterByValue",
Self::DefineClassSetterByName => "INST - DefineClassSetterByName", Self::DefineClassSetterByName => "INST - DefineClassSetterByName",
Self::DefineClassSetterByValue => "INST - DefineClassSetterByValue", 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::SetPrivateSetter => "INST - SetPrivateSetter",
Self::SetPrivateGetter => "INST - SetPrivateGetter", Self::SetPrivateGetter => "INST - SetPrivateGetter",
Self::GetPrivateField => "INST - GetPrivateField", 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", Self::ToPropertyKey => "INST - ToPropertyKey",
} }
} }

Loading…
Cancel
Save