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