Browse Source

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](2e7cdfbe18/INTERPRETING.md (strict-mode))
- Make 262 tester compliant with the `raw` flag in [test262/INTERPRETING.md](2e7cdfbe18/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
pull/2084/head
raskad 3 years ago
parent
commit
45dd2d416c
  1. 2
      boa_cli/src/main.rs
  2. 7
      boa_engine/src/builtins/eval/mod.rs
  3. 32
      boa_engine/src/context/mod.rs
  4. 7
      boa_engine/src/syntax/ast/node/await_expr/mod.rs
  5. 108
      boa_engine/src/syntax/ast/node/declaration/mod.rs
  6. 23
      boa_engine/src/syntax/ast/node/identifier/mod.rs
  7. 435
      boa_engine/src/syntax/ast/node/mod.rs
  8. 17
      boa_engine/src/syntax/ast/node/operator/assign/mod.rs
  9. 32
      boa_engine/src/syntax/lexer/identifier.rs
  10. 2
      boa_engine/src/syntax/parser/error.rs
  11. 18
      boa_engine/src/syntax/parser/expression/assignment/mod.rs
  12. 2
      boa_engine/src/syntax/parser/expression/identifiers.rs
  13. 1
      boa_engine/src/syntax/parser/expression/mod.rs
  14. 73
      boa_engine/src/syntax/parser/expression/primary/array_initializer/mod.rs
  15. 3
      boa_engine/src/syntax/parser/expression/primary/mod.rs
  16. 54
      boa_engine/src/syntax/parser/expression/update.rs
  17. 1
      boa_engine/src/syntax/parser/function/mod.rs
  18. 2
      boa_engine/src/syntax/parser/function/tests.rs
  19. 20
      boa_engine/src/syntax/parser/mod.rs
  20. 1
      boa_engine/src/syntax/parser/statement/block/mod.rs
  21. 32
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  22. 2
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  23. 30
      boa_engine/src/syntax/parser/statement/mod.rs
  24. 2
      boa_engine/src/syntax/parser/statement/switch/mod.rs
  25. 6
      boa_engine/src/syntax/parser/tests.rs
  26. 18
      boa_engine/src/tests.rs
  27. 19
      boa_engine/src/vm/code_block.rs
  28. 29
      boa_engine/src/vm/mod.rs
  29. 42
      boa_tester/src/exec/mod.rs

2
boa_cli/src/main.rs

@ -141,7 +141,7 @@ where
use boa_engine::syntax::parser::Parser; use boa_engine::syntax::parser::Parser;
let src_bytes = src.as_ref(); let src_bytes = src.as_ref();
Parser::new(src_bytes, false) Parser::new(src_bytes)
.parse_all(context) .parse_all(context)
.map_err(|e| format!("ParsingError: {e}")) .map_err(|e| format!("ParsingError: {e}"))
} }

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

@ -83,7 +83,12 @@ impl Eval {
// Parse the script body (11.a - 11.d) // Parse the script body (11.a - 11.d)
// TODO: Implement errors for 11.e - 11.h // 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, Ok(body) => body,
Err(e) => return context.throw_syntax_error(e), Err(e) => return context.throw_syntax_error(e),
}; };

32
boa_engine/src/context/mod.rs

@ -82,9 +82,6 @@ pub struct Context {
/// Intrinsic objects /// Intrinsic objects
intrinsics: Intrinsics, intrinsics: Intrinsics,
/// Whether or not global strict mode is active.
strict: bool,
pub(crate) vm: Vm, pub(crate) vm: Vm,
} }
@ -96,7 +93,6 @@ impl Default for Context {
#[cfg(feature = "console")] #[cfg(feature = "console")]
console: Console::default(), console: Console::default(),
intrinsics: Intrinsics::default(), intrinsics: Intrinsics::default(),
strict: false,
vm: Vm { vm: Vm {
frame: None, frame: None,
stack: Vec::with_capacity(1024), stack: Vec::with_capacity(1024),
@ -149,18 +145,6 @@ impl Context {
&mut self.console &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 /// Sets up the default global objects within Global
#[inline] #[inline]
fn create_intrinsics(&mut self) { fn create_intrinsics(&mut self) {
@ -178,11 +162,23 @@ impl Context {
) )
} }
/// Parse the given source text.
pub fn parse<S>(&mut self, src: S) -> Result<StatementList, ParseError> pub fn parse<S>(&mut self, src: S) -> Result<StatementList, ParseError>
where where
S: AsRef<[u8]>, 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<S>(&mut self, src: S) -> Result<StatementList, ParseError>
where
S: AsRef<[u8]>,
{
let mut parser = Parser::new(src.as_ref());
parser.set_strict();
parser.parse_all(self)
} }
/// <https://tc39.es/ecma262/#sec-call> /// <https://tc39.es/ecma262/#sec-call>
@ -641,7 +637,7 @@ impl Context {
{ {
let main_timer = Profiler::global().start_event("Evaluation", "Main"); 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) .parse_all(self)
.map_err(|e| e.to_string()); .map_err(|e| e.to_string());

7
boa_engine/src/syntax/ast/node/await_expr/mod.rs

@ -25,6 +25,13 @@ pub struct AwaitExpr {
expr: Box<Node>, expr: Box<Node>,
} }
impl AwaitExpr {
/// Return the expression that should be awaited.
pub(crate) fn expr(&self) -> &Node {
&self.expr
}
}
impl<T> From<T> for AwaitExpr impl<T> From<T> for AwaitExpr
where where
T: Into<Box<Node>>, T: Into<Box<Node>>,

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

@ -276,6 +276,114 @@ impl DeclarationPattern {
DeclarationPattern::Array(pattern) => pattern.init(), 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. /// `DeclarationPatternObject` represents an object binding pattern.

23
boa_engine/src/syntax/ast/node/identifier/mod.rs

@ -1,6 +1,9 @@
//! Local identifier node. //! 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_gc::{unsafe_empty_trace, Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString}; use boa_interner::{Interner, Sym, ToInternedString};
@ -39,6 +42,24 @@ impl Identifier {
pub fn sym(self) -> Sym { pub fn sym(self) -> Sym {
self.ident 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 { impl ToInternedString for Identifier {

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

@ -23,7 +23,6 @@ pub mod throw;
pub mod try_node; pub mod try_node;
pub mod r#yield; pub mod r#yield;
use self::field::get_private_field::GetPrivateField;
pub use self::{ pub use self::{
array::ArrayDecl, array::ArrayDecl,
await_expr::AwaitExpr, await_expr::AwaitExpr,
@ -36,7 +35,7 @@ pub use self::{
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList,
DeclarationPattern, FunctionDecl, FunctionExpr, DeclarationPattern, FunctionDecl, FunctionExpr,
}, },
field::{GetConstField, GetField}, field::{get_private_field::GetPrivateField, GetConstField, GetField},
identifier::Identifier, identifier::Identifier,
iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
new::New, new::New,
@ -52,6 +51,11 @@ pub use self::{
throw::Throw, throw::Throw,
try_node::{Catch, Finally, Try}, try_node::{Catch, Finally, Try},
}; };
use self::{
declaration::class_decl::ClassElement,
iteration::IterableLoopInitializer,
object::{MethodDefinition, PropertyDefinition},
};
pub(crate) use self::parameters::FormalParameterListFlags; pub(crate) use self::parameters::FormalParameterListFlags;
@ -397,7 +401,7 @@ impl Node {
for_loop.body().var_declared_names(vars); for_loop.body().var_declared_names(vars);
} }
Node::ForInLoop(for_in_loop) => { 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 { match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym()); vars.insert(ident.sym());
@ -412,7 +416,7 @@ impl Node {
for_in_loop.body().var_declared_names(vars); for_in_loop.body().var_declared_names(vars);
} }
Node::ForOfLoop(for_of_loop) => { 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 { match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
vars.insert(ident.sym()); 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 { impl ToInternedString for Node {
@ -509,7 +934,7 @@ fn test_formatting(source: &'static str) {
.collect::<Vec<&'static str>>() .collect::<Vec<&'static str>>()
.join("\n"); .join("\n");
let mut context = Context::default(); let mut context = Context::default();
let result = Parser::new(scenario.as_bytes(), false) let result = Parser::new(scenario.as_bytes())
.parse_all(&mut context) .parse_all(&mut context)
.expect("parsing failed") .expect("parsing failed")
.to_interned_string(context.interner()); .to_interned_string(context.interner());

17
boa_engine/src/syntax/ast/node/operator/assign/mod.rs

@ -1,4 +1,5 @@
use crate::syntax::ast::node::{ use crate::syntax::{
ast::node::{
declaration::{ declaration::{
BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPatternArray, BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPatternArray,
DeclarationPatternObject, DeclarationPatternObject,
@ -6,6 +7,8 @@ use crate::syntax::ast::node::{
field::get_private_field::GetPrivateField, field::get_private_field::GetPrivateField,
object::{PropertyDefinition, PropertyName}, object::{PropertyDefinition, PropertyName},
ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object, ArrayDecl, DeclarationPattern, GetConstField, GetField, Identifier, Node, Object,
},
parser::RESERVED_IDENTIFIERS_STRICT,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString}; use boa_interner::{Interner, Sym, ToInternedString};
@ -152,6 +155,10 @@ pub(crate) fn object_decl_to_declaration_pattern(
return None return None
} }
PropertyDefinition::IdentifierReference(ident) => { PropertyDefinition::IdentifierReference(ident) => {
if strict && RESERVED_IDENTIFIERS_STRICT.contains(ident) {
return None;
}
excluded_keys.push(*ident); excluded_keys.push(*ident);
bindings.push(BindingPatternTypeObject::SingleName { bindings.push(BindingPatternTypeObject::SingleName {
ident: *ident, ident: *ident,
@ -164,6 +171,10 @@ pub(crate) fn object_decl_to_declaration_pattern(
if strict && *name == Sym::EVAL { if strict && *name == Sym::EVAL {
return None; return None;
} }
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) {
return None;
}
excluded_keys.push(*name); excluded_keys.push(*name);
bindings.push(BindingPatternTypeObject::SingleName { bindings.push(BindingPatternTypeObject::SingleName {
ident: *name, ident: *name,
@ -217,6 +228,10 @@ pub(crate) fn array_decl_to_declaration_pattern(
for (i, node) in array.as_ref().iter().enumerate() { for (i, node) in array.as_ref().iter().enumerate() {
match node { match node {
Node::Identifier(ident) => { Node::Identifier(ident) => {
if strict && ident.sym() == Sym::ARGUMENTS {
return None;
}
bindings.push(BindingPatternTypeArray::SingleName { bindings.push(BindingPatternTypeArray::SingleName {
ident: ident.sym(), ident: ident.sym(),
default_init: None, default_init: None,

32
boa_engine/src/syntax/lexer/identifier.rs

@ -8,19 +8,7 @@ use crate::syntax::{
use boa_interner::Interner; use boa_interner::Interner;
use boa_profiler::Profiler; use boa_profiler::Profiler;
use boa_unicode::UnicodeProperties; use boa_unicode::UnicodeProperties;
use std::{io::Read, str}; use std::io::Read;
const STRICT_FORBIDDEN_IDENTIFIERS: [&str; 9] = [
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
"yield",
];
/// Identifier lexing. /// Identifier lexing.
/// ///
@ -90,13 +78,6 @@ impl<R> Tokenizer<R> for Identifier {
Self::take_identifier_name(cursor, start_pos, self.init)?; Self::take_identifier_name(cursor, start_pos, self.init)?;
let token_kind = if let Ok(keyword) = identifier_name.parse() { 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 { match keyword {
Keyword::True => TokenKind::BooleanLiteral(true), Keyword::True => TokenKind::BooleanLiteral(true),
Keyword::False => TokenKind::BooleanLiteral(false), Keyword::False => TokenKind::BooleanLiteral(false),
@ -104,17 +85,6 @@ impl<R> Tokenizer<R> for Identifier {
_ => TokenKind::Keyword((keyword, contains_escaped_chars)), _ => TokenKind::Keyword((keyword, contains_escaped_chars)),
} }
} else { } 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)) TokenKind::identifier(interner.get_or_intern(identifier_name))
}; };

2
boa_engine/src/syntax/parser/error.rs

@ -98,7 +98,7 @@ impl ParseError {
} }
/// Creates a "general" parsing error. /// 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 } Self::General { message, position }
} }

18
boa_engine/src/syntax/parser/expression/assignment/mod.rs

@ -214,17 +214,7 @@ where
TokenKind::Punctuator(Punctuator::Assign) => { TokenKind::Punctuator(Punctuator::Assign) => {
if cursor.strict_mode() { if cursor.strict_mode() {
if let Node::Identifier(ident) = lhs { if let Node::Identifier(ident) = lhs {
if ident.sym() == Sym::ARGUMENTS { ident.check_strict_arguments_or_eval(position)?;
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,
)));
}
} }
} }
@ -243,6 +233,12 @@ where
} }
} }
TokenKind::Punctuator(p) if p.as_binop().is_some() && p != &Punctuator::Comma => { 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"); cursor.next(interner)?.expect("token vanished");
if is_assignable(&lhs) { if is_assignable(&lhs) {
let binop = p.as_binop().expect("binop disappeared"); let binop = p.as_binop().expect("binop disappeared");

2
boa_engine/src/syntax/parser/expression/identifiers.rs

@ -15,7 +15,7 @@ use boa_interner::{Interner, Sym};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use std::io::Read; use std::io::Read;
const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [ pub(in crate::syntax) const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [
Sym::IMPLEMENTS, Sym::IMPLEMENTS,
Sym::INTERFACE, Sym::INTERFACE,
Sym::LET, Sym::LET,

1
boa_engine/src/syntax/parser/expression/mod.rs

@ -36,6 +36,7 @@ use boa_profiler::Profiler;
use std::io::Read; use std::io::Read;
pub(super) use self::{assignment::AssignmentExpression, primary::Initializer}; pub(super) use self::{assignment::AssignmentExpression, primary::Initializer};
pub(in crate::syntax) use identifiers::RESERVED_IDENTIFIERS_STRICT;
pub(in crate::syntax::parser) use { pub(in crate::syntax::parser) use {
identifiers::{BindingIdentifier, LabelIdentifier}, identifiers::{BindingIdentifier, LabelIdentifier},
left_hand_side::LeftHandSideExpression, left_hand_side::LeftHandSideExpression,

73
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 _timer = Profiler::global().start_event("ArrayLiteral", "Parsing");
let mut elements = Vec::new(); let mut elements = Vec::new();
let mut has_trailing_comma_spread = false; let mut has_trailing_comma_spread = false;
loop { let mut next_comma = false;
// TODO: Support all features. let mut last_spread = false;
while cursor.next_if(Punctuator::Comma, interner)?.is_some() {
elements.push(Node::Empty);
}
if cursor loop {
.next_if(Punctuator::CloseBracket, interner)? let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
.is_some() match token.kind() {
{ TokenKind::Punctuator(Punctuator::CloseBracket) => {
cursor.next(interner).expect("token disappeared");
break; break;
} }
TokenKind::Punctuator(Punctuator::Comma) if next_comma => {
cursor.next(interner).expect("token disappeared");
let _next = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd); // Check that there are more tokens to read. 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;
}
}
if cursor.next_if(Punctuator::Spread, interner)?.is_some() { next_comma = false;
}
TokenKind::Punctuator(Punctuator::Comma) => {
cursor.next(interner).expect("token disappeared");
elements.push(Node::Empty);
}
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 = let node =
AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) AssignmentExpression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .parse(cursor, interner)?;
elements.push(Spread::new(node).into()); elements.push(Spread::new(node).into());
// If the last element in the array is followed by a comma, push an elision. next_comma = true;
if cursor.next_if(Punctuator::Comma, interner)?.is_some() { last_spread = true;
if let Some(t) = cursor.peek(0, interner)? {
if *t.kind() == TokenKind::Punctuator(Punctuator::CloseBracket) {
has_trailing_comma_spread = true;
}
} }
_ if next_comma => {
return Err(ParseError::unexpected(
token.to_string(interner),
token.span(),
"expected comma or end of array",
));
} }
} else { _ => {
elements.push( let node =
AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) AssignmentExpression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?, .parse(cursor, interner)?;
); elements.push(node);
cursor.next_if(Punctuator::Comma, interner)?; next_comma = true;
last_spread = false;
}
}
}
if last_spread {
if let Some(Node::Empty) = elements.last() {
has_trailing_comma_spread = true;
} }
} }

3
boa_engine/src/syntax/parser/expression/primary/mod.rs

@ -113,7 +113,8 @@ where
} }
TokenKind::Keyword((Keyword::Class, _)) => { TokenKind::Keyword((Keyword::Class, _)) => {
cursor.next(interner).expect("token disappeared"); 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)) => { TokenKind::Keyword((Keyword::Async, false)) => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");

54
boa_engine/src/syntax/parser/expression/update.rs

@ -57,56 +57,46 @@ where
let _timer = Profiler::global().start_event("UpdateExpression", "Parsing"); let _timer = Profiler::global().start_event("UpdateExpression", "Parsing");
let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let position = tok.span().start();
match tok.kind() { match tok.kind() {
TokenKind::Punctuator(Punctuator::Inc) => { TokenKind::Punctuator(Punctuator::Inc) => {
cursor cursor
.next(interner)? .next(interner)?
.expect("Punctuator::Inc token disappeared"); .expect("Punctuator::Inc token disappeared");
return Ok(node::UnaryOp::new(
UnaryOp::IncrementPre, let target = UnaryExpression::new(self.name, self.allow_yield, self.allow_await)
UnaryExpression::new(self.name, self.allow_yield, self.allow_await) .parse(cursor, interner)?;
.parse(cursor, interner)?,
) if cursor.strict_mode() {
.into()); 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) => { TokenKind::Punctuator(Punctuator::Dec) => {
cursor cursor
.next(interner)? .next(interner)?
.expect("Punctuator::Dec token disappeared"); .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 position = cursor let target = UnaryExpression::new(self.name, self.allow_yield, self.allow_await)
.peek(0, interner)?
.ok_or(ParseError::AbruptEnd)?
.span()
.start();
let lhs = LeftHandSideExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?; .parse(cursor, interner)?;
if cursor.strict_mode() { if cursor.strict_mode() {
if let Node::Identifier(ident) = lhs { if let Node::Identifier(ident) = target {
if ident.sym() == Sym::ARGUMENTS { ident.check_strict_arguments_or_eval(position)?;
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,
)));
} }
return Ok(node::UnaryOp::new(UnaryOp::DecrementPre, target).into());
} }
_ => {}
} }
let lhs = LeftHandSideExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let strict = cursor.strict_mode(); let strict = cursor.strict_mode();
if let Some(tok) = cursor.peek(0, interner)? { if let Some(tok) = cursor.peek(0, interner)? {
let token_start = tok.span().start(); let token_start = tok.span().start();

1
boa_engine/src/syntax/parser/function/mod.rs

@ -541,7 +541,6 @@ where
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
true, true,
true,
&FUNCTION_BREAK_TOKENS, &FUNCTION_BREAK_TOKENS,
) )
.parse(cursor, interner); .parse(cursor, interner);

2
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 js = "'use strict'; function foo(a, a) {}";
let mut context = Context::default(); 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); dbg!(&res);
assert!(res.is_err()); assert!(res.is_err());
} }

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

@ -24,6 +24,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use std::io::Read; use std::io::Read;
pub use self::error::{ParseError, ParseResult}; pub use self::error::{ParseError, ParseResult};
pub(in crate::syntax) use expression::RESERVED_IDENTIFIERS_STRICT;
/// Trait implemented by parsers. /// Trait implemented by parsers.
/// ///
@ -103,14 +104,21 @@ pub struct Parser<R> {
impl<R> Parser<R> { impl<R> Parser<R> {
/// Create a new `Parser` with a reader as the input to parse. /// 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 where
R: Read, R: Read,
{ {
let mut cursor = Cursor::new(reader); Self {
cursor.set_strict_mode(strict_mode); 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. /// Parse the full input as a [ECMAScript Script][spec] into the boa AST representation.
@ -204,9 +212,9 @@ where
cursor: &mut Cursor<R>, cursor: &mut Cursor<R>,
interner: &mut Interner, interner: &mut Interner,
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
let mut strict = cursor.strict_mode();
match cursor.peek(0, interner)? { match cursor.peek(0, interner)? {
Some(tok) => { Some(tok) => {
let mut strict = false;
match tok.kind() { match tok.kind() {
// Set the strict mode // Set the strict mode
TokenKind::StringLiteral(string) TokenKind::StringLiteral(string)
@ -246,6 +254,6 @@ where
cursor: &mut Cursor<R>, cursor: &mut Cursor<R>,
interner: &mut Interner, interner: &mut Interner,
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
self::statement::StatementList::new(false, false, false, false, &[]).parse(cursor, interner) self::statement::StatementList::new(false, false, false, &[]).parse(cursor, interner)
} }
} }

1
boa_engine/src/syntax/parser/statement/block/mod.rs

@ -91,7 +91,6 @@ where
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
self.allow_return, self.allow_return,
true,
&BLOCK_BREAK_TOKENS, &BLOCK_BREAK_TOKENS,
) )
.parse(cursor, interner) .parse(cursor, interner)

32
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 token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let position = token.span().start();
let element = match token.kind() { let element = match token.kind() {
TokenKind::Identifier(Sym::CONSTRUCTOR) if !r#static => { TokenKind::Identifier(Sym::CONSTRUCTOR) if !r#static => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
@ -561,7 +562,7 @@ where
.span() .span()
.start(); .start();
let statement_list = let statement_list =
StatementList::new(false, true, false, true, &FUNCTION_BREAK_TOKENS) StatementList::new(false, true, false, &FUNCTION_BREAK_TOKENS)
.parse(cursor, interner)?; .parse(cursor, interner)?;
let lexically_declared_names = statement_list.lexically_declared_names(); 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))) Ok((None, Some(element)))
} }
} }

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

@ -111,7 +111,7 @@ where
} }
} }
TokenKind::Keyword((Keyword::Class, false)) => { TokenKind::Keyword((Keyword::Class, false)) => {
ClassDeclaration::new(false, false, self.is_default) ClassDeclaration::new(self.allow_yield, self.allow_await, false)
.parse(cursor, interner) .parse(cursor, interner)
.map(Node::from) .map(Node::from)
} }

30
boa_engine/src/syntax/parser/statement/mod.rs

@ -240,7 +240,6 @@ pub(super) struct StatementList {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
allow_return: AllowReturn, allow_return: AllowReturn,
in_block: bool,
break_nodes: &'static [TokenKind], break_nodes: &'static [TokenKind],
} }
@ -250,7 +249,6 @@ impl StatementList {
allow_yield: Y, allow_yield: Y,
allow_await: A, allow_await: A,
allow_return: R, allow_return: R,
in_block: bool,
break_nodes: &'static [TokenKind], break_nodes: &'static [TokenKind],
) -> Self ) -> Self
where where
@ -262,7 +260,6 @@ impl StatementList {
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
allow_return: allow_return.into(), allow_return: allow_return.into(),
in_block,
break_nodes, break_nodes,
} }
} }
@ -299,12 +296,8 @@ where
_ => {} _ => {}
} }
let item = StatementListItem::new( let item =
self.allow_yield, StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return)
self.allow_await,
self.allow_return,
self.in_block,
)
.parse(cursor, interner)?; .parse(cursor, interner)?;
items.push(item); items.push(item);
@ -333,12 +326,11 @@ struct StatementListItem {
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
allow_return: AllowReturn, allow_return: AllowReturn,
in_block: bool,
} }
impl StatementListItem { impl StatementListItem {
/// Creates a new `StatementListItem` parser. /// Creates a new `StatementListItem` parser.
fn new<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R, in_block: bool) -> Self fn new<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self
where where
Y: Into<AllowYield>, Y: Into<AllowYield>,
A: Into<AllowAwait>, A: Into<AllowAwait>,
@ -348,7 +340,6 @@ impl StatementListItem {
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
allow_return: allow_return.into(), allow_return: allow_return.into(),
in_block,
} }
} }
} }
@ -365,20 +356,13 @@ where
interner: &mut Interner, interner: &mut Interner,
) -> Result<Self::Output, ParseError> { ) -> Result<Self::Output, ParseError> {
let _timer = Profiler::global().start_event("StatementListItem", "Parsing"); let _timer = Profiler::global().start_event("StatementListItem", "Parsing");
let strict_mode = cursor.strict_mode();
let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match *tok.kind() { match *tok.kind() {
TokenKind::Keyword((Keyword::Function | Keyword::Async | Keyword::Class, _)) => { TokenKind::Keyword((
if strict_mode && self.in_block { Keyword::Function | Keyword::Async | Keyword::Class | Keyword::Const | Keyword::Let,
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, _)) => {
Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner) Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner)
} }
_ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return)

2
boa_engine/src/syntax/parser/statement/switch/mod.rs

@ -150,7 +150,6 @@ where
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
self.allow_return, self.allow_return,
false,
&CASE_BREAK_TOKENS, &CASE_BREAK_TOKENS,
) )
.parse(cursor, interner)?; .parse(cursor, interner)?;
@ -173,7 +172,6 @@ where
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
self.allow_return, self.allow_return,
false,
&CASE_BREAK_TOKENS, &CASE_BREAK_TOKENS,
) )
.parse(cursor, interner)?; .parse(cursor, interner)?;

6
boa_engine/src/syntax/parser/tests.rs

@ -25,7 +25,7 @@ where
{ {
let mut context = Context::new(interner); let mut context = Context::new(interner);
assert_eq!( assert_eq!(
Parser::new(js.as_bytes(), false) Parser::new(js.as_bytes())
.parse_all(&mut context) .parse_all(&mut context)
.expect("failed to parse"), .expect("failed to parse"),
StatementList::from(expr) StatementList::from(expr)
@ -36,9 +36,7 @@ where
#[track_caller] #[track_caller]
pub(super) fn check_invalid(js: &str) { pub(super) fn check_invalid(js: &str) {
let mut context = Context::default(); let mut context = Context::default();
assert!(Parser::new(js.as_bytes(), false) assert!(Parser::new(js.as_bytes()).parse_all(&mut context).is_err());
.parse_all(&mut context)
.is_err());
} }
/// Should be parsed as `new Class().method()` instead of `new (Class().method())` /// Should be parsed as `new Class().method()` instead of `new (Class().method())`

18
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] #[test]
fn test_strict_mode_dup_func_parameters() { fn test_strict_mode_dup_func_parameters() {
// Checks that a function cannot contain duplicate parameter // Checks that a function cannot contain duplicate parameter

19
boa_engine/src/vm/code_block.rs

@ -634,10 +634,14 @@ impl JsObject {
} else { } else {
context.global_object().clone().into() 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() context.global_object().clone().into()
} else { } else {
this.clone() this.to_object(context)
.expect("conversion cannot fail")
.into()
}; };
if code.params.has_expressions() { if code.params.has_expressions() {
@ -655,8 +659,7 @@ impl JsObject {
} }
if let Some(binding) = code.arguments_binding { if let Some(binding) = code.arguments_binding {
let arguments_obj = let arguments_obj = if code.strict || !code.params.is_simple() {
if context.strict() || code.strict || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context) Arguments::create_unmapped_arguments_object(args, context)
} else { } else {
let env = context.realm.environments.current(); let env = context.realm.environments.current();
@ -742,7 +745,7 @@ impl JsObject {
} else { } else {
context.global_object().clone().into() 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() context.global_object().clone().into()
} else { } else {
this.clone() this.clone()
@ -763,8 +766,7 @@ impl JsObject {
} }
if let Some(binding) = code.arguments_binding { if let Some(binding) = code.arguments_binding {
let arguments_obj = let arguments_obj = if code.strict || !code.params.is_simple() {
if context.strict() || code.strict || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context) Arguments::create_unmapped_arguments_object(args, context)
} else { } else {
let env = context.realm.environments.current(); let env = context.realm.environments.current();
@ -957,8 +959,7 @@ impl JsObject {
} }
if let Some(binding) = code.arguments_binding { if let Some(binding) = code.arguments_binding {
let arguments_obj = let arguments_obj = if code.strict || !code.params.is_simple() {
if context.strict() || code.strict || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context) Arguments::create_unmapped_arguments_object(args, context)
} else { } else {
let env = context.realm.environments.current(); let env = context.realm.environments.current();

29
boa_engine/src/vm/mod.rs

@ -9,7 +9,7 @@ use crate::{
value::Numeric, value::Numeric,
vm::{ vm::{
call_frame::CatchAddresses, 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, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
@ -25,6 +25,7 @@ pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
pub(crate) use { pub(crate) use {
call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry}, call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry},
code_block::create_function_object,
opcode::BindingOpcode, opcode::BindingOpcode,
}; };
@ -553,7 +554,7 @@ impl Context {
.into(); .into();
let exists = self.global_bindings_mut().contains_key(&key); 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!( return self.throw_reference_error(format!(
"assignment to undeclared variable {key}" "assignment to undeclared variable {key}"
)); ));
@ -566,7 +567,7 @@ impl Context {
self, self,
)?; )?;
if !success && (self.strict() || self.vm.frame().code.strict) { if !success && self.vm.frame().code.strict {
return self.throw_type_error(format!( return self.throw_type_error(format!(
"cannot set non-writable property: {key}", "cannot set non-writable property: {key}",
)); ));
@ -674,12 +675,7 @@ impl Context {
let name = self.vm.frame().code.names[index as usize]; let name = self.vm.frame().code.names[index as usize];
let name: PropertyKey = self.interner().resolve_expect(name).into(); let name: PropertyKey = self.interner().resolve_expect(name).into();
object.set( object.set(name, value, self.vm.frame().code.strict, self)?;
name,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
} }
Opcode::DefineOwnPropertyByName => { Opcode::DefineOwnPropertyByName => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
@ -736,12 +732,7 @@ impl Context {
}; };
let key = key.to_property_key(self)?; let key = key.to_property_key(self)?;
object.set( object.set(key, value, self.vm.frame().code.strict, self)?;
key,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
} }
Opcode::DefineOwnPropertyByValue => { Opcode::DefineOwnPropertyByValue => {
let value = self.vm.pop(); let value = self.vm.pop();
@ -1068,7 +1059,7 @@ impl Context {
let key = self.interner().resolve_expect(key).into(); let key = self.interner().resolve_expect(key).into();
let object = self.vm.pop(); let object = self.vm.pop();
let result = object.to_object(self)?.__delete__(&key, self)?; 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")); return Err(self.construct_type_error("Cannot delete property"));
} }
self.vm.push(result); self.vm.push(result);
@ -1079,7 +1070,7 @@ impl Context {
let result = object let result = object
.to_object(self)? .to_object(self)?
.__delete__(&key.to_property_key(self)?, 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")); return Err(self.construct_type_error("Cannot delete property"));
} }
self.vm.push(result); 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. // 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 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 eval {
if let Some(x) = arguments.get(0) { 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. // 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 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 eval {
if let Some(x) = arguments.get(0) { if let Some(x) = arguments.get(0) {

42
boa_tester/src/exec/mod.rs

@ -111,7 +111,7 @@ impl Test {
/// Runs the test. /// Runs the test.
pub(crate) fn run(&self, harness: &Harness, verbose: u8) -> Vec<TestResult> { pub(crate) fn run(&self, harness: &Harness, verbose: u8) -> Vec<TestResult> {
let mut results = Vec::new(); 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)); 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) let (result, result_text) = if !IGNORED.contains_any_flag(self.flags)
&& !IGNORED.contains_test(&self.name) && !IGNORED.contains_test(&self.name)
&& !IGNORED.contains_any_feature(&self.features) && !IGNORED.contains_any_feature(&self.features)
@ -162,10 +168,9 @@ impl Test {
// TODO: implement async and add `harness/doneprintHandle.js` to the includes. // TODO: implement async and add `harness/doneprintHandle.js` to the includes.
let mut context = Context::default(); let mut context = Context::default();
match self.set_up_env(harness, strict, &mut context) { match self.set_up_env(harness, &mut context) {
Ok(_) => { Ok(_) => {
context.set_strict_mode(strict); let res = context.eval(&test_content);
let res = context.eval(&self.content.as_ref());
let passed = res.is_ok(); let passed = res.is_ok();
let text = match res { let text = match res {
@ -190,8 +195,7 @@ impl Test {
); );
let mut context = Context::default(); let mut context = Context::default();
context.set_strict_mode(strict); match context.parse(&test_content) {
match context.parse(self.content.as_bytes()) {
Ok(statement_list) => match context.compile(&statement_list) { Ok(statement_list) => match context.compile(&statement_list) {
Ok(_) => (false, "StatementList compilation should fail".to_owned()), Ok(_) => (false, "StatementList compilation should fail".to_owned()),
Err(e) => (true, format!("Uncaught {e:?}")), Err(e) => (true, format!("Uncaught {e:?}")),
@ -208,15 +212,11 @@ impl Test {
ref error_type, ref error_type,
} => { } => {
let mut context = Context::default(); let mut context = Context::default();
if let Err(e) = if let Err(e) = Parser::new(test_content.as_bytes()).parse_all(&mut context) {
Parser::new(self.content.as_bytes(), strict).parse_all(&mut context)
{
(false, format!("Uncaught {e}")) (false, format!("Uncaught {e}"))
} else { } else {
match self.set_up_env(harness, strict, &mut context) { match self.set_up_env(harness, &mut context) {
Ok(_) => { Ok(_) => match context.eval(&test_content) {
context.set_strict_mode(strict);
match context.eval(&self.content.as_ref()) {
Ok(res) => (false, res.display().to_string()), Ok(res) => (false, res.display().to_string()),
Err(e) => { Err(e) => {
let passed = e let passed = e
@ -227,8 +227,7 @@ impl Test {
(passed, format!("Uncaught {}", e.display())) (passed, format!("Uncaught {}", e.display()))
} }
} },
}
Err(e) => (false, e), Err(e) => (false, e),
} }
} }
@ -307,22 +306,15 @@ impl Test {
} }
/// Sets the environment up to run the test. /// Sets the environment up to run the test.
fn set_up_env( fn set_up_env(&self, harness: &Harness, context: &mut Context) -> Result<(), String> {
&self,
harness: &Harness,
strict: bool,
context: &mut Context,
) -> Result<(), String> {
// Register the print() function. // Register the print() function.
context.register_global_function("print", 1, test262_print); context.register_global_function("print", 1, test262_print);
// add the $262 object. // add the $262 object.
let _js262 = js262::init(context); let _js262 = js262::init(context);
if strict { if self.flags.contains(TestFlags::RAW) {
context return Ok(());
.eval(r#""use strict";"#)
.map_err(|e| format!("could not set strict mode:\n{}", e.display()))?;
} }
context context

Loading…
Cancel
Save