From 45dd2d416cfd6209a76fc65c0a0126c128342f0e Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Sat, 21 May 2022 23:39:20 +0000 Subject: [PATCH] Remove `strict` flag from `Context` (#2069) The `Context` currently contains a `strict` flag that indicates is global strict mode is active. This is redundant to the strict flag that is set on every function and causes some non spec compliant situations. This pull request removes the strict flag from `Context` and fixes some resulting errors. Detailed changes: - Remove strict flag from `Context` - Make 262 tester compliant with the strict section in [test262/INTERPRETING.md](https://github.com/tc39/test262/blob/2e7cdfbe18eae4309677033673bb4b5ac6b1de40/INTERPRETING.md#strict-mode) - Make 262 tester compliant with the `raw` flag in [test262/INTERPRETING.md](https://github.com/tc39/test262/blob/2e7cdfbe18eae4309677033673bb4b5ac6b1de40/INTERPRETING.md#flags) - Allow function declarations in strict mode - Fix parser flag propagation for classes - Move some early errors from the lexer to the parser - Add / fix some early errors for 'arguments' and 'eval' identifier usage in strict mode - Refactor `ArrayLiteral` parser for readability and correct early errors --- boa_cli/src/main.rs | 2 +- boa_engine/src/builtins/eval/mod.rs | 7 +- boa_engine/src/context/mod.rs | 32 +- .../src/syntax/ast/node/await_expr/mod.rs | 7 + .../src/syntax/ast/node/declaration/mod.rs | 108 +++++ .../src/syntax/ast/node/identifier/mod.rs | 23 +- boa_engine/src/syntax/ast/node/mod.rs | 435 +++++++++++++++++- .../syntax/ast/node/operator/assign/mod.rs | 29 +- boa_engine/src/syntax/lexer/identifier.rs | 32 +- boa_engine/src/syntax/parser/error.rs | 2 +- .../parser/expression/assignment/mod.rs | 18 +- .../syntax/parser/expression/identifiers.rs | 2 +- .../src/syntax/parser/expression/mod.rs | 1 + .../primary/array_initializer/mod.rs | 85 ++-- .../syntax/parser/expression/primary/mod.rs | 3 +- .../src/syntax/parser/expression/update.rs | 56 +-- boa_engine/src/syntax/parser/function/mod.rs | 1 - .../src/syntax/parser/function/tests.rs | 2 +- boa_engine/src/syntax/parser/mod.rs | 20 +- .../src/syntax/parser/statement/block/mod.rs | 1 - .../declaration/hoistable/class_decl/mod.rs | 32 +- .../statement/declaration/hoistable/mod.rs | 2 +- boa_engine/src/syntax/parser/statement/mod.rs | 32 +- .../src/syntax/parser/statement/switch/mod.rs | 2 - boa_engine/src/syntax/parser/tests.rs | 6 +- boa_engine/src/tests.rs | 18 - boa_engine/src/vm/code_block.rs | 85 ++-- boa_engine/src/vm/mod.rs | 29 +- boa_tester/src/exec/mod.rs | 60 ++- 29 files changed, 839 insertions(+), 293 deletions(-) diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 93eae40257..c72d52bae7 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -141,7 +141,7 @@ where use boa_engine::syntax::parser::Parser; let src_bytes = src.as_ref(); - Parser::new(src_bytes, false) + Parser::new(src_bytes) .parse_all(context) .map_err(|e| format!("ParsingError: {e}")) } diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index f52108f533..54c04594e0 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -83,7 +83,12 @@ impl Eval { // 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()) { + let parse_result = if strict { + context.parse_strict(x.as_bytes()) + } else { + context.parse(x.as_bytes()) + }; + let body = match parse_result.map_err(|e| e.to_string()) { Ok(body) => body, Err(e) => return context.throw_syntax_error(e), }; diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 813bf6d8f0..080a3224a0 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -82,9 +82,6 @@ pub struct Context { /// Intrinsic objects intrinsics: Intrinsics, - /// Whether or not global strict mode is active. - strict: bool, - pub(crate) vm: Vm, } @@ -96,7 +93,6 @@ impl Default for Context { #[cfg(feature = "console")] console: Console::default(), intrinsics: Intrinsics::default(), - strict: false, vm: Vm { frame: None, stack: Vec::with_capacity(1024), @@ -149,18 +145,6 @@ impl Context { &mut self.console } - /// Returns if strict mode is currently active. - #[inline] - pub fn strict(&self) -> bool { - self.strict - } - - /// Set the global strict mode of the context. - #[inline] - pub fn set_strict_mode(&mut self, strict: bool) { - self.strict = strict; - } - /// Sets up the default global objects within Global #[inline] fn create_intrinsics(&mut self) { @@ -178,11 +162,23 @@ impl Context { ) } + /// Parse the given source text. pub fn parse(&mut self, src: S) -> Result where S: AsRef<[u8]>, { - Parser::new(src.as_ref(), self.strict).parse_all(self) + let mut parser = Parser::new(src.as_ref()); + parser.parse_all(self) + } + + /// Parse the given source text in strict mode. + pub(crate) fn parse_strict(&mut self, src: S) -> Result + where + S: AsRef<[u8]>, + { + let mut parser = Parser::new(src.as_ref()); + parser.set_strict(); + parser.parse_all(self) } /// @@ -641,7 +637,7 @@ impl Context { { let main_timer = Profiler::global().start_event("Evaluation", "Main"); - let parsing_result = Parser::new(src.as_ref(), false) + let parsing_result = Parser::new(src.as_ref()) .parse_all(self) .map_err(|e| e.to_string()); diff --git a/boa_engine/src/syntax/ast/node/await_expr/mod.rs b/boa_engine/src/syntax/ast/node/await_expr/mod.rs index f5a9703094..32237834f1 100644 --- a/boa_engine/src/syntax/ast/node/await_expr/mod.rs +++ b/boa_engine/src/syntax/ast/node/await_expr/mod.rs @@ -25,6 +25,13 @@ pub struct AwaitExpr { expr: Box, } +impl AwaitExpr { + /// Return the expression that should be awaited. + pub(crate) fn expr(&self) -> &Node { + &self.expr + } +} + impl From for AwaitExpr where T: Into>, diff --git a/boa_engine/src/syntax/ast/node/declaration/mod.rs b/boa_engine/src/syntax/ast/node/declaration/mod.rs index 4de2b26572..21514ca7fa 100644 --- a/boa_engine/src/syntax/ast/node/declaration/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/mod.rs @@ -276,6 +276,114 @@ impl DeclarationPattern { DeclarationPattern::Array(pattern) => pattern.init(), } } + + /// Returns true if the node contains a identifier reference named 'arguments'. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments + #[inline] + pub(crate) fn contains_arguments(&self) -> bool { + match self { + DeclarationPattern::Object(pattern) => { + if let Some(init) = pattern.init() { + if init.contains_arguments() { + return true; + } + } + for binding in pattern.bindings() { + match binding { + BindingPatternTypeObject::SingleName { + property_name, + default_init, + .. + } => { + if let PropertyName::Computed(node) = property_name { + if node.contains_arguments() { + return true; + } + } + if let Some(init) = default_init { + if init.contains_arguments() { + return true; + } + } + } + BindingPatternTypeObject::RestGetConstField { + get_const_field, .. + } => { + if get_const_field.obj().contains_arguments() { + return true; + } + } + BindingPatternTypeObject::BindingPattern { + ident, + pattern, + default_init, + } => { + if let PropertyName::Computed(node) = ident { + if node.contains_arguments() { + return true; + } + } + if pattern.contains_arguments() { + return true; + } + if let Some(init) = default_init { + if init.contains_arguments() { + return true; + } + } + } + _ => {} + } + } + } + DeclarationPattern::Array(pattern) => { + if let Some(init) = pattern.init() { + if init.contains_arguments() { + return true; + } + } + for binding in pattern.bindings() { + match binding { + BindingPatternTypeArray::SingleName { + default_init: Some(init), + .. + } => { + if init.contains_arguments() { + return true; + } + } + BindingPatternTypeArray::GetField { get_field } + | BindingPatternTypeArray::GetFieldRest { get_field } => { + if get_field.obj().contains_arguments() { + return true; + } + if get_field.field().contains_arguments() { + return true; + } + } + BindingPatternTypeArray::GetConstField { get_const_field } + | BindingPatternTypeArray::GetConstFieldRest { get_const_field } => { + if get_const_field.obj().contains_arguments() { + return true; + } + } + BindingPatternTypeArray::BindingPattern { pattern } + | BindingPatternTypeArray::BindingPatternRest { pattern } => { + if pattern.contains_arguments() { + return true; + } + } + _ => {} + } + } + } + } + false + } } /// `DeclarationPatternObject` represents an object binding pattern. diff --git a/boa_engine/src/syntax/ast/node/identifier/mod.rs b/boa_engine/src/syntax/ast/node/identifier/mod.rs index 7bec5fc53c..1864556953 100644 --- a/boa_engine/src/syntax/ast/node/identifier/mod.rs +++ b/boa_engine/src/syntax/ast/node/identifier/mod.rs @@ -1,6 +1,9 @@ //! Local identifier node. -use crate::syntax::ast::node::Node; +use crate::syntax::{ + ast::{node::Node, Position}, + parser::ParseError, +}; use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -39,6 +42,24 @@ impl Identifier { pub fn sym(self) -> Sym { self.ident } + + /// Returns an error if `arguments` or `eval` are used as identifier in strict mode. + pub(crate) fn check_strict_arguments_or_eval( + self, + position: Position, + ) -> Result<(), ParseError> { + match self.ident { + Sym::ARGUMENTS => Err(ParseError::general( + "unexpected identifier 'arguments' in strict mode", + position, + )), + Sym::EVAL => Err(ParseError::general( + "unexpected identifier 'eval' in strict mode", + position, + )), + _ => Ok(()), + } + } } impl ToInternedString for Identifier { diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index bfb4200a64..0e1d15c8e7 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -23,7 +23,6 @@ pub mod throw; pub mod try_node; pub mod r#yield; -use self::field::get_private_field::GetPrivateField; pub use self::{ array::ArrayDecl, await_expr::AwaitExpr, @@ -36,7 +35,7 @@ pub use self::{ ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, DeclarationPattern, FunctionDecl, FunctionExpr, }, - field::{GetConstField, GetField}, + field::{get_private_field::GetPrivateField, GetConstField, GetField}, identifier::Identifier, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, new::New, @@ -52,6 +51,11 @@ pub use self::{ throw::Throw, try_node::{Catch, Finally, Try}, }; +use self::{ + declaration::class_decl::ClassElement, + iteration::IterableLoopInitializer, + object::{MethodDefinition, PropertyDefinition}, +}; pub(crate) use self::parameters::FormalParameterListFlags; @@ -397,7 +401,7 @@ impl Node { for_loop.body().var_declared_names(vars); } Node::ForInLoop(for_in_loop) => { - if let iteration::IterableLoopInitializer::Var(declaration) = for_in_loop.init() { + if let IterableLoopInitializer::Var(declaration) = for_in_loop.init() { match declaration { Declaration::Identifier { ident, .. } => { vars.insert(ident.sym()); @@ -412,7 +416,7 @@ impl Node { for_in_loop.body().var_declared_names(vars); } Node::ForOfLoop(for_of_loop) => { - if let iteration::IterableLoopInitializer::Var(declaration) = for_of_loop.init() { + if let IterableLoopInitializer::Var(declaration) = for_of_loop.init() { match declaration { Declaration::Identifier { ident, .. } => { vars.insert(ident.sym()); @@ -456,6 +460,427 @@ impl Node { _ => {} } } + + /// Returns true if the node contains a identifier reference named 'arguments'. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments + pub(crate) fn contains_arguments(&self) -> bool { + match self { + Node::Identifier(ident) if ident.sym() == Sym::ARGUMENTS => return true, + Node::ArrayDecl(array) => { + for node in array.as_ref() { + if node.contains_arguments() { + return true; + } + } + } + Node::ArrowFunctionDecl(decl) => { + for node in decl.body().items() { + if node.contains_arguments() { + return true; + } + } + } + Node::Assign(assign) => { + if assign.rhs().contains_arguments() { + return true; + } + } + Node::AwaitExpr(r#await) => { + if r#await.expr().contains_arguments() { + return true; + } + } + Node::BinOp(bin_op) => { + if bin_op.lhs().contains_arguments() || bin_op.rhs().contains_arguments() { + return true; + } + } + Node::Block(block) => { + for node in block.items() { + if node.contains_arguments() { + return true; + } + } + } + Node::Call(call) => { + if call.expr().contains_arguments() { + return true; + } + for node in call.args() { + if node.contains_arguments() { + return true; + } + } + } + Node::ConditionalOp(conditional) => { + if conditional.cond().contains_arguments() { + return true; + } + if conditional.if_true().contains_arguments() { + return true; + } + if conditional.if_false().contains_arguments() { + return true; + } + } + Node::DoWhileLoop(do_while_loop) => { + if do_while_loop.body().contains_arguments() { + return true; + } + if do_while_loop.cond().contains_arguments() { + return true; + } + } + Node::GetConstField(get_const_field) => { + if get_const_field.obj().contains_arguments() { + return true; + } + } + Node::GetPrivateField(get_private_field) => { + if get_private_field.obj().contains_arguments() { + return true; + } + } + Node::GetField(get_field) => { + if get_field.obj().contains_arguments() { + return true; + } + if get_field.field().contains_arguments() { + return true; + } + } + Node::ForLoop(for_loop) => { + if let Some(node) = for_loop.init() { + if node.contains_arguments() { + return true; + } + } + if let Some(node) = for_loop.condition() { + if node.contains_arguments() { + return true; + } + } + if let Some(node) = for_loop.final_expr() { + if node.contains_arguments() { + return true; + } + } + if for_loop.body().contains_arguments() { + return true; + } + } + Node::ForInLoop(for_in_loop) => { + match for_in_loop.init() { + IterableLoopInitializer::Var(declaration) + | IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => match declaration { + Declaration::Identifier { init, .. } => { + if let Some(init) = init { + { + if init.contains_arguments() { + return true; + } + } + } + } + Declaration::Pattern(pattern) => { + if pattern.contains_arguments() { + return true; + } + } + }, + IterableLoopInitializer::DeclarationPattern(pattern) => { + if pattern.contains_arguments() { + return true; + } + } + IterableLoopInitializer::Identifier(_) => {} + } + if for_in_loop.expr().contains_arguments() { + return true; + } + if for_in_loop.body().contains_arguments() { + return true; + } + } + Node::ForOfLoop(for_of_loop) => { + match for_of_loop.init() { + IterableLoopInitializer::Var(declaration) + | IterableLoopInitializer::Let(declaration) + | IterableLoopInitializer::Const(declaration) => match declaration { + Declaration::Identifier { init, .. } => { + if let Some(init) = init { + { + if init.contains_arguments() { + return true; + } + } + } + } + Declaration::Pattern(pattern) => { + if pattern.contains_arguments() { + return true; + } + } + }, + IterableLoopInitializer::DeclarationPattern(pattern) => { + if pattern.contains_arguments() { + return true; + } + } + IterableLoopInitializer::Identifier(_) => {} + } + if for_of_loop.iterable().contains_arguments() { + return true; + } + if for_of_loop.body().contains_arguments() { + return true; + } + } + Node::If(r#if) => { + if r#if.cond().contains_arguments() { + return true; + } + if r#if.body().contains_arguments() { + return true; + } + if let Some(node) = r#if.else_node() { + if node.contains_arguments() { + return true; + } + } + } + Node::VarDeclList(decl_list) + | Node::ConstDeclList(decl_list) + | Node::LetDeclList(decl_list) => match decl_list { + DeclarationList::Const(declarations) + | DeclarationList::Let(declarations) + | DeclarationList::Var(declarations) => { + for declaration in declarations.iter() { + match declaration { + Declaration::Identifier { init, .. } => { + if let Some(init) = init { + { + if init.contains_arguments() { + return true; + } + } + } + } + Declaration::Pattern(pattern) => { + if pattern.contains_arguments() { + return true; + } + } + } + } + } + }, + Node::New(new) => { + if new.expr().contains_arguments() { + return true; + } + for node in new.args() { + if node.contains_arguments() { + return true; + } + } + } + Node::Object(object) => { + for property in object.properties() { + match property { + PropertyDefinition::IdentifierReference(ident) => { + if *ident == Sym::ARGUMENTS { + return true; + } + } + PropertyDefinition::Property(_, node) + | PropertyDefinition::SpreadObject(node) => { + if node.contains_arguments() { + return true; + } + } + PropertyDefinition::MethodDefinition(method, _) => match method { + MethodDefinition::Get(function) + | MethodDefinition::Set(function) + | MethodDefinition::Ordinary(function) => { + if let Some(Sym::ARGUMENTS) = function.name() { + return true; + } + } + MethodDefinition::Generator(generator) => { + if let Some(Sym::ARGUMENTS) = generator.name() { + return true; + } + } + MethodDefinition::AsyncGenerator(async_generator) => { + if let Some(Sym::ARGUMENTS) = async_generator.name() { + return true; + } + } + MethodDefinition::Async(function) => { + if let Some(Sym::ARGUMENTS) = function.name() { + return true; + } + } + }, + } + } + } + Node::Return(r#return) => { + if let Some(node) = r#return.expr() { + if node.contains_arguments() { + return true; + } + } + } + Node::Switch(r#switch) => { + if r#switch.val().contains_arguments() { + return true; + } + for case in r#switch.cases() { + if case.condition().contains_arguments() { + return true; + } + for node in case.body().items() { + if node.contains_arguments() { + return true; + } + } + } + } + Node::Spread(spread) => { + if spread.val().contains_arguments() { + return true; + } + } + Node::TaggedTemplate(tagged_template) => { + if tagged_template.tag().contains_arguments() { + return true; + } + for node in tagged_template.exprs() { + if node.contains_arguments() { + return true; + } + } + } + Node::TemplateLit(template_lit) => { + for element in template_lit.elements() { + if let template::TemplateElement::Expr(node) = element { + if node.contains_arguments() { + return false; + } + } + } + } + Node::Throw(throw) => { + if throw.expr().contains_arguments() { + return true; + } + } + Node::Try(r#try) => { + for node in r#try.block().items() { + if node.contains_arguments() { + return true; + } + } + if let Some(catch) = r#try.catch() { + for node in catch.block().items() { + if node.contains_arguments() { + return true; + } + } + } + if let Some(finally) = r#try.finally() { + for node in finally.items() { + if node.contains_arguments() { + return true; + } + } + } + } + Node::UnaryOp(unary_op) => { + if unary_op.target().contains_arguments() { + return true; + } + } + Node::WhileLoop(while_loop) => { + if while_loop.cond().contains_arguments() { + return true; + } + if while_loop.body().contains_arguments() { + return true; + } + } + Node::Yield(r#yield) => { + if let Some(node) = r#yield.expr() { + if node.contains_arguments() { + return true; + } + } + } + Node::ClassExpr(class) | Node::ClassDecl(class) => { + if let Some(node) = class.super_ref() { + if node.contains_arguments() { + return true; + } + for element in class.elements() { + match element { + ClassElement::MethodDefinition(_, method) + | ClassElement::StaticMethodDefinition(_, method) => match method { + MethodDefinition::Get(function) + | MethodDefinition::Set(function) + | MethodDefinition::Ordinary(function) => { + if let Some(Sym::ARGUMENTS) = function.name() { + return true; + } + } + MethodDefinition::Generator(generator) => { + if let Some(Sym::ARGUMENTS) = generator.name() { + return true; + } + } + MethodDefinition::AsyncGenerator(async_generator) => { + if let Some(Sym::ARGUMENTS) = async_generator.name() { + return true; + } + } + MethodDefinition::Async(function) => { + if let Some(Sym::ARGUMENTS) = function.name() { + return true; + } + } + }, + ClassElement::FieldDefinition(_, node) + | ClassElement::StaticFieldDefinition(_, node) + | ClassElement::PrivateFieldDefinition(_, node) + | ClassElement::PrivateStaticFieldDefinition(_, node) => { + if let Some(node) = node { + if node.contains_arguments() { + return true; + } + } + } + ClassElement::StaticBlock(statement_list) => { + for node in statement_list.items() { + if node.contains_arguments() { + return true; + } + } + } + _ => {} + } + } + } + } + _ => {} + } + false + } } impl ToInternedString for Node { @@ -509,7 +934,7 @@ fn test_formatting(source: &'static str) { .collect::>() .join("\n"); let mut context = Context::default(); - let result = Parser::new(scenario.as_bytes(), false) + let result = Parser::new(scenario.as_bytes()) .parse_all(&mut context) .expect("parsing failed") .to_interned_string(context.interner()); diff --git a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs b/boa_engine/src/syntax/ast/node/operator/assign/mod.rs index ed5b7c027a..c05cab7df0 100644 --- a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa_engine/src/syntax/ast/node/operator/assign/mod.rs @@ -1,11 +1,14 @@ -use crate::syntax::ast::node::{ - declaration::{ - BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPatternArray, - DeclarationPatternObject, +use crate::syntax::{ + ast::node::{ + declaration::{ + BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPatternArray, + DeclarationPatternObject, + }, + field::get_private_field::GetPrivateField, + object::{PropertyDefinition, PropertyName}, + ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object, }, - field::get_private_field::GetPrivateField, - object::{PropertyDefinition, PropertyName}, - ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object, + parser::RESERVED_IDENTIFIERS_STRICT, }; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -152,6 +155,10 @@ pub(crate) fn object_decl_to_declaration_pattern( return None } PropertyDefinition::IdentifierReference(ident) => { + if strict && RESERVED_IDENTIFIERS_STRICT.contains(ident) { + return None; + } + excluded_keys.push(*ident); bindings.push(BindingPatternTypeObject::SingleName { ident: *ident, @@ -164,6 +171,10 @@ pub(crate) fn object_decl_to_declaration_pattern( if strict && *name == Sym::EVAL { return None; } + if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) { + return None; + } + excluded_keys.push(*name); bindings.push(BindingPatternTypeObject::SingleName { ident: *name, @@ -217,6 +228,10 @@ pub(crate) fn array_decl_to_declaration_pattern( for (i, node) in array.as_ref().iter().enumerate() { match node { Node::Identifier(ident) => { + if strict && ident.sym() == Sym::ARGUMENTS { + return None; + } + bindings.push(BindingPatternTypeArray::SingleName { ident: ident.sym(), default_init: None, diff --git a/boa_engine/src/syntax/lexer/identifier.rs b/boa_engine/src/syntax/lexer/identifier.rs index 330d73ec90..0722c6bf17 100644 --- a/boa_engine/src/syntax/lexer/identifier.rs +++ b/boa_engine/src/syntax/lexer/identifier.rs @@ -8,19 +8,7 @@ use crate::syntax::{ use boa_interner::Interner; use boa_profiler::Profiler; use boa_unicode::UnicodeProperties; -use std::{io::Read, str}; - -const STRICT_FORBIDDEN_IDENTIFIERS: [&str; 9] = [ - "implements", - "interface", - "let", - "package", - "private", - "protected", - "public", - "static", - "yield", -]; +use std::io::Read; /// Identifier lexing. /// @@ -90,13 +78,6 @@ impl Tokenizer for Identifier { Self::take_identifier_name(cursor, start_pos, self.init)?; let token_kind = if let Ok(keyword) = identifier_name.parse() { - if cursor.strict_mode() && keyword == Keyword::With { - return Err(Error::Syntax( - "using 'with' statement not allowed in strict mode".into(), - start_pos, - )); - } - match keyword { Keyword::True => TokenKind::BooleanLiteral(true), Keyword::False => TokenKind::BooleanLiteral(false), @@ -104,17 +85,6 @@ impl Tokenizer for Identifier { _ => TokenKind::Keyword((keyword, contains_escaped_chars)), } } else { - if cursor.strict_mode() - && STRICT_FORBIDDEN_IDENTIFIERS.contains(&identifier_name.as_str()) - { - return Err(Error::Syntax( - format!( - "using future reserved keyword '{identifier_name}' not allowed in strict mode", - ) - .into(), - start_pos, - )); - } TokenKind::identifier(interner.get_or_intern(identifier_name)) }; diff --git a/boa_engine/src/syntax/parser/error.rs b/boa_engine/src/syntax/parser/error.rs index 8472326863..80f69d5be9 100644 --- a/boa_engine/src/syntax/parser/error.rs +++ b/boa_engine/src/syntax/parser/error.rs @@ -98,7 +98,7 @@ impl ParseError { } /// Creates a "general" parsing error. - pub(super) fn general(message: &'static str, position: Position) -> Self { + pub(crate) fn general(message: &'static str, position: Position) -> Self { Self::General { message, position } } diff --git a/boa_engine/src/syntax/parser/expression/assignment/mod.rs b/boa_engine/src/syntax/parser/expression/assignment/mod.rs index 3b6ea3776f..5511ec705a 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/mod.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/mod.rs @@ -214,17 +214,7 @@ where TokenKind::Punctuator(Punctuator::Assign) => { if cursor.strict_mode() { if let Node::Identifier(ident) = lhs { - if ident.sym() == Sym::ARGUMENTS { - return Err(ParseError::lex(LexError::Syntax( - "unexpected identifier 'arguments' in strict mode".into(), - position, - ))); - } else if ident.sym() == Sym::EVAL { - return Err(ParseError::lex(LexError::Syntax( - "unexpected identifier 'eval' in strict mode".into(), - position, - ))); - } + ident.check_strict_arguments_or_eval(position)?; } } @@ -243,6 +233,12 @@ where } } TokenKind::Punctuator(p) if p.as_binop().is_some() && p != &Punctuator::Comma => { + if cursor.strict_mode() { + if let Node::Identifier(ident) = lhs { + ident.check_strict_arguments_or_eval(position)?; + } + } + cursor.next(interner)?.expect("token vanished"); if is_assignable(&lhs) { let binop = p.as_binop().expect("binop disappeared"); diff --git a/boa_engine/src/syntax/parser/expression/identifiers.rs b/boa_engine/src/syntax/parser/expression/identifiers.rs index 43672b79b7..24d4c261cc 100644 --- a/boa_engine/src/syntax/parser/expression/identifiers.rs +++ b/boa_engine/src/syntax/parser/expression/identifiers.rs @@ -15,7 +15,7 @@ use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; use std::io::Read; -const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [ +pub(in crate::syntax) const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [ Sym::IMPLEMENTS, Sym::INTERFACE, Sym::LET, diff --git a/boa_engine/src/syntax/parser/expression/mod.rs b/boa_engine/src/syntax/parser/expression/mod.rs index b866348337..7df5de551d 100644 --- a/boa_engine/src/syntax/parser/expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/mod.rs @@ -36,6 +36,7 @@ use boa_profiler::Profiler; use std::io::Read; pub(super) use self::{assignment::AssignmentExpression, primary::Initializer}; +pub(in crate::syntax) use identifiers::RESERVED_IDENTIFIERS_STRICT; pub(in crate::syntax::parser) use { identifiers::{BindingIdentifier, LabelIdentifier}, left_hand_side::LeftHandSideExpression, diff --git a/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs index 4fb173d32a..df9097f3e0 100644 --- a/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs @@ -66,40 +66,69 @@ where let _timer = Profiler::global().start_event("ArrayLiteral", "Parsing"); let mut elements = Vec::new(); let mut has_trailing_comma_spread = false; - loop { - // TODO: Support all features. - while cursor.next_if(Punctuator::Comma, interner)?.is_some() { - elements.push(Node::Empty); - } - - if cursor - .next_if(Punctuator::CloseBracket, interner)? - .is_some() - { - break; - } + let mut next_comma = false; + let mut last_spread = false; - let _next = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd); // Check that there are more tokens to read. + loop { + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Punctuator(Punctuator::CloseBracket) => { + cursor.next(interner).expect("token disappeared"); + break; + } + TokenKind::Punctuator(Punctuator::Comma) if next_comma => { + cursor.next(interner).expect("token disappeared"); - if cursor.next_if(Punctuator::Spread, interner)?.is_some() { - let node = - AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - elements.push(Spread::new(node).into()); - // If the last element in the array is followed by a comma, push an elision. - if cursor.next_if(Punctuator::Comma, interner)?.is_some() { - if let Some(t) = cursor.peek(0, interner)? { - if *t.kind() == TokenKind::Punctuator(Punctuator::CloseBracket) { + if last_spread { + let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + if token.kind() == &TokenKind::Punctuator(Punctuator::CloseBracket) { has_trailing_comma_spread = true; } } + + next_comma = false; + } + TokenKind::Punctuator(Punctuator::Comma) => { + cursor.next(interner).expect("token disappeared"); + elements.push(Node::Empty); } - } else { - elements.push( - AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?, - ); - cursor.next_if(Punctuator::Comma, interner)?; + TokenKind::Punctuator(Punctuator::Spread) if next_comma => { + return Err(ParseError::unexpected( + token.to_string(interner), + token.span(), + "expected comma or end of array", + )); + } + TokenKind::Punctuator(Punctuator::Spread) => { + cursor.next(interner).expect("token disappeared"); + let node = + AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + elements.push(Spread::new(node).into()); + next_comma = true; + last_spread = true; + } + _ if next_comma => { + return Err(ParseError::unexpected( + token.to_string(interner), + token.span(), + "expected comma or end of array", + )); + } + _ => { + let node = + AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + elements.push(node); + next_comma = true; + last_spread = false; + } + } + } + + if last_spread { + if let Some(Node::Empty) = elements.last() { + has_trailing_comma_spread = true; } } diff --git a/boa_engine/src/syntax/parser/expression/primary/mod.rs b/boa_engine/src/syntax/parser/expression/primary/mod.rs index c3e216562c..eea2ecd247 100644 --- a/boa_engine/src/syntax/parser/expression/primary/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/mod.rs @@ -113,7 +113,8 @@ where } TokenKind::Keyword((Keyword::Class, _)) => { cursor.next(interner).expect("token disappeared"); - ClassExpression::new(self.name, false, false).parse(cursor, interner) + ClassExpression::new(self.name, self.allow_yield, self.allow_await) + .parse(cursor, interner) } TokenKind::Keyword((Keyword::Async, false)) => { cursor.next(interner).expect("token disappeared"); diff --git a/boa_engine/src/syntax/parser/expression/update.rs b/boa_engine/src/syntax/parser/expression/update.rs index 67d48202e2..c3364a8b59 100644 --- a/boa_engine/src/syntax/parser/expression/update.rs +++ b/boa_engine/src/syntax/parser/expression/update.rs @@ -57,56 +57,46 @@ where let _timer = Profiler::global().start_event("UpdateExpression", "Parsing"); let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let position = tok.span().start(); match tok.kind() { TokenKind::Punctuator(Punctuator::Inc) => { cursor .next(interner)? .expect("Punctuator::Inc token disappeared"); - return Ok(node::UnaryOp::new( - UnaryOp::IncrementPre, - UnaryExpression::new(self.name, self.allow_yield, self.allow_await) - .parse(cursor, interner)?, - ) - .into()); + + let target = UnaryExpression::new(self.name, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + + if cursor.strict_mode() { + if let Node::Identifier(ident) = target { + ident.check_strict_arguments_or_eval(position)?; + } + } + + return Ok(node::UnaryOp::new(UnaryOp::IncrementPre, target).into()); } TokenKind::Punctuator(Punctuator::Dec) => { cursor .next(interner)? .expect("Punctuator::Dec token disappeared"); - return Ok(node::UnaryOp::new( - UnaryOp::DecrementPre, - UnaryExpression::new(self.name, self.allow_yield, self.allow_await) - .parse(cursor, interner)?, - ) - .into()); + + let target = UnaryExpression::new(self.name, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + + if cursor.strict_mode() { + if let Node::Identifier(ident) = target { + ident.check_strict_arguments_or_eval(position)?; + } + } + + return Ok(node::UnaryOp::new(UnaryOp::DecrementPre, target).into()); } _ => {} } - let position = cursor - .peek(0, interner)? - .ok_or(ParseError::AbruptEnd)? - .span() - .start(); let lhs = LeftHandSideExpression::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner)?; - if cursor.strict_mode() { - if let Node::Identifier(ident) = lhs { - if ident.sym() == Sym::ARGUMENTS { - return Err(ParseError::lex(LexError::Syntax( - "unexpected identifier 'arguments' in strict mode".into(), - position, - ))); - } else if ident.sym() == Sym::EVAL { - return Err(ParseError::lex(LexError::Syntax( - "unexpected identifier 'eval' in strict mode".into(), - position, - ))); - } - } - } - let strict = cursor.strict_mode(); if let Some(tok) = cursor.peek(0, interner)? { let token_start = tok.span().start(); diff --git a/boa_engine/src/syntax/parser/function/mod.rs b/boa_engine/src/syntax/parser/function/mod.rs index 731e33ed33..5d368d4a19 100644 --- a/boa_engine/src/syntax/parser/function/mod.rs +++ b/boa_engine/src/syntax/parser/function/mod.rs @@ -541,7 +541,6 @@ where self.allow_yield, self.allow_await, true, - true, &FUNCTION_BREAK_TOKENS, ) .parse(cursor, interner); diff --git a/boa_engine/src/syntax/parser/function/tests.rs b/boa_engine/src/syntax/parser/function/tests.rs index 4ff08570ac..b1b5359f94 100644 --- a/boa_engine/src/syntax/parser/function/tests.rs +++ b/boa_engine/src/syntax/parser/function/tests.rs @@ -70,7 +70,7 @@ fn check_duplicates_strict_on() { let js = "'use strict'; function foo(a, a) {}"; let mut context = Context::default(); - let res = Parser::new(js.as_bytes(), false).parse_all(&mut context); + let res = Parser::new(js.as_bytes()).parse_all(&mut context); dbg!(&res); assert!(res.is_err()); } diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index 667fdabd5f..e001894a94 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/boa_engine/src/syntax/parser/mod.rs @@ -24,6 +24,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::io::Read; pub use self::error::{ParseError, ParseResult}; +pub(in crate::syntax) use expression::RESERVED_IDENTIFIERS_STRICT; /// Trait implemented by parsers. /// @@ -103,14 +104,21 @@ pub struct Parser { impl Parser { /// Create a new `Parser` with a reader as the input to parse. - pub fn new(reader: R, strict_mode: bool) -> Self + pub fn new(reader: R) -> Self where R: Read, { - let mut cursor = Cursor::new(reader); - cursor.set_strict_mode(strict_mode); + Self { + cursor: Cursor::new(reader), + } + } - Self { cursor } + /// Set the parser strict mode to true. + pub(crate) fn set_strict(&mut self) + where + R: Read, + { + self.cursor.set_strict_mode(true); } /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation. @@ -204,9 +212,9 @@ where cursor: &mut Cursor, interner: &mut Interner, ) -> Result { + let mut strict = cursor.strict_mode(); match cursor.peek(0, interner)? { Some(tok) => { - let mut strict = false; match tok.kind() { // Set the strict mode TokenKind::StringLiteral(string) @@ -246,6 +254,6 @@ where cursor: &mut Cursor, interner: &mut Interner, ) -> Result { - self::statement::StatementList::new(false, false, false, false, &[]).parse(cursor, interner) + self::statement::StatementList::new(false, false, false, &[]).parse(cursor, interner) } } diff --git a/boa_engine/src/syntax/parser/statement/block/mod.rs b/boa_engine/src/syntax/parser/statement/block/mod.rs index cc5996bbf5..4ff1aa4ed5 100644 --- a/boa_engine/src/syntax/parser/statement/block/mod.rs +++ b/boa_engine/src/syntax/parser/statement/block/mod.rs @@ -91,7 +91,6 @@ where self.allow_yield, self.allow_await, self.allow_return, - true, &BLOCK_BREAK_TOKENS, ) .parse(cursor, interner) diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs index 96f1ef3932..368ec7d456 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -515,6 +515,7 @@ where }; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + let position = token.span().start(); let element = match token.kind() { TokenKind::Identifier(Sym::CONSTRUCTOR) if !r#static => { cursor.next(interner).expect("token disappeared"); @@ -561,7 +562,7 @@ where .span() .start(); let statement_list = - StatementList::new(false, true, false, true, &FUNCTION_BREAK_TOKENS) + StatementList::new(false, true, false, &FUNCTION_BREAK_TOKENS) .parse(cursor, interner)?; let lexically_declared_names = statement_list.lexically_declared_names(); @@ -1243,6 +1244,35 @@ where } }; + match &element { + // FieldDefinition : ClassElementName Initializer [opt] + // It is a Syntax Error if Initializer is present and ContainsArguments of Initializer is true. + ClassElementNode::FieldDefinition(_, Some(node)) + | ClassElementNode::StaticFieldDefinition(_, Some(node)) + | ClassElementNode::PrivateFieldDefinition(_, Some(node)) + | ClassElementNode::PrivateStaticFieldDefinition(_, Some(node)) => { + if node.contains_arguments() { + return Err(ParseError::general( + "'arguments' not allowed in class field definition", + position, + )); + } + } + // ClassStaticBlockBody : ClassStaticBlockStatementList + // It is a Syntax Error if ContainsArguments of ClassStaticBlockStatementList is true. + ClassElementNode::StaticBlock(block) => { + for node in block.items() { + if node.contains_arguments() { + return Err(ParseError::general( + "'arguments' not allowed in class static block", + position, + )); + } + } + } + _ => {} + } + Ok((None, Some(element))) } } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs index 5e3b0110e0..8700afc544 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -111,7 +111,7 @@ where } } TokenKind::Keyword((Keyword::Class, false)) => { - ClassDeclaration::new(false, false, self.is_default) + ClassDeclaration::new(self.allow_yield, self.allow_await, false) .parse(cursor, interner) .map(Node::from) } diff --git a/boa_engine/src/syntax/parser/statement/mod.rs b/boa_engine/src/syntax/parser/statement/mod.rs index 6e9edd584e..49fe736ec1 100644 --- a/boa_engine/src/syntax/parser/statement/mod.rs +++ b/boa_engine/src/syntax/parser/statement/mod.rs @@ -240,7 +240,6 @@ pub(super) struct StatementList { allow_yield: AllowYield, allow_await: AllowAwait, allow_return: AllowReturn, - in_block: bool, break_nodes: &'static [TokenKind], } @@ -250,7 +249,6 @@ impl StatementList { allow_yield: Y, allow_await: A, allow_return: R, - in_block: bool, break_nodes: &'static [TokenKind], ) -> Self where @@ -262,7 +260,6 @@ impl StatementList { allow_yield: allow_yield.into(), allow_await: allow_await.into(), allow_return: allow_return.into(), - in_block, break_nodes, } } @@ -299,13 +296,9 @@ where _ => {} } - let item = StatementListItem::new( - self.allow_yield, - self.allow_await, - self.allow_return, - self.in_block, - ) - .parse(cursor, interner)?; + let item = + StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor, interner)?; items.push(item); // move the cursor forward for any consecutive semicolon. @@ -333,12 +326,11 @@ struct StatementListItem { allow_yield: AllowYield, allow_await: AllowAwait, allow_return: AllowReturn, - in_block: bool, } impl StatementListItem { /// Creates a new `StatementListItem` parser. - fn new(allow_yield: Y, allow_await: A, allow_return: R, in_block: bool) -> Self + fn new(allow_yield: Y, allow_await: A, allow_return: R) -> Self where Y: Into, A: Into, @@ -348,7 +340,6 @@ impl StatementListItem { allow_yield: allow_yield.into(), allow_await: allow_await.into(), allow_return: allow_return.into(), - in_block, } } } @@ -365,20 +356,13 @@ where interner: &mut Interner, ) -> Result { let _timer = Profiler::global().start_event("StatementListItem", "Parsing"); - let strict_mode = cursor.strict_mode(); let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; match *tok.kind() { - TokenKind::Keyword((Keyword::Function | Keyword::Async | Keyword::Class, _)) => { - if strict_mode && self.in_block { - return Err(ParseError::lex(LexError::Syntax( - "Function declaration in blocks not allowed in strict mode".into(), - tok.span().start(), - ))); - } - Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner) - } - TokenKind::Keyword((Keyword::Const | Keyword::Let, _)) => { + TokenKind::Keyword(( + Keyword::Function | Keyword::Async | Keyword::Class | Keyword::Const | Keyword::Let, + _, + )) => { Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner) } _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) diff --git a/boa_engine/src/syntax/parser/statement/switch/mod.rs b/boa_engine/src/syntax/parser/statement/switch/mod.rs index c913930624..48f0de0b3a 100644 --- a/boa_engine/src/syntax/parser/statement/switch/mod.rs +++ b/boa_engine/src/syntax/parser/statement/switch/mod.rs @@ -150,7 +150,6 @@ where self.allow_yield, self.allow_await, self.allow_return, - false, &CASE_BREAK_TOKENS, ) .parse(cursor, interner)?; @@ -173,7 +172,6 @@ where self.allow_yield, self.allow_await, self.allow_return, - false, &CASE_BREAK_TOKENS, ) .parse(cursor, interner)?; diff --git a/boa_engine/src/syntax/parser/tests.rs b/boa_engine/src/syntax/parser/tests.rs index ed20923b08..76bcdec1cf 100644 --- a/boa_engine/src/syntax/parser/tests.rs +++ b/boa_engine/src/syntax/parser/tests.rs @@ -25,7 +25,7 @@ where { let mut context = Context::new(interner); assert_eq!( - Parser::new(js.as_bytes(), false) + Parser::new(js.as_bytes()) .parse_all(&mut context) .expect("failed to parse"), StatementList::from(expr) @@ -36,9 +36,7 @@ where #[track_caller] pub(super) fn check_invalid(js: &str) { let mut context = Context::default(); - assert!(Parser::new(js.as_bytes(), false) - .parse_all(&mut context) - .is_err()); + assert!(Parser::new(js.as_bytes()).parse_all(&mut context).is_err()); } /// Should be parsed as `new Class().method()` instead of `new (Class().method())` diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs index e5e8d9dcbe..4e71550b42 100644 --- a/boa_engine/src/tests.rs +++ b/boa_engine/src/tests.rs @@ -1607,24 +1607,6 @@ fn test_strict_mode_reserved_name() { } } -#[test] -fn test_strict_mode_func_decl_in_block() { - // Checks that a function declaration in a block is an error in - // strict mode code as per https://tc39.es/ecma262/#early-error. - - let scenario = r#" - 'use strict'; - let a = 4; - let b = 5; - if (a < b) { function f() {} } - "#; - - check_output(&[TestAction::TestStartsWith( - scenario, - "Uncaught \"SyntaxError\": ", - )]); -} - #[test] fn test_strict_mode_dup_func_parameters() { // Checks that a function cannot contain duplicate parameter diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 80e3c34f10..d50889a9bd 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -634,10 +634,14 @@ impl JsObject { } else { context.global_object().clone().into() } - } else if (!code.strict && !context.strict()) && this.is_null_or_undefined() { + } else if code.strict { + this.clone() + } else if this.is_null_or_undefined() { context.global_object().clone().into() } else { - this.clone() + this.to_object(context) + .expect("conversion cannot fail") + .into() }; if code.params.has_expressions() { @@ -655,19 +659,18 @@ impl JsObject { } if let Some(binding) = code.arguments_binding { - let arguments_obj = - if context.strict() || code.strict || !code.params.is_simple() { - Arguments::create_unmapped_arguments_object(args, context) - } else { - let env = context.realm.environments.current(); - Arguments::create_mapped_arguments_object( - &this_function_object, - &code.params, - args, - &env, - context, - ) - }; + let arguments_obj = if code.strict || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.realm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + &env, + context, + ) + }; context.realm.environments.put_value( binding.environment_index(), binding.binding_index(), @@ -742,7 +745,7 @@ impl JsObject { } else { context.global_object().clone().into() } - } else if (!code.strict && !context.strict()) && this.is_null_or_undefined() { + } else if !code.strict && this.is_null_or_undefined() { context.global_object().clone().into() } else { this.clone() @@ -763,19 +766,18 @@ impl JsObject { } if let Some(binding) = code.arguments_binding { - let arguments_obj = - if context.strict() || code.strict || !code.params.is_simple() { - Arguments::create_unmapped_arguments_object(args, context) - } else { - let env = context.realm.environments.current(); - Arguments::create_mapped_arguments_object( - &this_function_object, - &code.params, - args, - &env, - context, - ) - }; + let arguments_obj = if code.strict || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.realm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + &env, + context, + ) + }; context.realm.environments.put_value( binding.environment_index(), binding.binding_index(), @@ -957,19 +959,18 @@ impl JsObject { } if let Some(binding) = code.arguments_binding { - let arguments_obj = - if context.strict() || code.strict || !code.params.is_simple() { - Arguments::create_unmapped_arguments_object(args, context) - } else { - let env = context.realm.environments.current(); - Arguments::create_mapped_arguments_object( - &this_function_object, - &code.params, - args, - &env, - context, - ) - }; + let arguments_obj = if code.strict || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.realm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + &env, + context, + ) + }; context.realm.environments.put_value( binding.environment_index(), binding.binding_index(), diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 62e09cc175..176e4f1388 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -9,7 +9,7 @@ use crate::{ value::Numeric, vm::{ call_frame::CatchAddresses, - code_block::{create_function_object, create_generator_function_object, Readable}, + code_block::{create_generator_function_object, Readable}, }, Context, JsBigInt, JsResult, JsString, JsValue, }; @@ -25,6 +25,7 @@ pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; pub(crate) use { call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry}, + code_block::create_function_object, opcode::BindingOpcode, }; @@ -553,7 +554,7 @@ impl Context { .into(); let exists = self.global_bindings_mut().contains_key(&key); - if !exists && (self.strict() || self.vm.frame().code.strict) { + if !exists && self.vm.frame().code.strict { return self.throw_reference_error(format!( "assignment to undeclared variable {key}" )); @@ -566,7 +567,7 @@ impl Context { self, )?; - if !success && (self.strict() || self.vm.frame().code.strict) { + if !success && self.vm.frame().code.strict { return self.throw_type_error(format!( "cannot set non-writable property: {key}", )); @@ -674,12 +675,7 @@ impl Context { let name = self.vm.frame().code.names[index as usize]; let name: PropertyKey = self.interner().resolve_expect(name).into(); - object.set( - name, - value, - self.strict() || self.vm.frame().code.strict, - self, - )?; + object.set(name, value, self.vm.frame().code.strict, self)?; } Opcode::DefineOwnPropertyByName => { let index = self.vm.read::(); @@ -736,12 +732,7 @@ impl Context { }; let key = key.to_property_key(self)?; - object.set( - key, - value, - self.strict() || self.vm.frame().code.strict, - self, - )?; + object.set(key, value, self.vm.frame().code.strict, self)?; } Opcode::DefineOwnPropertyByValue => { let value = self.vm.pop(); @@ -1068,7 +1059,7 @@ impl Context { let key = self.interner().resolve_expect(key).into(); let object = self.vm.pop(); let result = object.to_object(self)?.__delete__(&key, self)?; - if !result && self.strict() || self.vm.frame().code.strict { + if !result && self.vm.frame().code.strict { return Err(self.construct_type_error("Cannot delete property")); } self.vm.push(result); @@ -1079,7 +1070,7 @@ impl Context { let result = object .to_object(self)? .__delete__(&key.to_property_key(self)?, self)?; - if !result && self.strict() || self.vm.frame().code.strict { + if !result && self.vm.frame().code.strict { return Err(self.construct_type_error("Cannot delete property")); } self.vm.push(result); @@ -1256,7 +1247,7 @@ impl Context { // 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; + let strict = self.vm.frame().code.strict; if eval { if let Some(x) = arguments.get(0) { @@ -1304,7 +1295,7 @@ impl Context { // 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; + let strict = self.vm.frame().code.strict; if eval { if let Some(x) = arguments.get(0) { diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 2d351b2b5a..ff5748972a 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -111,7 +111,7 @@ impl Test { /// Runs the test. pub(crate) fn run(&self, harness: &Harness, verbose: u8) -> Vec { let mut results = Vec::new(); - if self.flags.contains(TestFlags::STRICT) { + if self.flags.contains(TestFlags::STRICT) && !self.flags.contains(TestFlags::RAW) { results.push(self.run_once(harness, true, verbose)); } @@ -132,6 +132,12 @@ impl Test { ); } + let test_content = if strict { + format!("\"use strict\";\n{}", self.content) + } else { + self.content.to_string() + }; + let (result, result_text) = if !IGNORED.contains_any_flag(self.flags) && !IGNORED.contains_test(&self.name) && !IGNORED.contains_any_feature(&self.features) @@ -162,10 +168,9 @@ impl Test { // TODO: implement async and add `harness/doneprintHandle.js` to the includes. let mut context = Context::default(); - match self.set_up_env(harness, strict, &mut context) { + match self.set_up_env(harness, &mut context) { Ok(_) => { - context.set_strict_mode(strict); - let res = context.eval(&self.content.as_ref()); + let res = context.eval(&test_content); let passed = res.is_ok(); let text = match res { @@ -190,8 +195,7 @@ impl Test { ); let mut context = Context::default(); - context.set_strict_mode(strict); - match context.parse(self.content.as_bytes()) { + match context.parse(&test_content) { Ok(statement_list) => match context.compile(&statement_list) { Ok(_) => (false, "StatementList compilation should fail".to_owned()), Err(e) => (true, format!("Uncaught {e:?}")), @@ -208,27 +212,22 @@ impl Test { ref error_type, } => { let mut context = Context::default(); - if let Err(e) = - Parser::new(self.content.as_bytes(), strict).parse_all(&mut context) - { + if let Err(e) = Parser::new(test_content.as_bytes()).parse_all(&mut context) { (false, format!("Uncaught {e}")) } else { - match self.set_up_env(harness, strict, &mut context) { - Ok(_) => { - context.set_strict_mode(strict); - match context.eval(&self.content.as_ref()) { - Ok(res) => (false, res.display().to_string()), - Err(e) => { - let passed = e - .display() - .internals(true) - .to_string() - .contains(error_type.as_ref()); - - (passed, format!("Uncaught {}", e.display())) - } + match self.set_up_env(harness, &mut context) { + Ok(_) => match context.eval(&test_content) { + Ok(res) => (false, res.display().to_string()), + Err(e) => { + let passed = e + .display() + .internals(true) + .to_string() + .contains(error_type.as_ref()); + + (passed, format!("Uncaught {}", e.display())) } - } + }, Err(e) => (false, e), } } @@ -307,22 +306,15 @@ impl Test { } /// Sets the environment up to run the test. - fn set_up_env( - &self, - harness: &Harness, - strict: bool, - context: &mut Context, - ) -> Result<(), String> { + fn set_up_env(&self, harness: &Harness, context: &mut Context) -> Result<(), String> { // Register the print() function. context.register_global_function("print", 1, test262_print); // add the $262 object. let _js262 = js262::init(context); - if strict { - context - .eval(r#""use strict";"#) - .map_err(|e| format!("could not set strict mode:\n{}", e.display()))?; + if self.flags.contains(TestFlags::RAW) { + return Ok(()); } context