diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs new file mode 100644 index 0000000000..f52108f533 --- /dev/null +++ b/boa_engine/src/builtins/eval/mod.rs @@ -0,0 +1,152 @@ +//! This module implements the global `eval` function. +//! +//! The `eval()` function evaluates JavaScript code represented as a string. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-eval-x +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval + +use crate::{ + builtins::{function::Function, BuiltIn, JsArgs}, + object::{JsObject, ObjectData}, + property::Attribute, + Context, JsValue, +}; +use boa_profiler::Profiler; +use rustc_hash::FxHashSet; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Eval; + +impl BuiltIn for Eval { + const NAME: &'static str = "eval"; + + const ATTRIBUTE: Attribute = Attribute::READONLY + .union(Attribute::NON_ENUMERABLE) + .union(Attribute::PERMANENT); + + fn init(context: &mut Context) -> Option { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + + let object = JsObject::from_proto_and_data( + context.intrinsics().constructors().function().prototype(), + ObjectData::function(Function::Native { + function: Self::eval, + constructor: false, + }), + ); + + Some(object.into()) + } +} + +impl Eval { + /// `19.2.1 eval ( x )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-eval-x + fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result { + // 1. Return ? PerformEval(x, false, false). + Self::perform_eval(args.get_or_undefined(0), false, false, context) + } + + /// `19.2.1.1 PerformEval ( x, strictCaller, direct )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-performeval + pub(crate) fn perform_eval( + x: &JsValue, + direct: bool, + strict: bool, + context: &mut Context, + ) -> Result { + // 1. Assert: If direct is false, then strictCaller is also false. + if !direct { + debug_assert!(!strict); + } + + // 2. If Type(x) is not String, return x. + let x = if let Some(x) = x.as_string() { + x.clone() + } else { + return Ok(x.clone()); + }; + + // 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 body = match context.parse(x.as_bytes()).map_err(|e| e.to_string()) { + Ok(body) => body, + Err(e) => return context.throw_syntax_error(e), + }; + + // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`. + + // Because our environment model does not map directly to the spec this section looks very different. + // 14 - 33 are in the following section, together with EvalDeclarationInstantiation. + if direct { + // If the call to eval is direct, the code is executed in the current environment. + + // Poison the current environment, because it may contain new declarations after/during eval. + context.realm.environments.poison_current(); + + // Set the compile time environment to the current running environment and save the number of current environments. + context.realm.compile_env = context.realm.environments.current_compile_environment(); + let environments_len = context.realm.environments.len(); + + // Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment. + let mut vars = FxHashSet::default(); + body.var_declared_names_new(&mut vars); + if let Some(name) = context + .realm + .environments + .has_lex_binding_until_function_environment(&vars) + { + let name = context.interner().resolve_expect(name); + let msg = format!("variable declaration {name} in eval function already exists as lexically declaration"); + return context.throw_syntax_error(msg); + } + + // Compile and execute the eval statement list. + let code_block = context.compile_with_new_declarative(&body, strict)?; + context + .realm + .environments + .extend_outer_function_environment(); + let result = context.execute(code_block); + + // Pop any added runtime environments that where not removed during the eval execution. + context.realm.environments.truncate(environments_len); + + result + } else { + // If the call to eval is indirect, the code is executed in the global environment. + + // Poison all environments, because the global environment may contain new declarations after/during eval. + context.realm.environments.poison_all(); + + // Pop all environments before the eval execution. + let environments = context.realm.environments.pop_to_global(); + let environments_len = context.realm.environments.len(); + context.realm.compile_env = context.realm.environments.current_compile_environment(); + + // Compile and execute the eval statement list. + let code_block = context.compile_with_new_declarative(&body, false)?; + let result = context.execute(code_block); + + // Restore all environments to the state from before the eval execution. + context.realm.environments.truncate(environments_len); + context.realm.environments.extend(environments); + + result + } + } +} diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index ddf02edf2d..7da47d784f 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -9,6 +9,7 @@ pub mod console; pub mod dataview; pub mod date; pub mod error; +pub mod eval; pub mod function; pub mod generator; pub mod generator_function; @@ -41,6 +42,7 @@ pub(crate) use self::{ AggregateError, Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, UriError, }, + eval::Eval, function::BuiltInFunctionObject, global_this::GlobalThis, infinity::Infinity, @@ -152,6 +154,7 @@ pub fn init(context: &mut Context) { DataView, Map, Number, + Eval, Set, String, RegExp, diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 76419b7a0c..82bef93560 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -1,6 +1,6 @@ use crate::{ builtins::function::ThisMode, - environments::BindingLocator, + environments::{BindingLocator, CompileTimeEnvironment}, syntax::ast::{ node::{ declaration::{ @@ -19,7 +19,7 @@ use crate::{ vm::{BindingOpcode, CodeBlock, Opcode}, Context, JsBigInt, JsResult, JsString, JsValue, }; -use boa_gc::Gc; +use boa_gc::{Cell, Gc}; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; use std::mem::size_of; @@ -95,6 +95,14 @@ impl<'b> ByteCompiler<'b> { self.context.interner() } + /// Push a compile time environment to the current `CodeBlock` and return it's index. + #[inline] + fn push_compile_environment(&mut self, environment: Gc>) -> usize { + let index = self.code_block.compile_environments.len(); + self.code_block.compile_environments.push(environment); + index + } + #[inline] fn get_or_insert_literal(&mut self, literal: Literal) -> u32 { if let Some(index) = self.literals_map.get(&literal) { @@ -281,13 +289,24 @@ impl<'b> ByteCompiler<'b> { Label { index } } + /// Emit an opcode with a dummy operand. + /// Return the `Label` of the operand. #[inline] - fn jump_with_custom_opcode(&mut self, opcode: Opcode) -> Label { + fn emit_opcode_with_operand(&mut self, opcode: Opcode) -> Label { let index = self.next_opcode_location(); self.emit(opcode, &[Self::DUMMY_ADDRESS]); Label { index } } + /// Emit an opcode with two dummy operands. + /// Return the `Label`s of the two operands. + #[inline] + fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) { + let index = self.next_opcode_location(); + self.emit(opcode, &[Self::DUMMY_ADDRESS, Self::DUMMY_ADDRESS]); + (Label { index }, Label { index: index + 4 }) + } + #[inline] fn patch_jump_with_target(&mut self, label: Label, target: u32) { let Label { index } = label; @@ -551,6 +570,35 @@ impl<'b> ByteCompiler<'b> { Ok(()) } + /// Compile a statement list in a new declarative environment. + #[inline] + pub(crate) fn compile_statement_list_with_new_declarative( + &mut self, + list: &[Node], + use_expr: bool, + strict: bool, + ) -> JsResult<()> { + self.context.push_compile_time_environment(strict); + let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); + + self.create_declarations(list)?; + + if let Some((last, items)) = list.split_last() { + for node in items { + self.compile_stmt(node, false)?; + } + self.compile_stmt(last, use_expr)?; + } + + let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); + self.emit_opcode(Opcode::PopEnvironment); + + Ok(()) + } + #[inline] pub fn compile_expr(&mut self, expr: &Node, use_expr: bool) -> JsResult<()> { match expr { @@ -726,17 +774,17 @@ impl<'b> ByteCompiler<'b> { BinOp::Log(op) => { match op { LogOp::And => { - let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); + let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd); self.compile_expr(binary.rhs(), true)?; self.patch_jump(exit); } LogOp::Or => { - let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); + let exit = self.emit_opcode_with_operand(Opcode::LogicalOr); self.compile_expr(binary.rhs(), true)?; self.patch_jump(exit); } LogOp::Coalesce => { - let exit = self.jump_with_custom_opcode(Opcode::Coalesce); + let exit = self.emit_opcode_with_operand(Opcode::Coalesce); self.compile_expr(binary.rhs(), true)?; self.patch_jump(exit); } @@ -761,7 +809,7 @@ impl<'b> ByteCompiler<'b> { AssignOp::Shr => Some(Opcode::ShiftRight), AssignOp::Ushr => Some(Opcode::UnsignedShiftRight), AssignOp::BoolAnd => { - let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); + let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd); self.compile_expr(binary.rhs(), true)?; let access = Self::compile_access(binary.lhs()).ok_or_else(|| { @@ -774,7 +822,7 @@ impl<'b> ByteCompiler<'b> { None } AssignOp::BoolOr => { - let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); + let exit = self.emit_opcode_with_operand(Opcode::LogicalOr); self.compile_expr(binary.rhs(), true)?; let access = Self::compile_access(binary.lhs()).ok_or_else(|| { @@ -787,7 +835,7 @@ impl<'b> ByteCompiler<'b> { None } AssignOp::Coalesce => { - let exit = self.jump_with_custom_opcode(Opcode::Coalesce); + let exit = self.emit_opcode_with_operand(Opcode::Coalesce); self.compile_expr(binary.rhs(), true)?; let access = Self::compile_access(binary.lhs()).ok_or_else(|| { @@ -1067,7 +1115,7 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::PushUndefined); let start_address = self.next_opcode_location(); - let start = self.jump_with_custom_opcode(Opcode::GeneratorNextDelegate); + let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate); self.emit(Opcode::Jump, &[start_address]); self.patch_jump(start); } else { @@ -1260,7 +1308,8 @@ impl<'b> ByteCompiler<'b> { } Node::ForLoop(for_loop) => { self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); if let Some(init) = for_loop.init() { self.create_decls_from_stmt(init)?; @@ -1298,13 +1347,16 @@ impl<'b> ByteCompiler<'b> { self.pop_loop_control_info(); self.emit_opcode(Opcode::LoopEnd); - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); } Node::ForInLoop(for_in_loop) => { self.compile_expr(for_in_loop.expr(), true)?; - let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator); + let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator); self.emit_opcode(Opcode::LoopStart); let start_address = self.next_opcode_location(); @@ -1312,8 +1364,9 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::LoopContinue); self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); - let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); + let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext); match for_in_loop.init() { IterableLoopInitializer::Identifier(ref ident) => { @@ -1368,8 +1421,11 @@ impl<'b> ByteCompiler<'b> { self.compile_stmt(for_in_loop.body(), false)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); self.emit(Opcode::Jump, &[start_address]); @@ -1392,8 +1448,9 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::LoopContinue); self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); - let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); + let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext); match for_of_loop.init() { IterableLoopInitializer::Identifier(ref ident) => { @@ -1448,8 +1505,11 @@ impl<'b> ByteCompiler<'b> { self.compile_stmt(for_of_loop.body(), false)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); self.emit(Opcode::Jump, &[start_address]); @@ -1623,11 +1683,15 @@ impl<'b> ByteCompiler<'b> { } Node::Block(block) => { self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); self.create_declarations(block.items())?; self.compile_statement_list(block.items(), use_expr)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); } Node::Throw(throw) => { @@ -1636,7 +1700,8 @@ impl<'b> ByteCompiler<'b> { } Node::Switch(switch) => { self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); for case in switch.cases() { self.create_declarations(case.body().items())?; } @@ -1649,10 +1714,10 @@ impl<'b> ByteCompiler<'b> { let mut labels = Vec::with_capacity(switch.cases().len()); for case in switch.cases() { self.compile_expr(case.condition(), true)?; - labels.push(self.jump_with_custom_opcode(Opcode::Case)); + labels.push(self.emit_opcode_with_operand(Opcode::Case)); } - let exit = self.jump_with_custom_opcode(Opcode::Default); + let exit = self.emit_opcode_with_operand(Opcode::Default); for (label, case) in labels.into_iter().zip(switch.cases()) { self.patch_jump(label); @@ -1668,8 +1733,12 @@ impl<'b> ByteCompiler<'b> { self.pop_switch_control_info(); self.emit_opcode(Opcode::LoopEnd); - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); } Node::FunctionDecl(_function) => self.function(node, false)?, @@ -1686,13 +1755,17 @@ impl<'b> ByteCompiler<'b> { let try_start = self.next_opcode_location(); self.emit(Opcode::TryStart, &[Self::DUMMY_ADDRESS, 0]); self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); self.create_declarations(t.block().items())?; self.compile_statement_list(t.block().items(), use_expr)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::TryEnd); @@ -1702,12 +1775,13 @@ impl<'b> ByteCompiler<'b> { if let Some(catch) = t.catch() { self.push_try_control_info_catch_start(); let catch_start = if t.finally().is_some() { - Some(self.jump_with_custom_opcode(Opcode::CatchStart)) + Some(self.emit_opcode_with_operand(Opcode::CatchStart)) } else { None }; self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); if let Some(decl) = catch.parameter() { match decl { Declaration::Identifier { ident, .. } => { @@ -1728,8 +1802,12 @@ impl<'b> ByteCompiler<'b> { self.create_declarations(catch.block().items())?; self.compile_statement_list(catch.block().items(), use_expr)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = + self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); if let Some(catch_start) = catch_start { self.emit_opcode(Opcode::CatchEnd); @@ -1755,13 +1833,18 @@ impl<'b> ByteCompiler<'b> { ); self.context.push_compile_time_environment(false); - let push_env = self.jump_with_custom_opcode(Opcode::PushDeclarativeEnvironment); + let push_env = + self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); self.create_declarations(finally.items())?; self.compile_statement_list(finally.items(), false)?; - let num_bindings = self.context.pop_compile_time_environment().num_bindings(); - self.patch_jump_with_target(push_env, num_bindings as u32); + let (num_bindings, compile_environment) = + self.context.pop_compile_time_environment(); + let index_compile_environment = + self.push_compile_environment(compile_environment); + self.patch_jump_with_target(push_env.0, num_bindings as u32); + self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::FinallyEnd); @@ -1879,7 +1962,7 @@ impl<'b> ByteCompiler<'b> { Declaration::Identifier { ident, .. } => { compiler.context.create_mutable_binding(ident.sym(), false); if let Some(init) = parameter.declaration().init() { - let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); compiler.compile_expr(init, true)?; compiler.patch_jump(skip); } @@ -1901,7 +1984,7 @@ impl<'b> ByteCompiler<'b> { let env_label = if parameters.has_expressions() { compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.context.push_compile_time_environment(true); - Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment)) + Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) } else { None }; @@ -1916,17 +1999,22 @@ impl<'b> ByteCompiler<'b> { compiler.compile_statement_list(body.items(), false)?; if let Some(env_label) = env_label { - let num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); - compiler.patch_jump_with_target(env_label, num_bindings as u32); - compiler.context.pop_compile_time_environment(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + let index_compile_environment = compiler.push_compile_environment(compile_environment); + compiler.patch_jump_with_target(env_label.0, num_bindings as u32); + compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); + + let (_, compile_environment) = compiler.context.pop_compile_time_environment(); + compiler.push_compile_environment(compile_environment); } else { - compiler.code_block.num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + compiler + .code_block + .compile_environments + .push(compile_environment); + compiler.code_block.num_bindings = num_bindings; } compiler.code_block.params = parameters.clone(); @@ -1966,12 +2054,16 @@ impl<'b> ByteCompiler<'b> { pub(crate) fn call(&mut self, node: &Node, use_expr: bool) -> JsResult<()> { #[derive(PartialEq)] enum CallKind { + CallEval, Call, New, } let (call, kind) = match node { - Node::Call(call) => (call, CallKind::Call), + Node::Call(call) => match call.expr() { + Node::Identifier(ident) if ident.sym() == Sym::EVAL => (call, CallKind::CallEval), + _ => (call, CallKind::Call), + }, Node::New(new) => (new.call(), CallKind::New), _ => unreachable!(), }; @@ -1996,7 +2088,7 @@ impl<'b> ByteCompiler<'b> { } expr => { self.compile_expr(expr, true)?; - if kind == CallKind::Call { + if kind == CallKind::Call || kind == CallKind::CallEval { self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::Swap); } @@ -2010,6 +2102,10 @@ impl<'b> ByteCompiler<'b> { let last_is_rest_parameter = matches!(call.args().last(), Some(Node::Spread(_))); match kind { + CallKind::CallEval if last_is_rest_parameter => { + self.emit(Opcode::CallEvalWithRest, &[call.args().len() as u32]); + } + CallKind::CallEval => self.emit(Opcode::CallEval, &[call.args().len() as u32]), CallKind::Call if last_is_rest_parameter => { self.emit(Opcode::CallWithRest, &[call.args().len() as u32]); } @@ -2039,7 +2135,7 @@ impl<'b> ByteCompiler<'b> { ) -> JsResult<()> { match pattern { DeclarationPattern::Object(pattern) => { - let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip_init = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); if let Some(init) = pattern.init() { self.compile_expr(init, true)?; } else { @@ -2078,7 +2174,8 @@ impl<'b> ByteCompiler<'b> { } if let Some(init) = default_init { - let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip = + self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); self.compile_expr(init, true)?; self.patch_jump(skip); } @@ -2140,7 +2237,8 @@ impl<'b> ByteCompiler<'b> { } if let Some(init) = default_init { - let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip = + self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); self.compile_expr(init, true)?; self.patch_jump(skip); } @@ -2153,7 +2251,7 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::Pop); } DeclarationPattern::Array(pattern) => { - let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip_init = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); if let Some(init) = pattern.init() { self.compile_expr(init, true)?; } else { @@ -2192,7 +2290,8 @@ impl<'b> ByteCompiler<'b> { } => { self.emit_opcode(next); if let Some(init) = default_init { - let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip = + self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); self.compile_expr(init, true)?; self.patch_jump(skip); } @@ -2462,7 +2561,8 @@ impl<'b> ByteCompiler<'b> { Declaration::Identifier { ident, .. } => { compiler.context.create_mutable_binding(ident.sym(), false); if let Some(init) = parameter.declaration().init() { - let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); + let skip = + compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); compiler.compile_expr(init, true)?; compiler.patch_jump(skip); } @@ -2482,30 +2582,38 @@ impl<'b> ByteCompiler<'b> { let env_label = if expr.parameters().has_expressions() { compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.context.push_compile_time_environment(true); - Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment)) + Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) } else { None }; compiler.create_declarations(expr.body().items())?; compiler.compile_statement_list(expr.body().items(), false)?; if let Some(env_label) = env_label { - let num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); - compiler.patch_jump_with_target(env_label, num_bindings as u32); - compiler.context.pop_compile_time_environment(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + let index_compile_environment = + compiler.push_compile_environment(compile_environment); + compiler.patch_jump_with_target(env_label.0, num_bindings as u32); + compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); + let (_, compile_environment) = compiler.context.pop_compile_time_environment(); + compiler.push_compile_environment(compile_environment); } else { - compiler.code_block.num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + compiler + .code_block + .compile_environments + .push(compile_environment); + compiler.code_block.num_bindings = num_bindings; } } else { - compiler.code_block.num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + compiler + .code_block + .compile_environments + .push(compile_environment); + compiler.code_block.num_bindings = num_bindings; } compiler.emit_opcode(Opcode::PushUndefined); @@ -2655,16 +2763,20 @@ impl<'b> ByteCompiler<'b> { compiler.context.push_compile_time_environment(true); compiler.create_declarations(statement_list.items())?; compiler.compile_statement_list(statement_list.items(), false)?; - compiler.code_block.num_bindings = compiler - .context - .pop_compile_time_environment() - .num_bindings(); + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + compiler + .code_block + .compile_environments + .push(compile_environment); + compiler.code_block.num_bindings = num_bindings; let code = Gc::new(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::Call, &[0]); + self.emit_opcode(Opcode::Pop); } ClassElement::MethodDefinition(..) | ClassElement::PrivateMethodDefinition(..) diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index da4088cabc..813bf6d8f0 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -670,6 +670,23 @@ impl Context { Ok(Gc::new(compiler.finish())) } + /// Compile the AST into a `CodeBlock` with an additional declarative environment. + #[inline] + pub(crate) fn compile_with_new_declarative( + &mut self, + statement_list: &StatementList, + strict: bool, + ) -> JsResult> { + let _timer = Profiler::global().start_event("Compilation", "Main"); + let mut compiler = ByteCompiler::new(Sym::MAIN, statement_list.strict(), self); + compiler.compile_statement_list_with_new_declarative( + statement_list.items(), + true, + strict || statement_list.strict(), + )?; + Ok(Gc::new(compiler.finish())) + } + /// Call the VM with a `CodeBlock` and return the result. /// /// Since this function receives a `Gc`, cloning the code is very cheap, since it's diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index 28e3be12d3..9bea4ba6f2 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -1,6 +1,7 @@ use crate::{ environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue, }; +use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::Sym; use rustc_hash::FxHashMap; @@ -11,60 +12,195 @@ use rustc_hash::FxHashMap; struct CompileTimeBinding { index: usize, mutable: bool, + lex: bool, } /// A compile time environment maps bound identifiers to their binding positions. /// /// A compile time environment also indicates, if it is a function environment. -#[derive(Debug)] +#[derive(Debug, Finalize, Trace)] pub(crate) struct CompileTimeEnvironment { + outer: Option>>, + environment_index: usize, + #[unsafe_ignore_trace] bindings: FxHashMap, function_scope: bool, } impl CompileTimeEnvironment { + /// Crate a new global compile time environment. + #[inline] + pub(crate) fn new_global() -> Self { + Self { + outer: None, + environment_index: 0, + bindings: FxHashMap::default(), + function_scope: true, + } + } + + /// Check if environment has a lexical binding with the given name. + #[inline] + pub(crate) fn has_lex_binding(&self, name: Sym) -> bool { + self.bindings + .get(&name) + .map_or(false, |binding| binding.lex) + } + /// Returns the number of bindings in this environment. #[inline] pub(crate) fn num_bindings(&self) -> usize { self.bindings.len() } -} -/// The compile time environment stack contains a stack of all environments at bytecode compile time. -/// -/// The first environment on the stack represents the global environment. -/// This is never being deleted and is tied to the existence of the realm. -/// All other environments are being dropped once they are not needed anymore. -#[derive(Debug)] -pub(crate) struct CompileTimeEnvironmentStack { - stack: Vec, -} + /// Check if the environment is a function environment. + #[inline] + pub(crate) fn is_function(&self) -> bool { + self.function_scope + } + + /// Get the locator for a binding name. + #[inline] + pub(crate) fn get_binding(&self, name: Sym) -> Option { + self.bindings + .get(&name) + .map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index)) + } + + /// Get the locator for a binding name in this and all outer environments. + #[inline] + pub(crate) fn get_binding_recursive(&self, name: Sym) -> BindingLocator { + if let Some(binding) = self.bindings.get(&name) { + BindingLocator::declarative(name, self.environment_index, binding.index) + } else if let Some(outer) = &self.outer { + outer.borrow().get_binding_recursive(name) + } else { + BindingLocator::global(name) + } + } -impl CompileTimeEnvironmentStack { - /// Creates a new compile time environment stack. + /// Check if a binding name exists in this and all outer environments. + #[inline] + pub(crate) fn has_binding_recursive(&self, name: Sym) -> bool { + if self.bindings.contains_key(&name) { + true + } else if let Some(outer) = &self.outer { + outer.borrow().has_binding_recursive(name) + } else { + false + } + } + + /// Create a mutable binding. /// - /// This function should only be used once, on realm creation. + /// If the binding is a function scope binding and this is a declarative environment, try the outer environment. #[inline] - pub(crate) fn new() -> Self { - Self { - stack: vec![CompileTimeEnvironment { - bindings: FxHashMap::default(), - function_scope: true, - }], + pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) -> bool { + if let Some(outer) = &self.outer { + if !function_scope || self.function_scope { + if !self.bindings.contains_key(&name) { + let binding_index = self.bindings.len(); + self.bindings.insert( + name, + CompileTimeBinding { + index: binding_index, + mutable: true, + lex: !function_scope, + }, + ); + } + true + } else { + return outer + .borrow_mut() + .create_mutable_binding(name, function_scope); + } + } else if function_scope { + false + } else { + if !self.bindings.contains_key(&name) { + let binding_index = self.bindings.len(); + self.bindings.insert( + name, + CompileTimeBinding { + index: binding_index, + mutable: true, + lex: !function_scope, + }, + ); + } + true + } + } + + /// Crate an immutable binding. + #[inline] + pub(crate) fn create_immutable_binding(&mut self, name: Sym) { + let binding_index = self.bindings.len(); + self.bindings.insert( + name, + CompileTimeBinding { + index: binding_index, + mutable: false, + lex: true, + }, + ); + } + + /// Return the binding locator for a mutable binding with the given binding name and scope. + #[inline] + pub(crate) fn initialize_mutable_binding( + &self, + name: Sym, + function_scope: bool, + ) -> BindingLocator { + if let Some(outer) = &self.outer { + if function_scope && !self.function_scope { + return outer + .borrow() + .initialize_mutable_binding(name, function_scope); + } + if let Some(binding) = self.bindings.get(&name) { + BindingLocator::declarative(name, self.environment_index, binding.index) + } else { + outer + .borrow() + .initialize_mutable_binding(name, function_scope) + } + } else if let Some(binding) = self.bindings.get(&name) { + BindingLocator::declarative(name, self.environment_index, binding.index) + } else { + BindingLocator::global(name) } } - /// Get the number of bindings for the current last environment. + /// Return the binding locator for an immutable binding. /// /// # Panics /// - /// Panics if there are no environments on the stack. + /// Panics if the binding is not in the current environment. #[inline] - pub(crate) fn get_binding_number(&self) -> usize { - self.stack - .last() - .expect("global environment must always exist") - .num_bindings() + pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator { + let binding = self.bindings.get(&name).expect("binding must exist"); + BindingLocator::declarative(name, self.environment_index, binding.index) + } + + /// Return the binding locator for a mutable binding. + #[inline] + pub(crate) fn set_mutable_binding_recursive(&self, name: Sym) -> BindingLocator { + match self.bindings.get(&name) { + Some(binding) if binding.mutable => { + BindingLocator::declarative(name, self.environment_index, binding.index) + } + Some(_) => BindingLocator::mutate_immutable(name), + None => { + if let Some(outer) = &self.outer { + outer.borrow().set_mutable_binding_recursive(name) + } else { + BindingLocator::global(name) + } + } + } } } @@ -74,10 +210,15 @@ impl Context { /// Note: This function only works at bytecode compile time! #[inline] pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) { - self.realm.compile_env.stack.push(CompileTimeEnvironment { + let environment_index = self.realm.compile_env.borrow().environment_index + 1; + let outer = self.realm.compile_env.clone(); + + self.realm.compile_env = Gc::new(Cell::new(CompileTimeEnvironment { + outer: Some(outer), + environment_index, bindings: FxHashMap::default(), function_scope, - }); + })); } /// Pop the last compile time environment from the stack. @@ -88,16 +229,20 @@ impl Context { /// /// Panics if there are no more environments that can be pop'ed. #[inline] - pub(crate) fn pop_compile_time_environment(&mut self) -> CompileTimeEnvironment { - assert!( - self.realm.compile_env.stack.len() > 1, - "cannot pop global environment" - ); - self.realm - .compile_env - .stack - .pop() - .expect("len > 1 already checked") + pub(crate) fn pop_compile_time_environment( + &mut self, + ) -> (usize, Gc>) { + let current_env_borrow = self.realm.compile_env.borrow(); + if let Some(outer) = ¤t_env_borrow.outer { + let outer_clone = outer.clone(); + let num_bindings = current_env_borrow.num_bindings(); + drop(current_env_borrow); + let current = self.realm.compile_env.clone(); + self.realm.compile_env = outer_clone; + (num_bindings, current) + } else { + panic!("cannot pop global environment") + } } /// Get the number of bindings for the current compile time environment. @@ -109,12 +254,7 @@ impl Context { /// Panics if there are no environments on the compile time environment stack. #[inline] pub(crate) fn get_binding_number(&self) -> usize { - self.realm - .compile_env - .stack - .last() - .expect("global environment must always exist") - .num_bindings() + self.realm.compile_env.borrow().num_bindings() } /// Get the binding locator of the binding at bytecode compile time. @@ -122,12 +262,7 @@ impl Context { /// Note: This function only works at bytecode compile time! #[inline] pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator { - for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { - if let Some(binding) = env.bindings.get(&name) { - return BindingLocator::declarative(name, i, binding.index); - } - } - BindingLocator::global(name) + self.realm.compile_env.borrow().get_binding_recursive(name) } /// Return if a declarative binding exists at bytecode compile time. @@ -136,12 +271,7 @@ impl Context { /// Note: This function only works at bytecode compile time! #[inline] pub(crate) fn has_binding(&self, name: Sym) -> bool { - for env in self.realm.compile_env.stack.iter().rev() { - if env.bindings.contains_key(&name) { - return true; - } - } - false + self.realm.compile_env.borrow().has_binding_recursive(name) } /// Create a mutable binding at bytecode compile time. @@ -154,49 +284,30 @@ impl Context { /// Panics if the global environment is not function scoped. #[inline] pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) { - let name_str = JsString::from(self.interner().resolve_expect(name)); - - for (i, env) in self.realm.compile_env.stack.iter_mut().enumerate().rev() { - if !function_scope || env.function_scope { - if env.bindings.contains_key(&name) { - return; - } - - if i == 0 { - let desc = self - .realm - .global_property_map - .string_property_map() - .get(&name_str); - if function_scope && desc.is_none() { - self.global_bindings_mut().insert( - name_str, - PropertyDescriptor::builder() - .value(JsValue::Undefined) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - ); - return; - } else if function_scope { - return; - } - } - - let binding_index = env.bindings.len(); - env.bindings.insert( - name, - CompileTimeBinding { - index: binding_index, - mutable: true, - }, + if !self + .realm + .compile_env + .borrow_mut() + .create_mutable_binding(name, function_scope) + { + let name_str = JsString::from(self.interner().resolve_expect(name)); + let desc = self + .realm + .global_property_map + .string_property_map() + .get(&name_str); + if desc.is_none() { + self.global_bindings_mut().insert( + name_str, + PropertyDescriptor::builder() + .value(JsValue::Undefined) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), ); - return; } - continue; } - panic!("global environment must be function scoped") } /// Initialize a mutable binding at bytecode compile time and return it's binding locator. @@ -208,16 +319,10 @@ impl Context { name: Sym, function_scope: bool, ) -> BindingLocator { - for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { - if function_scope && !env.function_scope { - continue; - } - if let Some(binding) = env.bindings.get(&name) { - return BindingLocator::declarative(name, i, binding.index); - } - return BindingLocator::global(name); - } - BindingLocator::global(name) + self.realm + .compile_env + .borrow() + .initialize_mutable_binding(name, function_scope) } /// Create an immutable binding at bytecode compile time. @@ -230,21 +335,10 @@ impl Context { /// Panics if the global environment does not exist. #[inline] pub(crate) fn create_immutable_binding(&mut self, name: Sym) { - let env = self - .realm + self.realm .compile_env - .stack - .last_mut() - .expect("global environment must always exist"); - - let binding_index = env.bindings.len(); - env.bindings.insert( - name, - CompileTimeBinding { - index: binding_index, - mutable: false, - }, - ); + .borrow_mut() + .create_immutable_binding(name); } /// Initialize an immutable binding at bytecode compile time and return it's binding locator. @@ -256,16 +350,10 @@ impl Context { /// Panics if the global environment does not exist or a the binding was not created on the current environment. #[inline] pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator { - let environment_index = self.realm.compile_env.stack.len() - 1; - let env = self - .realm + self.realm .compile_env - .stack - .last() - .expect("global environment must always exist"); - - let binding = env.bindings.get(&name).expect("binding must exist"); - BindingLocator::declarative(name, environment_index, binding.index) + .borrow() + .initialize_immutable_binding(name) } /// Return the binding locator for a set operation on an existing binding. @@ -273,14 +361,9 @@ impl Context { /// Note: This function only works at bytecode compile time! #[inline] pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator { - for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { - if let Some(binding) = env.bindings.get(&name) { - if binding.mutable { - return BindingLocator::declarative(name, i, binding.index); - } - return BindingLocator::mutate_immutable(name); - } - } - BindingLocator::global(name) + self.realm + .compile_env + .borrow() + .set_mutable_binding_recursive(name) } } diff --git a/boa_engine/src/environments/mod.rs b/boa_engine/src/environments/mod.rs index b33b65ab7f..a1f88c3b42 100644 --- a/boa_engine/src/environments/mod.rs +++ b/boa_engine/src/environments/mod.rs @@ -28,7 +28,7 @@ mod compile; mod runtime; pub(crate) use { - compile::CompileTimeEnvironmentStack, + compile::CompileTimeEnvironment, runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack}, }; diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 1e2c3dd7e7..8842386a7b 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -1,18 +1,34 @@ -use crate::{Context, JsResult, JsValue}; +use crate::{environments::CompileTimeEnvironment, Context, JsResult, JsValue}; use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::Sym; +use rustc_hash::FxHashSet; -/// A declarative environment holds the bindings values at runtime. +/// A declarative environment holds binding values at runtime. /// /// Bindings are stored in a fixed size list of optional values. /// If a binding is not initialized, the value is `None`. /// /// Optionally, an environment can hold a `this` value. /// The `this` value is present only if the environment is a function environment. +/// +/// Code evaluation at runtime (e.g. the `eval` built-in function) can add +/// bindings to existing, compiled function environments. +/// This makes it impossible to determine the location of all bindings at compile time. +/// To dynamically check for added bindings at runtime, a reference to the +/// corresponding compile time environment is needed. +/// +/// Checking all environments for potential added bindings at runtime on every get/set +/// would offset the performance improvement of determining binding locations at compile time. +/// To minimize this, each environment holds a `poisoned` flag. +/// If bindings where added at runtime, the current environment and all inner environments +/// are marked as poisoned. +/// All poisoned environments have to be checked for added bindings. #[derive(Debug, Trace, Finalize)] pub(crate) struct DeclarativeEnvironment { bindings: Cell>>, this: Option, + compile: Gc>, + poisoned: Cell, } impl DeclarativeEnvironment { @@ -59,15 +75,77 @@ pub struct DeclarativeEnvironmentStack { impl DeclarativeEnvironmentStack { /// Create a new environment stack with the most outer declarative environment. #[inline] - pub(crate) fn new() -> Self { + pub(crate) fn new(global_compile_environment: Gc>) -> Self { Self { stack: vec![Gc::new(DeclarativeEnvironment { bindings: Cell::new(Vec::new()), this: None, + compile: global_compile_environment, + poisoned: Cell::new(false), })], } } + /// Extends the length of the next outer function environment to the number of compiled bindings. + /// + /// 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() { + let compile_bindings_number = env.compile.borrow().num_bindings(); + let mut bindings_mut = env.bindings.borrow_mut(); + + if compile_bindings_number > bindings_mut.len() { + let diff = compile_bindings_number - bindings_mut.len(); + bindings_mut.extend(vec![None; diff]); + } + break; + } + } + } + + /// Check if any of the provided binding names are defined as lexical bindings. + /// + /// Start at the current environment. + /// Stop at the next outer function environment. + pub(crate) fn has_lex_binding_until_function_environment( + &self, + names: &FxHashSet, + ) -> Option { + for env in self.stack.iter().rev() { + let compile = env.compile.borrow(); + for name in names { + if compile.has_lex_binding(*name) { + return Some(*name); + } + } + if compile.is_function() { + break; + } + } + None + } + + /// Pop all current environments except the global environment. + pub(crate) fn pop_to_global(&mut self) -> Vec> { + self.stack.split_off(1) + } + + /// Get the number of current environments. + pub(crate) fn len(&self) -> usize { + self.stack.len() + } + + /// Truncate current environments to the given number. + pub(crate) fn truncate(&mut self, len: usize) { + self.stack.truncate(len); + } + + /// Extend the current environment stack with the given environments. + pub(crate) fn extend(&mut self, other: Vec>) { + self.stack.extend(other); + } + /// Set the number of bindings on the global environment. /// /// # Panics @@ -97,20 +175,57 @@ impl DeclarativeEnvironmentStack { } /// Push a declarative environment on the environments stack. + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. #[inline] - pub(crate) fn push_declarative(&mut self, num_bindings: usize) { + pub(crate) fn push_declarative( + &mut self, + num_bindings: usize, + compile_environment: Gc>, + ) { + let poisoned = self + .stack + .last() + .expect("global environment must always exist") + .poisoned + .borrow() + .to_owned(); + self.stack.push(Gc::new(DeclarativeEnvironment { bindings: Cell::new(vec![None; num_bindings]), this: None, + compile: compile_environment, + poisoned: Cell::new(poisoned), })); } /// Push a function environment on the environments stack. + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. #[inline] - pub(crate) fn push_function(&mut self, num_bindings: usize, this: JsValue) { + pub(crate) fn push_function( + &mut self, + num_bindings: usize, + compile_environment: Gc>, + this: JsValue, + ) { + let poisoned = self + .stack + .last() + .expect("global environment must always exist") + .poisoned + .borrow() + .to_owned(); + self.stack.push(Gc::new(DeclarativeEnvironment { bindings: Cell::new(vec![None; num_bindings]), this: Some(this), + compile: compile_environment, + poisoned: Cell::new(poisoned), })); } @@ -134,6 +249,47 @@ impl DeclarativeEnvironmentStack { .clone() } + /// Get the compile environment for the current runtime environment. + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. + pub(crate) fn current_compile_environment(&self) -> Gc> { + self.stack + .last() + .expect("global environment must always exist") + .compile + .clone() + } + + /// Mark that there may be added bindings in the current environment. + /// + /// # Panics + /// + /// Panics if no environment exists on the stack. + #[inline] + pub(crate) fn poison_current(&mut self) { + let mut poisoned = self + .stack + .last() + .expect("global environment must always exist") + .poisoned + .borrow_mut(); + *poisoned = true; + } + + /// Mark that there may be added binding in all environments. + #[inline] + pub(crate) fn poison_all(&mut self) { + for env in &mut self.stack { + let mut poisoned = env.poisoned.borrow_mut(); + if *poisoned { + return; + } + *poisoned = true; + } + } + /// Get the value of a binding. /// /// # Panics @@ -142,9 +298,30 @@ impl DeclarativeEnvironmentStack { #[inline] pub(crate) fn get_value_optional( &self, - environment_index: usize, - binding_index: usize, + mut environment_index: usize, + mut binding_index: usize, + name: Sym, ) -> Option { + if environment_index != self.stack.len() - 1 { + for env_index in (environment_index + 1..self.stack.len()).rev() { + let env = self + .stack + .get(env_index) + .expect("environment index must be in range"); + if !*env.poisoned.borrow() { + break; + } + let compile = env.compile.borrow(); + if compile.is_function() { + if let Some(b) = compile.get_binding(name) { + environment_index = b.environment_index; + binding_index = b.binding_index; + break; + } + } + } + } + self.stack .get(environment_index) .expect("environment index must be in range") @@ -155,6 +332,34 @@ impl DeclarativeEnvironmentStack { .clone() } + /// Get the value of a binding by it's name. + /// + /// This only considers function environments that are poisoned. + /// All other bindings are accessed via indices. + #[inline] + pub(crate) fn get_value_global_poisoned(&self, name: Sym) -> Option { + for env in self.stack.iter().rev() { + if !*env.poisoned.borrow() { + return None; + } + let compile = env.compile.borrow(); + if compile.is_function() { + if let Some(b) = compile.get_binding(name) { + return self + .stack + .get(b.environment_index) + .expect("environment index must be in range") + .bindings + .borrow() + .get(b.binding_index) + .expect("binding index must be in range") + .clone(); + } + } + } + None + } + /// Set the value of a binding. /// /// # Panics @@ -188,10 +393,31 @@ impl DeclarativeEnvironmentStack { #[inline] pub(crate) fn put_value_if_initialized( &mut self, - environment_index: usize, - binding_index: usize, + mut environment_index: usize, + mut binding_index: usize, + name: Sym, value: JsValue, ) -> bool { + if environment_index != self.stack.len() - 1 { + for env_index in (environment_index + 1..self.stack.len()).rev() { + let env = self + .stack + .get(env_index) + .expect("environment index must be in range"); + if !*env.poisoned.borrow() { + break; + } + let compile = env.compile.borrow(); + if compile.is_function() { + if let Some(b) = compile.get_binding(name) { + environment_index = b.environment_index; + binding_index = b.binding_index; + break; + } + } + } + } + let mut bindings = self .stack .get(environment_index) @@ -234,6 +460,40 @@ impl DeclarativeEnvironmentStack { *binding = Some(value); } } + + /// Set the value of a binding by it's name. + /// + /// This only considers function environments that are poisoned. + /// All other bindings are set via indices. + /// + /// # Panics + /// + /// Panics if the environment or binding index are out of range. + #[inline] + pub(crate) fn put_value_global_poisoned(&mut self, name: Sym, value: &JsValue) -> bool { + for env in self.stack.iter().rev() { + if !*env.poisoned.borrow() { + return false; + } + let compile = env.compile.borrow(); + if compile.is_function() { + if let Some(b) = compile.get_binding(name) { + let mut bindings = self + .stack + .get(b.environment_index) + .expect("environment index must be in range") + .bindings + .borrow_mut(); + let binding = bindings + .get_mut(b.binding_index) + .expect("binding index must be in range"); + *binding = Some(value.clone()); + return true; + } + } + } + false + } } /// A binding locator contains all information about a binding that is needed to resolve it at runtime. diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index 1e9c5ab6cd..66f50e00f1 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -5,9 +5,10 @@ //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. use crate::{ - environments::{CompileTimeEnvironmentStack, DeclarativeEnvironmentStack}, + environments::{CompileTimeEnvironment, DeclarativeEnvironmentStack}, object::{GlobalPropertyMap, JsObject, ObjectData, PropertyMap}, }; +use boa_gc::{Cell, Gc}; use boa_profiler::Profiler; /// Representation of a Realm. @@ -19,7 +20,7 @@ pub struct Realm { pub(crate) global_extensible: bool, pub(crate) global_property_map: PropertyMap, pub(crate) environments: DeclarativeEnvironmentStack, - pub(crate) compile_env: CompileTimeEnvironmentStack, + pub(crate) compile_env: Gc>, } impl Realm { @@ -31,12 +32,14 @@ impl Realm { // Allow identification of the global object easily let global_object = JsObject::from_proto_and_data(None, ObjectData::global()); + let global_compile_environment = Gc::new(Cell::new(CompileTimeEnvironment::new_global())); + Self { global_object, global_extensible: true, global_property_map: PropertyMap::default(), - environments: DeclarativeEnvironmentStack::new(), - compile_env: CompileTimeEnvironmentStack::new(), + environments: DeclarativeEnvironmentStack::new(global_compile_environment.clone()), + compile_env: global_compile_environment, } } @@ -53,7 +56,7 @@ impl Realm { /// Set the number of bindings on the global environment. #[inline] pub(crate) fn set_global_binding_number(&mut self) { - let binding_number = self.compile_env.get_binding_number(); + let binding_number = self.compile_env.borrow().num_bindings(); self.environments.set_global_binding_number(binding_number); } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index fc6574a069..80e3c34f10 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -11,7 +11,7 @@ use crate::{ generator::{Generator, GeneratorContext, GeneratorState}, }, context::intrinsics::StandardConstructors, - environments::{BindingLocator, DeclarativeEnvironmentStack}, + environments::{BindingLocator, CompileTimeEnvironment, DeclarativeEnvironmentStack}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, syntax::ast::node::FormalParameterList, @@ -100,6 +100,9 @@ pub struct CodeBlock { /// Similar to the `[[ClassFieldInitializerName]]` slot in the spec. /// Holds class field names that are computed at class declaration time. pub(crate) computed_field_names: Option>>, + + /// Compile time environments in this function. + pub(crate) compile_environments: Vec>>, } impl CodeBlock { @@ -121,6 +124,7 @@ impl CodeBlock { lexical_name_argument: false, arguments_binding: None, computed_field_names: None, + compile_environments: Vec::new(), } } @@ -190,6 +194,8 @@ impl CodeBlock { | Opcode::LogicalAnd | Opcode::LogicalOr | Opcode::Coalesce + | Opcode::CallEval + | Opcode::CallEvalWithRest | Opcode::Call | Opcode::CallWithRest | Opcode::New @@ -198,13 +204,14 @@ impl CodeBlock { | Opcode::ForInLoopNext | Opcode::ConcatToString | Opcode::CopyDataProperties - | Opcode::GeneratorNextDelegate - | Opcode::PushDeclarativeEnvironment => { + | Opcode::GeneratorNextDelegate => { let result = self.read::(*pc).to_string(); *pc += size_of::(); result } - Opcode::TryStart => { + Opcode::TryStart + | Opcode::PushDeclarativeEnvironment + | Opcode::PushFunctionEnvironment => { let operand1 = self.read::(*pc); *pc += size_of::(); let operand2 = self.read::(*pc); @@ -322,7 +329,6 @@ impl CodeBlock { | Opcode::FinallyEnd | Opcode::This | Opcode::Return - | Opcode::PushFunctionEnvironment | Opcode::PopEnvironment | Opcode::LoopStart | Opcode::LoopContinue @@ -634,10 +640,19 @@ impl JsObject { this.clone() }; - context - .realm - .environments - .push_function(code.num_bindings, this.clone()); + if code.params.has_expressions() { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[1].clone(), + this.clone(), + ); + } else { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[0].clone(), + this.clone(), + ); + } if let Some(binding) = code.arguments_binding { let arguments_obj = @@ -733,10 +748,19 @@ impl JsObject { this.clone() }; - context - .realm - .environments - .push_function(code.num_bindings, this.clone()); + if code.params.has_expressions() { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[1].clone(), + this.clone(), + ); + } else { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[0].clone(), + this.clone(), + ); + } if let Some(binding) = code.arguments_binding { let arguments_obj = @@ -904,10 +928,19 @@ impl JsObject { this }; - context - .realm - .environments - .push_function(code.num_bindings, this.clone().into()); + if code.params.has_expressions() { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[1].clone(), + this.clone().into(), + ); + } else { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[0].clone(), + this.clone().into(), + ); + } let mut arguments_in_parameter_names = false; let mut is_simple_parameter_list = true; diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 5546808960..62e09cc175 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -445,30 +445,42 @@ impl Context { binding_locator.throw_mutate_immutable(self)?; let value = if binding_locator.is_global() { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name()) - .into(); - match self.global_bindings_mut().get(&key) { - Some(desc) => match desc.kind() { - DescriptorKind::Data { - value: Some(value), .. - } => value.clone(), - DescriptorKind::Accessor { get: Some(get), .. } - if !get.is_undefined() => - { - let get = get.clone(); - self.call(&get, &self.global_object().clone().into(), &[])? - } + if let Some(value) = self + .realm + .environments + .get_value_global_poisoned(binding_locator.name()) + { + value + } else { + let key: JsString = self + .interner() + .resolve_expect(binding_locator.name()) + .into(); + match self.global_bindings_mut().get(&key) { + Some(desc) => match desc.kind() { + DescriptorKind::Data { + value: Some(value), .. + } => value.clone(), + DescriptorKind::Accessor { get: Some(get), .. } + if !get.is_undefined() => + { + let get = get.clone(); + self.call(&get, &self.global_object().clone().into(), &[])? + } + _ => { + return self + .throw_reference_error(format!("{key} is not defined")) + } + }, _ => { return self.throw_reference_error(format!("{key} is not defined")) } - }, - _ => return self.throw_reference_error(format!("{key} is not defined")), + } } } else if let Some(value) = self.realm.environments.get_value_optional( binding_locator.environment_index(), binding_locator.binding_index(), + binding_locator.name(), ) { value } else { @@ -484,28 +496,37 @@ impl Context { let binding_locator = self.vm.frame().code.bindings[index as usize]; binding_locator.throw_mutate_immutable(self)?; let value = if binding_locator.is_global() { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name()) - .into(); - match self.global_bindings_mut().get(&key) { - Some(desc) => match desc.kind() { - DescriptorKind::Data { - value: Some(value), .. - } => value.clone(), - DescriptorKind::Accessor { get: Some(get), .. } - if !get.is_undefined() => - { - let get = get.clone(); - self.call(&get, &self.global_object().clone().into(), &[])? - } + if let Some(value) = self + .realm + .environments + .get_value_global_poisoned(binding_locator.name()) + { + value + } else { + let key: JsString = self + .interner() + .resolve_expect(binding_locator.name()) + .into(); + match self.global_bindings_mut().get(&key) { + Some(desc) => match desc.kind() { + DescriptorKind::Data { + value: Some(value), .. + } => value.clone(), + DescriptorKind::Accessor { get: Some(get), .. } + if !get.is_undefined() => + { + let get = get.clone(); + self.call(&get, &self.global_object().clone().into(), &[])? + } + _ => JsValue::undefined(), + }, _ => JsValue::undefined(), - }, - _ => JsValue::undefined(), + } } } else if let Some(value) = self.realm.environments.get_value_optional( binding_locator.environment_index(), binding_locator.binding_index(), + binding_locator.name(), ) { value } else { @@ -521,31 +542,40 @@ impl Context { binding_locator.throw_mutate_immutable(self)?; if binding_locator.is_global() { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name()) - .into(); - let exists = self.global_bindings_mut().contains_key(&key); - - if !exists && (self.strict() || self.vm.frame().code.strict) { - return self.throw_reference_error(format!( - "assignment to undeclared variable {key}" - )); - } - - let success = crate::object::internal_methods::global::global_set_no_receiver( - &key.clone().into(), - value, - self, - )?; + if !self + .realm + .environments + .put_value_global_poisoned(binding_locator.name(), &value) + { + let key: JsString = self + .interner() + .resolve_expect(binding_locator.name()) + .into(); + let exists = self.global_bindings_mut().contains_key(&key); + + if !exists && (self.strict() || self.vm.frame().code.strict) { + return self.throw_reference_error(format!( + "assignment to undeclared variable {key}" + )); + } - if !success && (self.strict() || self.vm.frame().code.strict) { - return self - .throw_type_error(format!("cannot set non-writable property: {key}",)); + let success = + crate::object::internal_methods::global::global_set_no_receiver( + &key.clone().into(), + value, + self, + )?; + + if !success && (self.strict() || self.vm.frame().code.strict) { + return self.throw_type_error(format!( + "cannot set non-writable property: {key}", + )); + } } } else if !self.realm.environments.put_value_if_initialized( binding_locator.environment_index(), binding_locator.binding_index(), + binding_locator.name(), value, ) { self.throw_reference_error(format!( @@ -1200,6 +1230,95 @@ impl Context { let function = create_generator_function_object(code, self); self.vm.push(function); } + Opcode::CallEval => { + if self.vm.stack_size_limit <= self.vm.stack.len() { + return self.throw_range_error("Maximum call stack size exceeded"); + } + let argument_count = self.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(self.vm.pop()); + } + arguments.reverse(); + + let func = self.vm.pop(); + let mut this = self.vm.pop(); + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => return self.throw_type_error("not a callable function"), + }; + + if this.is_null_or_undefined() { + this = self.global_object().clone().into(); + } + + // A native function with the name "eval" implies, that is this the built-in eval function. + let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + + let strict = self.strict() || self.vm.frame().code.strict; + + if eval { + if let Some(x) = arguments.get(0) { + let result = + crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; + self.vm.push(result); + } else { + self.vm.push(JsValue::Undefined); + } + } else { + let result = object.__call__(&this, &arguments, self)?; + self.vm.push(result); + } + } + Opcode::CallEvalWithRest => { + if self.vm.stack_size_limit <= self.vm.stack.len() { + return self.throw_range_error("Maximum call stack size exceeded"); + } + let argument_count = self.vm.read::(); + let rest_argument = self.vm.pop(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..(argument_count - 1) { + arguments.push(self.vm.pop()); + } + arguments.reverse(); + let func = self.vm.pop(); + let mut this = self.vm.pop(); + + 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 object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => return self.throw_type_error("not a callable function"), + }; + + if this.is_null_or_undefined() { + this = self.global_object().clone().into(); + } + + // A native function with the name "eval" implies, that is this the built-in eval function. + let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + + let strict = self.strict() || self.vm.frame().code.strict; + + if eval { + if let Some(x) = arguments.get(0) { + let result = + crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; + self.vm.push(result); + } else { + self.vm.push(JsValue::Undefined); + } + } else { + let result = object.__call__(&this, &arguments, self)?; + self.vm.push(result); + } + } Opcode::Call => { if self.vm.stack_size_limit <= self.vm.stack.len() { return self.throw_range_error("Maximum call stack size exceeded"); @@ -1340,14 +1459,23 @@ impl Context { } Opcode::PushDeclarativeEnvironment => { let num_bindings = self.vm.read::(); + let compile_environments_index = self.vm.read::(); + let compile_environment = self.vm.frame().code.compile_environments + [compile_environments_index as usize] + .clone(); self.realm .environments - .push_declarative(num_bindings as usize); + .push_declarative(num_bindings as usize, compile_environment); self.vm.frame_mut().loop_env_stack_inc(); self.vm.frame_mut().try_env_stack_inc(); } Opcode::PushFunctionEnvironment => { let num_bindings = self.vm.read::(); + let compile_environments_index = self.vm.read::(); + let compile_environment = self.vm.frame().code.compile_environments + [compile_environments_index as usize] + .clone(); + let is_constructor = self.vm.frame().code.constructor; let is_lexical = self.vm.frame().code.this_mode.is_lexical(); let this = if is_constructor || !is_lexical { @@ -1356,9 +1484,11 @@ impl Context { JsValue::undefined() }; - self.realm - .environments - .push_function(num_bindings as usize, this); + self.realm.environments.push_function( + num_bindings as usize, + compile_environment, + this, + ); } Opcode::PopEnvironment => { self.realm.environments.pop(); @@ -1839,7 +1969,7 @@ impl Context { println!("\n"); } - if self.vm.stack.is_empty() { + if self.vm.stack.len() <= start_stack_size { return Ok((JsValue::undefined(), ReturnType::Normal)); } diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index 4bce1bc343..7523c2e9d1 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -825,6 +825,20 @@ pub enum Opcode { /// Stack: **=>** func GetGenerator, + /// Call a function named "eval". + /// + /// Operands: argument_count: `u32` + /// + /// Stack: func, this, argument_1, ... argument_n **=>** result + CallEval, + + /// Call a function named "eval" where the last argument is a rest parameter. + /// + /// Operands: argument_count: `u32` + /// + /// Stack: func, this, argument_1, ... argument_n **=>** result + CallEvalWithRest, + /// Call a function. /// /// Operands: argument_count: `u32` @@ -862,14 +876,14 @@ pub enum Opcode { /// Push a declarative environment. /// - /// Operands: num_bindings: `u32` + /// Operands: num_bindings: `u32`, compile_environments_index: `u32` /// /// Stack: **=>** PushDeclarativeEnvironment, /// Push a function environment. /// - /// Operands: + /// Operands: num_bindings: `u32`, compile_environments_index: `u32` /// /// Stack: **=>** PushFunctionEnvironment, @@ -1155,6 +1169,8 @@ impl Opcode { Opcode::Default => "Default", Opcode::GetFunction => "GetFunction", Opcode::GetGenerator => "GetGenerator", + Opcode::CallEval => "CallEval", + Opcode::CallEvalWithRest => "CallEvalWithRest", Opcode::Call => "Call", Opcode::CallWithRest => "CallWithRest", Opcode::New => "New", @@ -1287,6 +1303,8 @@ impl Opcode { Opcode::Default => "INST - Default", Opcode::GetFunction => "INST - GetFunction", Opcode::GetGenerator => "INST - GetGenerator", + Opcode::CallEval => "INST - CallEval", + Opcode::CallEvalWithRest => "INST - CallEvalWithRest", Opcode::Call => "INST - Call", Opcode::CallWithRest => "INST - CallWithRest", Opcode::New => "INST - New",