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.
// Parse the script body (11.a - 11.d)
// TODO: Implement errors for 11.e - 11.h
let parse_result = if strict {
context.parse_strict(x.as_bytes())
} else {
context.parse(x.as_bytes())
};
let body = match parse_result.map_err(|e| e.to_string()) {
// Parse the script body and handle early errors (6 - 11)
let body = match context.parse_eval(x.as_bytes(), direct, strict) {
Ok(body) => body,
Err(e) => return context.throw_syntax_error(e),
Err(e) => return context.throw_syntax_error(e.to_string()),
};
// 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.

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

@ -19,7 +19,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object,
ObjectData,
},
object::{ConstructorBuilder, FunctionBuilder, Ref, RefMut},
object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser},
@ -108,7 +108,13 @@ impl ThisMode {
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
/// Represents the `[[ConstructorKind]]` internal slot of function objects.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ConstructorKind {
Base,
Derived,
@ -126,6 +132,18 @@ impl ConstructorKind {
}
}
/// Record containing the field definition of classes.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-classfielddefinition-record-specification-type
#[derive(Clone, Debug, Trace, Finalize)]
pub enum ClassFieldDefinition {
Public(PropertyKey, JsFunction),
Private(Sym, JsFunction),
}
/// Wrapper for `Gc<GcCell<dyn NativeObject>>` that allows passing additional
/// captures through a `Copy` closure.
///
@ -191,6 +209,19 @@ pub enum Function {
Ordinary {
code: Gc<crate::vm::CodeBlock>,
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 {
code: Gc<crate::vm::CodeBlock>,
@ -207,14 +238,85 @@ impl fmt::Debug for Function {
impl Function {
/// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool {
self.constructor().is_some()
match self {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => {
constructor.is_some()
}
Self::Generator { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
}
}
/// Returns the constructor kind if the function is constructable, or `None` otherwise.
pub fn constructor(&self) -> Option<ConstructorKind> {
match self {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor,
Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor,
/// Returns true if the function object is a derived constructor.
pub(crate) fn is_derived_constructor(&self) -> bool {
if let Self::Ordinary {
constructor_kind, ..
} = self
{
constructor_kind.is_derived()
} else {
false
}
}
/// Returns a reference to the function `[[HomeObject]]` slot if present.
pub(crate) fn get_home_object(&self) -> Option<&JsObject> {
if let Self::Ordinary { home_object, .. } = self {
home_object.as_ref()
} else {
None
}
}
/// Sets the `[[HomeObject]]` slot if present.
pub(crate) fn set_home_object(&mut self, object: JsObject) {
if let Self::Ordinary { home_object, .. } = self {
*home_object = Some(object);
}
}
/// Returns the values of the `[[Fields]]` internal slot.
pub(crate) fn get_fields(&self) -> &[ClassFieldDefinition] {
if let Self::Ordinary { fields, .. } = self {
fields
} else {
&[]
}
}
/// Pushes a value to the `[[Fields]]` internal slot if present.
pub(crate) fn push_field(&mut self, key: PropertyKey, value: JsFunction) {
if let Self::Ordinary { fields, .. } = self {
fields.push(ClassFieldDefinition::Public(key, value));
}
}
/// Pushes a private value to the `[[Fields]]` internal slot if present.
pub(crate) fn push_field_private(&mut self, key: Sym, value: JsFunction) {
if let Self::Ordinary { fields, .. } = self {
fields.push(ClassFieldDefinition::Private(key, value));
}
}
/// Returns the values of the `[[PrivateMethods]]` internal slot.
pub(crate) fn get_private_methods(&self) -> &[(Sym, PrivateElement)] {
if let Self::Ordinary {
private_methods, ..
} = self
{
private_methods
} else {
&[]
}
}
/// Pushes a private method to the `[[PrivateMethods]]` internal slot if present.
pub(crate) fn push_private_method(&mut self, name: Sym, method: PrivateElement) {
if let Self::Ordinary {
private_methods, ..
} = self
{
private_methods.push((name, method));
}
}
}

309
boa_engine/src/bytecompiler.rs

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

23
boa_engine/src/context/mod.rs

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

4
boa_engine/src/environments/mod.rs

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

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_interner::Sym;
use rustc_hash::FxHashSet;
@ -26,12 +26,161 @@ use rustc_hash::FxHashSet;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment {
bindings: Cell<Vec<Option<JsValue>>>,
this: Option<JsValue>,
compile: Gc<Cell<CompileTimeEnvironment>>,
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 {
/// Returns the internal slot data of the current environment.
pub(crate) fn slots(&self) -> Option<&EnvironmentSlots> {
self.slots.as_ref()
}
/// Get the binding value from the environment by it's index.
///
/// # Panics
@ -79,9 +228,9 @@ impl DeclarativeEnvironmentStack {
Self {
stack: vec![Gc::new(DeclarativeEnvironment {
bindings: Cell::new(Vec::new()),
this: None,
compile: global_compile_environment,
poisoned: Cell::new(false),
slots: Some(EnvironmentSlots::Global),
})],
}
}
@ -91,7 +240,7 @@ impl DeclarativeEnvironmentStack {
/// This is only useful when compiled bindings are added after the initial compilation (eval).
pub(crate) fn extend_outer_function_environment(&mut self) {
for env in self.stack.iter().rev() {
if env.this.is_some() {
if let Some(EnvironmentSlots::Function(_)) = env.slots {
let compile_bindings_number = env.compile.borrow().num_bindings();
let mut bindings_mut = env.bindings.borrow_mut();
@ -163,15 +312,34 @@ impl DeclarativeEnvironmentStack {
}
}
/// Get the `this` value of the most outer function environment.
/// `GetThisEnvironment`
///
/// Returns the environment that currently provides a `this` biding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getthisenvironment
///
/// # Panics
///
/// Panics if no environment exists on the stack.
#[inline]
pub(crate) fn get_last_this(&self) -> Option<JsValue> {
pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots {
for env in self.stack.iter().rev() {
if let Some(this) = &env.this {
return Some(this.clone());
if let Some(slots) = &env.slots {
match slots {
EnvironmentSlots::Function(function_env) => {
if function_env.borrow().has_this_binding() {
return slots;
}
}
EnvironmentSlots::Global => return slots,
}
}
}
None
panic!("global environment must exist")
}
/// Push a declarative environment on the environments stack.
@ -195,9 +363,9 @@ impl DeclarativeEnvironmentStack {
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
this: None,
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: None,
}));
}
@ -211,29 +379,78 @@ impl DeclarativeEnvironmentStack {
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
this: JsValue,
this: Option<JsValue>,
function_object: JsObject,
new_target: Option<JsObject>,
lexical: bool,
) {
let poisoned = self
let outer = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow()
.to_owned();
.expect("global environment must always exist");
let poisoned = outer.poisoned.borrow().to_owned();
let this_binding_status = if lexical {
ThisBindingStatus::Lexical
} else if this.is_some() {
ThisBindingStatus::Initialized
} else {
ThisBindingStatus::Uninitialized
};
let this = if let Some(this) = this {
this
} else {
JsValue::Null
};
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: Some(EnvironmentSlots::Function(Cell::new(FunctionSlots {
this,
this_binding_status,
function_object,
new_target,
}))),
}));
}
/// Push a function environment that inherits it's internal slots from the outer environment.
///
/// # Panics
///
/// Panics if no environment exists on the stack.
pub(crate) fn push_function_inherit(
&mut self,
num_bindings: usize,
compile_environment: Gc<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 {
bindings: Cell::new(vec![None; num_bindings]),
this: Some(this),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots,
}));
}
/// Pop environment from the environments stack.
#[inline]
pub(crate) fn pop(&mut self) {
pub(crate) fn pop(&mut self) -> Gc<DeclarativeEnvironment> {
debug_assert!(self.stack.len() > 1);
self.stack.pop();
self.stack
.pop()
.expect("environment stack is cannot be empty")
}
/// Get the most outer environment.

29
boa_engine/src/object/jsobject.rs

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

5
boa_engine/src/object/mod.rs

@ -124,8 +124,9 @@ pub struct Object {
/// The representation of private object elements.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum PrivateElement {
Value(JsValue),
pub enum PrivateElement {
Field(JsValue),
Method(JsObject),
Accessor {
getter: Option<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,
object::PropertyName,
statement_list::StatementList,
Identifier, Node,
ContainsSymbol, Identifier, Node,
};
use boa_interner::{Interner, Sym, ToInternedString};
@ -229,6 +229,30 @@ impl Declaration {
Self::Pattern(pattern) => pattern.init(),
}
}
/// Returns `true` if the node contains the given token.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
match self {
Self::Identifier { init, .. } => {
if let Some(node) = init {
if node.contains(symbol) {
return true;
}
}
}
Self::Pattern(pattern) => {
if pattern.contains(symbol) {
return true;
}
}
}
false
}
}
/// `DeclarationPattern` represents an object or array binding pattern.
@ -383,6 +407,99 @@ impl DeclarationPattern {
}
false
}
/// Returns `true` if the node contains the given token.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
match self {
DeclarationPattern::Object(object) => {
if let Some(node) = object.init() {
if node.contains(symbol) {
return true;
}
}
for binding in &object.bindings {
match binding {
BindingPatternTypeObject::SingleName {
default_init: Some(node),
..
} => {
if node.contains(symbol) {
return true;
}
}
BindingPatternTypeObject::RestGetConstField {
get_const_field, ..
} => {
if get_const_field.obj().contains(symbol) {
return true;
}
}
BindingPatternTypeObject::BindingPattern {
pattern,
default_init,
..
} => {
if let Some(node) = default_init {
if node.contains(symbol) {
return true;
}
}
if pattern.contains(symbol) {
return true;
}
}
_ => {}
}
}
}
DeclarationPattern::Array(array) => {
if let Some(node) = array.init() {
if node.contains(symbol) {
return true;
}
}
for binding in array.bindings() {
match binding {
BindingPatternTypeArray::SingleName {
default_init: Some(node),
..
} => {
if node.contains(symbol) {
return true;
}
}
BindingPatternTypeArray::GetField { get_field }
| BindingPatternTypeArray::GetFieldRest { get_field } => {
if get_field.obj().contains(symbol)
|| get_field.field().contains(symbol)
{
return true;
}
}
BindingPatternTypeArray::GetConstField { get_const_field }
| BindingPatternTypeArray::GetConstFieldRest { get_const_field } => {
if get_const_field.obj().contains(symbol) {
return true;
}
}
BindingPatternTypeArray::BindingPattern { pattern }
| BindingPatternTypeArray::BindingPatternRest { pattern } => {
if pattern.contains(symbol) {
return true;
}
}
_ => {}
}
}
}
}
false
}
}
/// `DeclarationPatternObject` represents an object binding pattern.

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_field;
pub mod get_private_field;
pub mod get_super_field;
pub use self::{get_const_field::GetConstField, get_field::GetField};
pub use self::{
get_const_field::GetConstField, get_field::GetField, get_private_field::GetPrivateField,
get_super_field::GetSuperField,
};
#[cfg(test)]
mod tests;

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

@ -17,6 +17,7 @@ pub mod operator;
pub mod return_smt;
pub mod spread;
pub mod statement_list;
pub mod super_call;
pub mod switch;
pub mod template;
pub mod throw;
@ -35,7 +36,7 @@ pub use self::{
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList,
DeclarationPattern, FunctionDecl, FunctionExpr,
},
field::{get_private_field::GetPrivateField, GetConstField, GetField},
field::{get_private_field::GetPrivateField, GetConstField, GetField, GetSuperField},
identifier::Identifier,
iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
new::New,
@ -46,6 +47,7 @@ pub use self::{
return_smt::Return,
spread::Spread,
statement_list::StatementList,
super_call::SuperCall,
switch::{Case, Switch},
template::{TaggedTemplate, TemplateLit},
throw::Throw,
@ -55,6 +57,7 @@ use self::{
declaration::class_decl::ClassElement,
iteration::IterableLoopInitializer,
object::{MethodDefinition, PropertyDefinition},
operator::assign::AssignTarget,
};
pub(crate) use self::parameters::FormalParameterListFlags;
@ -146,6 +149,9 @@ pub enum Node {
/// Provides access to object fields. [More information](./declaration/struct.GetField.html).
GetField(GetField),
/// Provides access to super fields. [More information](./declaration/struct.GetSuperField.html).
GetSuperField(GetSuperField),
/// A `for` statement. [More information](./iteration/struct.ForLoop.html).
ForLoop(ForLoop),
@ -240,6 +246,9 @@ pub enum Node {
/// A class declaration. [More information](./declaration/struct.class_decl.Class.html).
ClassExpr(Class),
/// A call of the super constructor. [More information](./super_call/struct.SuperCall.html).
SuperCall(SuperCall),
}
impl From<Const> for Node {
@ -314,6 +323,9 @@ impl Node {
get_private_field.to_interned_string(interner)
}
Self::GetField(ref get_field) => get_field.to_interned_string(interner),
Self::GetSuperField(ref get_super_field) => {
get_super_field.to_interned_string(interner)
}
Self::WhileLoop(ref while_loop) => while_loop.to_indented_string(interner, indentation),
Self::DoWhileLoop(ref do_while) => do_while.to_indented_string(interner, indentation),
Self::If(ref if_smt) => if_smt.to_indented_string(interner, indentation),
@ -345,6 +357,7 @@ impl Node {
Self::AsyncGeneratorDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation),
Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation),
Self::SuperCall(ref super_call) => super_call.to_interned_string(interner),
}
}
@ -880,6 +893,361 @@ impl Node {
}
false
}
/// Returns `true` if the node contains the given token.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
pub(crate) fn contains(&self, symbol: ContainsSymbol) -> bool {
match self {
Node::ArrayDecl(array) => {
for node in array.as_ref() {
if node.contains(symbol) {
return true;
}
}
}
Node::Assign(assign) => {
match assign.lhs() {
AssignTarget::GetPrivateField(field) => {
if field.obj().contains(symbol) {
return true;
}
}
AssignTarget::GetConstField(field) => {
if field.obj().contains(symbol) {
return true;
}
}
AssignTarget::GetField(field) => {
if field.obj().contains(symbol) || field.field().contains(symbol) {
return true;
}
}
AssignTarget::DeclarationPattern(pattern) => {
if pattern.contains(symbol) {
return true;
}
}
AssignTarget::Identifier(_) => {}
}
if assign.rhs().contains(symbol) {
return true;
}
}
Node::AwaitExpr(expr) => {
if expr.expr().contains(symbol) {
return true;
}
}
Node::BinOp(bin_op) => {
if bin_op.lhs().contains(symbol) || bin_op.rhs().contains(symbol) {
return true;
}
}
Node::Block(block) => {
for node in block.items() {
if node.contains(symbol) {
return true;
}
}
}
Node::Call(call) => {
if call.expr().contains(symbol) {
return true;
}
for node in call.args() {
if node.contains(symbol) {
return true;
}
}
}
Node::ConditionalOp(conditional) => {
if conditional.cond().contains(symbol)
|| conditional.if_true().contains(symbol)
|| conditional.if_false().contains(symbol)
{
return true;
}
}
Node::ConstDeclList(decl_list)
| Node::LetDeclList(decl_list)
| Node::VarDeclList(decl_list) => match decl_list {
DeclarationList::Const(declarations)
| DeclarationList::Let(declarations)
| DeclarationList::Var(declarations) => {
for declaration in declarations.iter() {
if declaration.contains(symbol) {
return true;
}
}
}
},
Node::DoWhileLoop(do_while_loop) => {
if do_while_loop.cond().contains(symbol) || do_while_loop.body().contains(symbol) {
return true;
}
}
Node::GetConstField(field) => {
if field.obj().contains(symbol) {
return true;
}
}
Node::GetPrivateField(field) => {
if field.obj().contains(symbol) {
return true;
}
}
Node::GetField(field) => {
if field.obj().contains(symbol) || field.field().contains(symbol) {
return true;
}
}
Node::ForLoop(for_loop) => {
if let Some(node) = for_loop.init() {
if node.contains(symbol) {
return true;
}
}
if let Some(node) = for_loop.condition() {
if node.contains(symbol) {
return true;
}
}
if let Some(node) = for_loop.final_expr() {
if node.contains(symbol) {
return true;
}
}
if for_loop.body().contains(symbol) {
return true;
}
}
Node::ForInLoop(for_in_loop) => {
match for_in_loop.init() {
IterableLoopInitializer::Var(declaration)
| IterableLoopInitializer::Let(declaration)
| IterableLoopInitializer::Const(declaration) => {
if declaration.contains(symbol) {
return true;
}
}
IterableLoopInitializer::DeclarationPattern(pattern) => {
if pattern.contains(symbol) {
return true;
}
}
IterableLoopInitializer::Identifier(_) => {}
}
if for_in_loop.body().contains(symbol) || for_in_loop.expr().contains(symbol) {
return true;
}
}
Node::ForOfLoop(for_of_loop) => {
match for_of_loop.init() {
IterableLoopInitializer::Var(declaration)
| IterableLoopInitializer::Let(declaration)
| IterableLoopInitializer::Const(declaration) => {
if declaration.contains(symbol) {
return true;
}
}
IterableLoopInitializer::DeclarationPattern(pattern) => {
if pattern.contains(symbol) {
return true;
}
}
IterableLoopInitializer::Identifier(_) => {}
}
if for_of_loop.body().contains(symbol) || for_of_loop.iterable().contains(symbol) {
return true;
}
}
Node::If(if_node) => {
if if_node.cond().contains(symbol) || if_node.body().contains(symbol) {
return true;
}
if let Some(node) = if_node.else_node() {
if node.contains(symbol) {
return true;
}
}
}
Node::New(new) => {
if new.call().expr().contains(symbol) {
return true;
}
for node in new.call().args() {
if node.contains(symbol) {
return true;
}
}
}
Node::Return(expr) => {
if let Some(expr) = expr.expr() {
if expr.contains(symbol) {
return true;
}
}
}
Node::Switch(switch) => {
if switch.val().contains(symbol) {
return true;
}
for case in switch.cases() {
if case.condition().contains(symbol) {
return true;
}
for node in case.body().items() {
if node.contains(symbol) {
return true;
}
}
}
if let Some(default) = switch.default() {
for node in default {
if node.contains(symbol) {
return true;
}
}
}
}
Node::Spread(spread) => {
if spread.val().contains(symbol) {
return true;
}
}
Node::TaggedTemplate(template) => {
if template.tag().contains(symbol) {
return true;
}
for node in template.exprs() {
if node.contains(symbol) {
return true;
}
}
}
Node::TemplateLit(template) => {
for element in template.elements() {
if let template::TemplateElement::Expr(node) = element {
if node.contains(symbol) {
return true;
}
}
}
}
Node::Throw(expr) => {
if expr.expr().contains(symbol) {
return true;
}
}
Node::Try(try_node) => {
for node in try_node.block().items() {
if node.contains(symbol) {
return true;
}
}
if let Some(catch) = try_node.catch() {
if let Some(declaration) = catch.parameter() {
if declaration.contains(symbol) {
return true;
}
}
for node in catch.block().items() {
if node.contains(symbol) {
return true;
}
}
}
}
Node::UnaryOp(unary) => {
if unary.target().contains(symbol) {
return true;
}
}
Node::WhileLoop(while_loop) => {
if while_loop.cond().contains(symbol) || while_loop.body().contains(symbol) {
return true;
}
}
Node::SuperCall(_) if symbol == ContainsSymbol::SuperCall => return true,
Node::GetSuperField(_) if symbol == ContainsSymbol::SuperProperty => return true,
Node::ArrowFunctionDecl(arrow) => {
for parameter in arrow.params().parameters.iter() {
if parameter.declaration().contains(symbol) {
return true;
}
}
for node in arrow.body().items() {
if node.contains(symbol) {
return true;
}
}
}
Node::Object(object) => {
for property in object.properties() {
match property {
PropertyDefinition::IdentifierReference(_) => {}
PropertyDefinition::Property(name, init) => {
if let Some(node) = name.computed() {
if node.contains(symbol) {
return true;
}
}
if init.contains(symbol) {
return true;
}
}
PropertyDefinition::SpreadObject(spread) => {
if spread.contains(symbol) {
return true;
}
}
PropertyDefinition::MethodDefinition(_, name) => {
if let Some(node) = name.computed() {
if node.contains(symbol) {
return true;
}
}
}
}
}
}
Node::ClassDecl(class) | Node::ClassExpr(class) => {
if let Some(node) = class.super_ref() {
if node.contains(symbol) {
return true;
}
}
for element in class.elements() {
match element {
ClassElement::MethodDefinition(name, _)
| ClassElement::StaticMethodDefinition(name, _)
| ClassElement::FieldDefinition(name, _)
| ClassElement::StaticFieldDefinition(name, _) => {
if let Some(node) = name.computed() {
if node.contains(symbol) {
return true;
}
}
}
_ => {}
}
}
}
_ => {}
}
false
}
}
/// Represents the possible symbols that can be use the the `Node.contains` function.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum ContainsSymbol {
SuperProperty,
SuperCall,
}
impl ToInternedString for Node {
@ -946,3 +1314,44 @@ fn test_formatting(source: &'static str) {
panic!("parsing test did not give the correct result (see above)");
}
}
/// Helper function to check if a function contains a super call or super property access.
pub(crate) fn function_contains_super(
body: &StatementList,
parameters: &FormalParameterList,
) -> bool {
for param in parameters.parameters.iter() {
if param.declaration().contains(ContainsSymbol::SuperCall)
|| param.declaration().contains(ContainsSymbol::SuperProperty)
{
return true;
}
}
for node in body.items() {
if node.contains(ContainsSymbol::SuperCall) || node.contains(ContainsSymbol::SuperProperty)
{
return true;
}
}
false
}
/// Returns `true` if the function parameters or body contain a direct `super` call.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper
pub(crate) fn has_direct_super(body: &StatementList, parameters: &FormalParameterList) -> bool {
for param in parameters.parameters.iter() {
if param.declaration().contains(ContainsSymbol::SuperCall) {
return true;
}
}
for node in body.items() {
if node.contains(ContainsSymbol::SuperCall) {
return true;
}
}
false
}

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

@ -3,7 +3,7 @@
use crate::syntax::ast::{
node::{
declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr,
FunctionExpr, GeneratorExpr, Node,
FormalParameterList, FunctionExpr, GeneratorExpr, Node, StatementList,
},
Const,
};
@ -321,6 +321,32 @@ pub enum MethodDefinition {
Async(AsyncFunctionExpr),
}
impl MethodDefinition {
/// Return the body of the method.
pub(crate) fn body(&self) -> &StatementList {
match self {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => expr.body(),
MethodDefinition::Generator(expr) => expr.body(),
MethodDefinition::AsyncGenerator(expr) => expr.body(),
MethodDefinition::Async(expr) => expr.body(),
}
}
/// Return the parameters of the method.
pub(crate) fn parameters(&self) -> &FormalParameterList {
match self {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => expr.parameters(),
MethodDefinition::Generator(expr) => expr.parameters(),
MethodDefinition::AsyncGenerator(expr) => expr.parameters(),
MethodDefinition::Async(expr) => expr.parameters(),
}
}
}
/// `PropertyName` can be either a literal or computed.
///
/// More information:
@ -348,6 +374,7 @@ pub enum PropertyName {
}
impl PropertyName {
/// Returns the literal property name if it exists.
pub(in crate::syntax) fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
@ -356,6 +383,16 @@ impl PropertyName {
}
}
/// Returns the expression node if the property name is computed.
pub(in crate::syntax) fn computed(&self) -> Option<&Node> {
if let Self::Computed(node) = self {
Some(node)
} else {
None
}
}
/// Returns either the literal property name or the computed const string property name.
pub(in crate::syntax) fn prop_name(&self) -> Option<Sym> {
match self {
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::{
node::{
field::{get_private_field::GetPrivateField, GetConstField, GetField},
Call, New, Node,
Call, GetSuperField, New, Node,
},
Keyword, Punctuator,
},
@ -66,7 +66,7 @@ where
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let mut lhs = match token.kind() {
TokenKind::Keyword((Keyword::New, true)) => {
TokenKind::Keyword((Keyword::New | Keyword::Super, true)) => {
return Err(ParseError::general(
"keyword must not contain escaped characters",
token.span().start(),
@ -86,6 +86,55 @@ where
Node::from(New::from(call_node))
}
TokenKind::Keyword((Keyword::Super, _)) => {
cursor.next(interner).expect("token disappeared");
let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() {
TokenKind::Punctuator(Punctuator::Dot) => {
let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?;
let field = match token.kind() {
TokenKind::Identifier(name) => GetSuperField::from(*name),
TokenKind::Keyword((kw, _)) => GetSuperField::from(kw.to_sym(interner)),
TokenKind::BooleanLiteral(true) => {
GetSuperField::from(Keyword::True.to_sym(interner))
}
TokenKind::BooleanLiteral(false) => {
GetSuperField::from(Keyword::False.to_sym(interner))
}
TokenKind::NullLiteral => {
GetSuperField::from(Keyword::Null.to_sym(interner))
}
TokenKind::PrivateIdentifier(_) => {
return Err(ParseError::general(
"unexpected private identifier",
token.span().start(),
));
}
_ => {
return Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
"expected super property",
))
}
};
field.into()
}
TokenKind::Punctuator(Punctuator::OpenBracket) => {
let expr = Expression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBracket, "super property", interner)?;
GetSuperField::from(expr).into()
}
_ => {
return Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
"expected super property",
))
}
}
}
_ => PrimaryExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
};

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

@ -12,11 +12,15 @@ mod call;
mod member;
mod template;
use self::{call::CallExpression, member::MemberExpression};
use crate::syntax::{
ast::{Node, Punctuator},
ast::{node::SuperCall, Keyword, Node, Punctuator},
lexer::{InputElement, TokenKind},
parser::{AllowAwait, AllowYield, Cursor, ParseResult, TokenParser},
parser::{
expression::left_hand_side::{
arguments::Arguments, call::CallExpression, member::MemberExpression,
},
AllowAwait, AllowYield, Cursor, ParseResult, TokenParser,
},
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
@ -64,6 +68,19 @@ where
cursor.set_goal(InputElement::TemplateTail);
if let Some(next) = cursor.peek(0, interner)? {
if let TokenKind::Keyword((Keyword::Super, _)) = next.kind() {
if let Some(next) = cursor.peek(1, interner)? {
if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
cursor.next(interner).expect("token disappeared");
let args = Arguments::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
return Ok(SuperCall::new(args).into());
}
}
}
}
// TODO: Implement NewExpression: new MemberExpression
let lhs = MemberExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;

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

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

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

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

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

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

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

@ -14,7 +14,7 @@ mod tests;
use crate::{
syntax::{
ast::{
node::{FormalParameterList, StatementList},
node::{ContainsSymbol, FormalParameterList, StatementList},
Position,
},
lexer::TokenKind,
@ -135,65 +135,65 @@ impl<R> Parser<R> {
where
R: Read,
{
let statement_list = Script.parse(&mut self.cursor, context.interner_mut())?;
// It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
let mut var_declared_names = FxHashSet::default();
statement_list.var_declared_names_new(&mut var_declared_names);
let lexically_declared_names = statement_list.lexically_declared_names();
let mut lexically_declared_names_map: FxHashMap<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),
));
Script::new(false).parse(&mut self.cursor, context)
}
pub(crate) fn parse_eval(
&mut self,
direct: bool,
context: &mut Context,
) -> Result<StatementList, ParseError>
where
R: Read,
{
let (in_method, in_derived_constructor) = if let Some(function_env) = context
.realm
.environments
.get_this_environment()
.as_function_slots()
{
let function_env_borrow = function_env.borrow();
let has_super_binding = function_env_borrow.has_super_binding();
let function_object = function_env_borrow.function_object().borrow();
(
has_super_binding,
function_object
.as_function()
.expect("must be function object")
.is_derived_constructor(),
)
} else {
(false, false)
};
let statement_list = Script::new(direct).parse(&mut self.cursor, context)?;
let mut contains_super_property = false;
let mut contains_super_call = false;
if direct {
for node in statement_list.items() {
if !contains_super_property && node.contains(ContainsSymbol::SuperProperty) {
contains_super_property = true;
}
}
lexically_declared_names_map.insert(*name, *is_function_declaration);
if !is_function_declaration && var_declared_names.contains(name) {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
}
if context.has_binding(*name) {
return Err(ParseError::general(
"lexical name declared multiple times",
Position::new(1, 1),
));
}
if !is_function_declaration {
let name_str = context.interner().resolve_expect(*name);
let desc = context
.realm
.global_property_map
.string_property_map()
.get(name_str);
let non_configurable_binding_exists = match desc {
Some(desc) => !matches!(desc.configurable(), Some(true)),
None => false,
};
if non_configurable_binding_exists {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
if !contains_super_call && node.contains(ContainsSymbol::SuperCall) {
contains_super_call = true;
}
}
}
for name in var_declared_names {
if context.has_binding(name) {
return Err(ParseError::general(
"lexical name declared in var names",
Position::new(1, 1),
));
}
}
if !in_method && contains_super_property {
return Err(ParseError::general(
"invalid super usage",
Position::new(1, 1),
));
}
if !in_derived_constructor && contains_super_call {
return Err(ParseError::general(
"invalid super usage",
Position::new(1, 1),
));
}
Ok(statement_list)
}
@ -235,34 +235,97 @@ impl<R> Parser<R> {
///
/// [spec]: https://tc39.es/ecma262/#prod-Script
#[derive(Debug, Clone, Copy)]
pub struct Script;
pub struct Script {
direct_eval: bool,
}
impl<R> TokenParser<R> for Script
where
R: Read,
{
type Output = StatementList;
impl Script {
/// Create a new `Script` parser.
fn new(direct_eval: bool) -> Self {
Self { direct_eval }
}
fn parse(
fn parse<R: Read>(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
context: &mut Context,
) -> Result<StatementList, ParseError> {
let mut strict = cursor.strict_mode();
match cursor.peek(0, interner)? {
match cursor.peek(0, context.interner_mut())? {
Some(tok) => {
match tok.kind() {
// Set the strict mode
TokenKind::StringLiteral(string)
if interner.resolve_expect(*string) == "use strict" =>
if context.interner_mut().resolve_expect(*string) == "use strict" =>
{
cursor.set_strict_mode(true);
strict = true;
}
_ => {}
}
let mut statement_list = ScriptBody.parse(cursor, interner)?;
let mut statement_list =
ScriptBody::new(self.direct_eval).parse(cursor, context.interner_mut())?;
statement_list.set_strict(strict);
// It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries.
// It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody.
let mut var_declared_names = FxHashSet::default();
statement_list.var_declared_names_new(&mut var_declared_names);
let lexically_declared_names = statement_list.lexically_declared_names();
let mut lexically_declared_names_map: FxHashMap<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)
}
None => Ok(StatementList::from(Vec::new())),
@ -277,7 +340,16 @@ where
///
/// [spec]: https://tc39.es/ecma262/#prod-ScriptBody
#[derive(Debug, Clone, Copy)]
pub struct ScriptBody;
pub struct ScriptBody {
direct_eval: bool,
}
impl ScriptBody {
/// Create a new `ScriptBody` parser.
fn new(direct_eval: bool) -> Self {
Self { direct_eval }
}
}
impl<R> TokenParser<R> for ScriptBody
where
@ -290,6 +362,24 @@ where
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> 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::{
self,
declaration::class_decl::ClassElement as ClassElementNode,
function_contains_super, has_direct_super,
object::{MethodDefinition, PropertyName::Literal},
Class, FormalParameterList, FunctionExpr,
Class, ContainsSymbol, FormalParameterList, FunctionExpr,
},
Keyword, Punctuator,
},
@ -165,10 +166,27 @@ where
cursor.next(interner).expect("token disappeared");
Ok(Class::new(self.name, super_ref, None, vec![]))
} else {
let body_start = cursor
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let (constructor, elements) =
ClassBody::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(Punctuator::CloseBlock, "class tail", interner)?;
if super_ref.is_none() {
if let Some(constructor) = &constructor {
if function_contains_super(constructor.body(), constructor.parameters()) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
body_start,
)));
}
}
}
Ok(Class::new(self.name, super_ref, constructor, elements))
}
}
@ -295,57 +313,72 @@ where
}
(None, Some(element)) => {
match &element {
ClassElementNode::PrivateMethodDefinition(name, method) => match method
{
MethodDefinition::Get(_) => {
match private_elements_names.get(name) {
Some(PrivateElement::Setter) => {
private_elements_names
.insert(*name, PrivateElement::Value);
}
Some(_) => {
return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
None => {
private_elements_names
.insert(*name, PrivateElement::Getter);
ClassElementNode::PrivateMethodDefinition(name, method) => {
// It is a Syntax Error if PropName of MethodDefinition is not "constructor" and HasDirectSuper of MethodDefinition is true.
if has_direct_super(method.body(), method.parameters()) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
match method {
MethodDefinition::Get(_) => {
match private_elements_names.get(name) {
Some(PrivateElement::Setter) => {
private_elements_names
.insert(*name, PrivateElement::Value);
}
Some(_) => {
return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
None => {
private_elements_names
.insert(*name, PrivateElement::Getter);
}
}
}
}
MethodDefinition::Set(_) => {
match private_elements_names.get(name) {
Some(PrivateElement::Getter) => {
private_elements_names
.insert(*name, PrivateElement::Value);
MethodDefinition::Set(_) => {
match private_elements_names.get(name) {
Some(PrivateElement::Getter) => {
private_elements_names
.insert(*name, PrivateElement::Value);
}
Some(_) => {
return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
None => {
private_elements_names
.insert(*name, PrivateElement::Setter);
}
}
Some(_) => {
}
_ => {
if private_elements_names
.insert(*name, PrivateElement::Value)
.is_some()
{
return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
None => {
private_elements_names
.insert(*name, PrivateElement::Setter);
}
}
}
_ => {
if private_elements_names
.insert(*name, PrivateElement::Value)
.is_some()
{
return Err(ParseError::general(
"private identifier has already been declared",
position,
));
}
}
},
}
ClassElementNode::PrivateStaticMethodDefinition(name, method) => {
// It is a Syntax Error if HasDirectSuper of MethodDefinition is true.
if has_direct_super(method.body(), method.parameters()) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
match method {
MethodDefinition::Get(_) => {
match private_elements_names.get(name) {
@ -396,7 +429,15 @@ where
}
}
}
ClassElementNode::PrivateFieldDefinition(name, _) => {
ClassElementNode::PrivateFieldDefinition(name, init) => {
if let Some(node) = init {
if node.contains(node::ContainsSymbol::SuperCall) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
}
if private_elements_names
.insert(*name, PrivateElement::Value)
.is_some()
@ -407,7 +448,15 @@ where
));
}
}
ClassElementNode::PrivateStaticFieldDefinition(name, _) => {
ClassElementNode::PrivateStaticFieldDefinition(name, init) => {
if let Some(node) = init {
if node.contains(node::ContainsSymbol::SuperCall) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
}
if private_elements_names
.insert(*name, PrivateElement::StaticValue)
.is_some()
@ -418,6 +467,28 @@ where
));
}
}
ClassElementNode::MethodDefinition(_, method)
| ClassElementNode::StaticMethodDefinition(_, method) => {
// ClassElement : MethodDefinition:
// It is a Syntax Error if PropName of MethodDefinition is not "constructor" and HasDirectSuper of MethodDefinition is true.
// ClassElement : static MethodDefinition:
// It is a Syntax Error if HasDirectSuper of MethodDefinition is true.
if has_direct_super(method.body(), method.parameters()) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
}
ClassElementNode::FieldDefinition(_, Some(node))
| ClassElementNode::StaticFieldDefinition(_, Some(node)) => {
if node.contains(node::ContainsSymbol::SuperCall) {
return Err(ParseError::lex(LexError::Syntax(
"invalid super usage".into(),
position,
)));
}
}
_ => {}
}
elements.push(element);
@ -1276,6 +1347,7 @@ where
}
// ClassStaticBlockBody : ClassStaticBlockStatementList
// It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true.
// It is a Syntax Error if ClassStaticBlockStatementList Contains SuperCall is true.
ClassElementNode::StaticBlock(block) => {
for node in block.items() {
if node.contains_arguments() {
@ -1284,6 +1356,9 @@ where
position,
));
}
if node.contains(ContainsSymbol::SuperCall) {
return Err(ParseError::general("invalid super usage", position));
}
}
}
_ => {}

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

@ -21,7 +21,7 @@ use self::{
};
use crate::syntax::{
ast::node::{FormalParameterList, StatementList},
ast::{Keyword, Node, Position, Punctuator},
ast::{node::function_contains_super, Keyword, Node, Position, Punctuator},
lexer::TokenKind,
parser::{
expression::BindingIdentifier,
@ -213,5 +213,12 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
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))
}

4
boa_engine/src/vm/call_frame.rs

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

464
boa_engine/src/vm/code_block.rs

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

537
boa_engine/src/vm/mod.rs

@ -3,13 +3,18 @@
//! plus an interpreter to execute those instructions
use crate::{
builtins::{function::Function, iterable::IteratorRecord, Array, ForInIterator, Number},
object::PrivateElement,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
builtins::{
function::{ConstructorKind, Function},
iterable::IteratorRecord,
Array, ForInIterator, Number,
},
environments::EnvironmentSlots,
object::{JsFunction, JsObject, ObjectData, PrivateElement},
property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey},
value::Numeric,
vm::{
call_frame::CatchAddresses,
code_block::{create_generator_function_object, Readable},
code_block::{create_generator_function_object, initialize_instance_elements, Readable},
},
Context, JsBigInt, JsResult, JsString, JsValue,
};
@ -198,20 +203,103 @@ impl Context {
Opcode::PushEmptyObject => self.vm.push(self.construct_object()),
Opcode::PushClassPrototype => {
let superclass = self.vm.pop();
if superclass.is_null() {
self.vm.push(JsValue::Null);
}
if let Some(superclass) = superclass.as_constructor() {
let proto = superclass.get("prototype", self)?;
if !proto.is_object() && !proto.is_null() {
return self
.throw_type_error("superclass prototype must be an object or null");
}
let class = self.vm.pop();
{
let class_object = class.as_object().expect("class must be object");
class_object.set_prototype(Some(superclass.clone()));
let mut class_object_mut = class_object.borrow_mut();
let class_function = class_object_mut
.as_function_mut()
.expect("class must be function object");
if let Function::Ordinary {
constructor_kind, ..
} = class_function
{
*constructor_kind = ConstructorKind::Derived;
}
}
self.vm.push(class);
self.vm.push(proto);
} else if superclass.is_null() {
self.vm.push(JsValue::Null);
} else {
return self.throw_type_error("superclass must be a constructor");
}
}
Opcode::SetClassPrototype => {
let prototype_value = self.vm.pop();
let prototype = match &prototype_value {
JsValue::Object(proto) => Some(proto.clone()),
JsValue::Null => None,
JsValue::Undefined => {
Some(self.intrinsics().constructors().object().prototype.clone())
}
_ => unreachable!(),
};
let proto = JsObject::from_proto_and_data(prototype, ObjectData::ordinary());
let class = self.vm.pop();
{
let class_object = class.as_object().expect("class must be object");
class_object
.define_property_or_throw(
"prototype",
PropertyDescriptorBuilder::new()
.value(proto.clone())
.writable(false)
.enumerable(false)
.configurable(false),
self,
)
.expect("cannot fail per spec");
let mut class_object_mut = class_object.borrow_mut();
let class_function = class_object_mut
.as_function_mut()
.expect("class must be function object");
class_function.set_home_object(proto.clone());
}
proto
.__define_own_property__(
"constructor".into(),
PropertyDescriptorBuilder::new()
.value(class)
.writable(true)
.enumerable(false)
.configurable(true)
.build(),
self,
)
.expect("cannot fail per spec");
self.vm.push(proto);
}
Opcode::SetHomeObject => {
let function = self.vm.pop();
let function_object = function.as_object().expect("must be object");
let home = self.vm.pop();
let home_object = home.as_object().expect("must be object");
function_object
.borrow_mut()
.as_function_mut()
.expect("must be function object")
.set_home_object(home_object.clone());
self.vm.push(home);
self.vm.push(function);
}
Opcode::PushNewArray => {
let array = Array::array_create(0, None, self)
.expect("Array creation with 0 length should never fail");
@ -721,6 +809,13 @@ impl Context {
} else {
object.to_object(self)?
};
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name);
object.__define_own_property__(
@ -777,6 +872,13 @@ impl Context {
} else {
object.to_object(self)?
};
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let key = key.to_property_key(self)?;
object.__define_own_property__(
key,
@ -817,6 +919,13 @@ impl Context {
let object = self.vm.pop();
let value = self.vm.pop();
let object = object.to_object(self)?;
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into();
let set = object
@ -862,6 +971,13 @@ impl Context {
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let name = key.to_property_key(self)?;
let set = object
.__get_own_property__(&name, self)?
@ -907,6 +1023,13 @@ impl Context {
let object = self.vm.pop();
let value = self.vm.pop();
let object = object.to_object(self)?;
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let name = self.vm.frame().code.names[index as usize];
let name = self.interner().resolve_expect(name).into();
let get = object
@ -952,6 +1075,13 @@ impl Context {
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?;
value
.as_object()
.expect("method must be function object")
.borrow_mut()
.as_function_mut()
.expect("method must be function object")
.set_home_object(object.clone());
let name = key.to_property_key(self)?;
let get = object
.__get_own_property__(&name, self)?
@ -969,7 +1099,41 @@ impl Context {
self,
)?;
}
Opcode::SetPrivateValue => {
Opcode::AssignPrivateField => {
let index = self.vm.read::<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 name = self.vm.frame().code.names[index as usize];
let value = self.vm.pop();
@ -985,12 +1149,26 @@ impl Context {
drop(object_borrow_mut);
setter.call(&object.clone().into(), &[value], self)?;
} else {
object_borrow_mut.set_private_element(name, PrivateElement::Value(value));
object_borrow_mut.set_private_element(name, PrivateElement::Field(value));
}
} else {
return self.throw_type_error("cannot set private property on non-object");
}
}
Opcode::SetPrivateMethod => {
let index = self.vm.read::<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 => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.names[index as usize];
@ -1025,7 +1203,8 @@ impl Context {
let object_borrow_mut = object.borrow();
if let Some(element) = object_borrow_mut.get_private_element(name) {
match element {
PrivateElement::Value(value) => self.vm.push(value),
PrivateElement::Field(value) => self.vm.push(value),
PrivateElement::Method(method) => self.vm.push(method.clone()),
PrivateElement::Accessor {
getter: Some(getter),
setter: _,
@ -1046,25 +1225,111 @@ impl Context {
return self.throw_type_error("cannot read private property from non-object");
}
}
Opcode::PushClassComputedFieldName => {
let object = self.vm.pop();
let value = self.vm.pop();
let value = value.to_property_key(self)?;
let object_obj = object
Opcode::PushClassField => {
let field_function_value = self.vm.pop();
let field_name_value = self.vm.pop();
let class_value = self.vm.pop();
let field_name_key = field_name_value.to_property_key(self)?;
let field_function_object = field_function_value
.as_object()
.expect("can only add field to function object");
let object = object_obj.borrow();
let function = object
.as_function()
.expect("can only add field to function object");
if let Function::Ordinary { code, .. } = function {
let code_b = code
.computed_field_names
.as_ref()
.expect("class constructor must have fields");
let mut fields_mut = code_b.borrow_mut();
fields_mut.push(value);
}
.expect("field value must be function object");
let mut field_function_object_borrow = field_function_object.borrow_mut();
let field_function = field_function_object_borrow
.as_function_mut()
.expect("field value must be function object");
let class_object = class_value
.as_object()
.expect("class must be function object");
field_function.set_home_object(class_object.clone());
class_object
.borrow_mut()
.as_function_mut()
.expect("class must be function object")
.push_field(
field_name_key,
JsFunction::from_object_unchecked(field_function_object.clone()),
);
}
Opcode::PushClassFieldPrivate => {
let index = self.vm.read::<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 => {
let index = self.vm.read::<u32>();
@ -1204,8 +1469,203 @@ impl Context {
.expect("finally jump must exist here") = Some(address);
}
Opcode::This => {
let this = self.vm.frame().this.clone();
self.vm.push(this);
let env = self.realm.environments.get_this_environment();
match env {
EnvironmentSlots::Function(env) => {
let env_b = env.borrow();
if let Some(this) = env_b.get_this_binding() {
self.vm.push(this);
} else {
drop(env_b);
return self.throw_reference_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor");
}
}
EnvironmentSlots::Global => {
let this = self.realm.global_object();
self.vm.push(this.clone());
}
}
}
Opcode::Super => {
let env = self
.realm
.environments
.get_this_environment()
.as_function_slots()
.expect("super access must be in a function environment");
let home = if env.borrow().get_this_binding().is_some() {
let env = env.borrow();
let function_object = env.function_object().borrow();
let function = function_object
.as_function()
.expect("must be function object");
function.get_home_object().cloned()
} else {
return self.throw_range_error("Must call super constructor in derived class before accessing 'this' or returning from derived constructor");
};
if let Some(home) = home {
if let Some(proto) = home.__get_prototype_of__(self)? {
self.vm.push(JsValue::from(proto));
} else {
self.vm.push(JsValue::Null);
}
} else {
self.vm.push(JsValue::Null);
};
}
Opcode::SuperCall => {
let argument_count = self.vm.read::<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 => {
let address = self.vm.read::<u32>();
@ -1480,20 +1940,9 @@ impl Context {
let compile_environment = self.vm.frame().code.compile_environments
[compile_environments_index as usize]
.clone();
let constructor = self.vm.frame().code.constructor;
let is_lexical = self.vm.frame().code.this_mode.is_lexical();
let this = if constructor.is_some() || !is_lexical {
self.vm.frame().this.clone()
} else {
JsValue::undefined()
};
self.realm.environments.push_function(
num_bindings as usize,
compile_environment,
this,
);
self.realm
.environments
.push_function_inherit(num_bindings as usize, compile_environment);
}
Opcode::PopEnvironment => {
self.realm.environments.pop();

138
boa_engine/src/vm/opcode.rs

@ -142,9 +142,23 @@ pub enum Opcode {
///
/// Operands:
///
/// Stack: superclass **=>** superclass.prototype
/// Stack: class, superclass **=>** class, superclass.prototype
PushClassPrototype,
/// Set the prototype of a class object.
///
/// Operands:
///
/// Stack: class, prototype **=>** class.prototype
SetClassPrototype,
/// Set home object internal slot of a function object.
///
/// Operands:
///
/// Stack: home, function **=>** home, function
SetHomeObject,
/// Push an empty array value on the stack.
///
/// Operands:
@ -624,16 +638,34 @@ pub enum Opcode {
/// Stack: object, key, value **=>**
DefineClassSetterByValue,
/// Set a private property by name from an object.
/// Assign the value of a private property of an object by it's name.
///
/// Like `obj.#name = value`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
AssignPrivateField,
/// Set a private property of a class constructor by it's name.
///
/// Like `#name = value`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
SetPrivateValue,
SetPrivateField,
/// Set a private setter property by name from an object.
/// Set a private method of a class constructor by it's name.
///
/// Like `#name() {}`
///
/// Operands: name_index: `u32`
///
/// Stack: object, value **=>**
SetPrivateMethod,
/// Set a private setter property of a class constructor by it's name.
///
/// Like `set #name() {}`
///
@ -642,7 +674,7 @@ pub enum Opcode {
/// Stack: object, value **=>**
SetPrivateSetter,
/// Set a private getter property by name from an object.
/// Set a private getter property of a class constructor by it's name.
///
/// Like `get #name() {}`
///
@ -660,12 +692,40 @@ pub enum Opcode {
/// Stack: object **=>** value
GetPrivateField,
/// Push a computed class field name to a class constructor object.
/// Push a field to a class.
///
/// Operands:
///
/// Stack: value, object **=>**
PushClassComputedFieldName,
/// Stack: class, field_name, field_function **=>**
PushClassField,
/// Push a private field to the class.
///
/// Operands: name_index: `u32`
///
/// Stack: class, field_function **=>**
PushClassFieldPrivate,
/// Push a private getter to the class.
///
/// Operands: name_index: `u32`
///
/// Stack: class, getter **=>**
PushClassPrivateGetter,
/// Push a private setter to the class.
///
/// Operands: name_index: `u32`
///
/// Stack: class, setter **=>**
PushClassPrivateSetter,
/// Push a private method to the class.
///
/// Operands: name_index: `u32`
///
/// Stack: class, method **=>**
PushClassPrivateMethod,
/// Deletes a property by name of an object.
///
@ -803,6 +863,34 @@ pub enum Opcode {
/// Stack: **=>** this
This,
/// Pushes the current `super` value to the stack.
///
/// Operands:
///
/// Stack: **=>** super
Super,
/// Execute the `super()` method.
///
/// Operands: argument_count: `u32`
///
/// Stack: argument_1, ... argument_n **=>**
SuperCall,
/// Execute the `super()` method where the last argument is a rest parameter.
///
/// Operands: argument_count: `u32`
///
/// Stack: argument_1, ... argument_n **=>**
SuperCallWithRest,
/// Execute the `super()` method when no constructor of the class is defined.
///
/// Operands:
///
/// Stack: argument_1, ... argument_n **=>**
SuperCallDerived,
/// Pop the two values of the stack, strict equal compares the two values,
/// if true jumps to address, otherwise push the second pop'ed value.
///
@ -1079,6 +1167,8 @@ impl Opcode {
Self::PushLiteral => "PushLiteral",
Self::PushEmptyObject => "PushEmptyObject",
Self::PushClassPrototype => "PushClassPrototype",
Self::SetClassPrototype => "SetClassPrototype",
Self::SetHomeObject => "SetHomeObject",
Self::PushNewArray => "PushNewArray",
Self::PushValueToArray => "PushValueToArray",
Self::PushElisionToArray => "PushElisionToArray",
@ -1143,11 +1233,17 @@ impl Opcode {
Self::DefineClassSetterByName => "DefineClassSetterByName",
Self::SetPropertySetterByValue => "SetPropertySetterByValue",
Self::DefineClassSetterByValue => "DefineClassSetterByValue",
Self::SetPrivateValue => "SetPrivateValue",
Self::AssignPrivateField => "AssignPrivateField",
Self::SetPrivateField => "SetPrivateValue",
Self::SetPrivateMethod => "SetPrivateMethod",
Self::SetPrivateSetter => "SetPrivateSetter",
Self::SetPrivateGetter => "SetPrivateGetter",
Self::GetPrivateField => "GetPrivateByName",
Self::PushClassComputedFieldName => "PushClassComputedFieldName",
Self::GetPrivateField => "GetPrivateField",
Self::PushClassField => "PushClassField",
Self::PushClassFieldPrivate => "PushClassFieldPrivate",
Self::PushClassPrivateGetter => "PushClassPrivateGetter",
Self::PushClassPrivateSetter => "PushClassPrivateSetter",
Self::PushClassPrivateMethod => "PushClassPrivateMethod",
Self::DeletePropertyByName => "DeletePropertyByName",
Self::DeletePropertyByValue => "DeletePropertyByValue",
Self::CopyDataProperties => "CopyDataProperties",
@ -1166,6 +1262,10 @@ impl Opcode {
Self::FinallySetJump => "FinallySetJump",
Self::ToBoolean => "ToBoolean",
Self::This => "This",
Self::Super => "Super",
Self::SuperCall => "SuperCall",
Self::SuperCallWithRest => "SuperCallWithRest",
Self::SuperCallDerived => "SuperCallDerived",
Self::Case => "Case",
Self::Default => "Default",
Self::GetFunction => "GetFunction",
@ -1300,6 +1400,10 @@ impl Opcode {
Self::FinallySetJump => "INST - FinallySetJump",
Self::ToBoolean => "INST - ToBoolean",
Self::This => "INST - This",
Self::Super => "INST - Super",
Self::SuperCall => "INST - SuperCall",
Self::SuperCallWithRest => "INST - SuperCallWithRest",
Self::SuperCallDerived => "INST - SuperCallDerived",
Self::Case => "INST - Case",
Self::Default => "INST - Default",
Self::GetFunction => "INST - GetFunction",
@ -1335,17 +1439,25 @@ impl Opcode {
Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Self::Nop => "INST - Nop",
Self::PushClassPrototype => "INST - PushClassPrototype",
Self::SetClassPrototype => "INST - SetClassPrototype",
Self::SetHomeObject => "INST - SetHomeObject",
Self::DefineClassMethodByName => "INST - DefineClassMethodByName",
Self::DefineClassMethodByValue => "INST - DefineClassMethodByValue",
Self::DefineClassGetterByName => "INST - DefineClassGetterByName",
Self::DefineClassGetterByValue => "INST - DefineClassGetterByValue",
Self::DefineClassSetterByName => "INST - DefineClassSetterByName",
Self::DefineClassSetterByValue => "INST - DefineClassSetterByValue",
Self::SetPrivateValue => "INST - SetPrivateValue",
Self::AssignPrivateField => "INST - AssignPrivateField",
Self::SetPrivateField => "INST - SetPrivateValue",
Self::SetPrivateMethod => "INST - SetPrivateMethod",
Self::SetPrivateSetter => "INST - SetPrivateSetter",
Self::SetPrivateGetter => "INST - SetPrivateGetter",
Self::GetPrivateField => "INST - GetPrivateField",
Self::PushClassComputedFieldName => "INST - PushClassComputedFieldName",
Self::PushClassField => "INST - PushClassField",
Self::PushClassFieldPrivate => "INST - PushClassFieldPrivate",
Self::PushClassPrivateGetter => "INST - PushClassPrivateGetter",
Self::PushClassPrivateSetter => "INST - PushClassPrivateSetter",
Self::PushClassPrivateMethod => "INST - PushClassPrivateMethod",
Self::ToPropertyKey => "INST - ToPropertyKey",
}
}

Loading…
Cancel
Save