From 6723252c9286995c114f9fed46ea2ca6f0fd7362 Mon Sep 17 00:00:00 2001 From: 0x7D2B <72297086+0x7D2B@users.noreply.github.com> Date: Thu, 20 May 2021 08:58:07 +0000 Subject: [PATCH] Handle early errors for declarations in StatementList (#1175) * Merge {Let,Const,Var}DeclList * Rustfmt * Handle early errors for declarations in StatementList --- boa/src/syntax/ast/node/declaration/mod.rs | 1 + boa/src/syntax/ast/node/statement_list/mod.rs | 30 ++++++++++- .../expression/assignment/arrow_function.rs | 21 +++++++- .../primary/async_function_expression/mod.rs | 20 +++++++- .../primary/function_expression/mod.rs | 20 +++++++- .../hoistable/async_function_decl/mod.rs | 22 +++++++- .../hoistable/function_decl/mod.rs | 22 +++++++- boa/src/syntax/parser/statement/mod.rs | 50 ++++++++++++++++++- 8 files changed, 179 insertions(+), 7 deletions(-) diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index c81906e8ed..4f375ceb6d 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -107,6 +107,7 @@ impl Executable for DeclarationList { } continue; } + match &self { Const(_) => context.create_immutable_binding( decl.name().to_owned(), diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 563eafbda8..89c5e36101 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -6,7 +6,7 @@ use crate::{ syntax::ast::node::Node, BoaProfiler, Context, Result, Value, }; -use std::{fmt, ops::Deref, rc::Rc}; +use std::{collections::HashSet, fmt, ops::Deref, rc::Rc}; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; @@ -55,6 +55,34 @@ impl StatementList { } Ok(()) } + + pub fn lexically_declared_names(&self) -> HashSet<&str> { + let mut set = HashSet::new(); + for stmt in self.items() { + if let Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) = stmt { + for decl in decl_list.as_ref() { + if !set.insert(decl.name()) { + // It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate entries. + // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors + unreachable!("Redeclaration of {}", decl.name()); + } + } + } + } + set + } + + pub fn var_declared_names(&self) -> HashSet<&str> { + let mut set = HashSet::new(); + for stmt in self.items() { + if let Node::VarDeclList(decl_list) = stmt { + for decl in decl_list.as_ref() { + set.insert(decl.name()); + } + } + } + set + } } impl Executable for StatementList { diff --git a/boa/src/syntax/parser/expression/assignment/arrow_function.rs b/boa/src/syntax/parser/expression/assignment/arrow_function.rs index 43b717b784..5528c1c8b9 100644 --- a/boa/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa/src/syntax/parser/expression/assignment/arrow_function.rs @@ -8,13 +8,13 @@ //! [spec]: https://tc39.es/ecma262/#sec-arrow-function-definitions use super::AssignmentExpression; -use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ ast::{ node::{ArrowFunctionDecl, FormalParameter, Node, Return, StatementList}, Punctuator, }, + lexer::{Error as LexError, Position, TokenKind}, parser::{ error::{ErrorContext, ParseError, ParseResult}, function::{FormalParameters, FunctionBody}, @@ -90,6 +90,25 @@ where cursor.expect(TokenKind::Punctuator(Punctuator::Arrow), "arrow function")?; let body = ConciseBody::new(self.allow_in).parse(cursor)?; + + // It is a Syntax Error if any element of the BoundNames of ArrowParameters + // also occurs in the LexicallyDeclaredNames of ConciseBody. + // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Ok(ArrowFunctionDecl::new(params, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs b/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs index 3bc2a5e271..b931863c5e 100644 --- a/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs +++ b/boa/src/syntax/parser/expression/primary/async_function_expression/mod.rs @@ -4,7 +4,7 @@ mod tests; use crate::{ syntax::{ ast::{node::AsyncFunctionExpr, Keyword, Punctuator}, - lexer::TokenKind, + lexer::{Error as LexError, Position, TokenKind}, parser::{ function::{FormalParameters, FunctionBody}, statement::BindingIdentifier, @@ -74,6 +74,24 @@ where cursor.expect(Punctuator::CloseBlock, "async function expression")?; + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Ok(AsyncFunctionExpr::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa/src/syntax/parser/expression/primary/function_expression/mod.rs index 645a588866..4083e9fa68 100644 --- a/boa/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -13,7 +13,7 @@ mod tests; use crate::{ syntax::{ ast::{node::FunctionExpr, Keyword, Punctuator}, - lexer::TokenKind, + lexer::{Error as LexError, Position, TokenKind}, parser::{ function::{FormalParameters, FunctionBody}, statement::BindingIdentifier, @@ -69,6 +69,24 @@ where cursor.expect(Punctuator::CloseBlock, "function expression")?; + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Ok(FunctionExpr::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs index 049b458b3b..0c9f3c5e97 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/async_function_decl/mod.rs @@ -5,7 +5,9 @@ use crate::syntax::{ ast::{node::AsyncFunctionDecl, Keyword, Punctuator}, lexer::TokenKind, parser::{ - function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, + function::FormalParameters, + function::FunctionBody, + statement::{BindingIdentifier, LexError, Position}, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -84,6 +86,24 @@ where cursor.expect(Punctuator::CloseBlock, "async function declaration")?; + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Ok(AsyncFunctionDecl::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs b/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs index f40045b5fd..a2f3c765b4 100644 --- a/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs +++ b/boa/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs @@ -4,7 +4,9 @@ mod tests; use crate::syntax::{ ast::{node::FunctionDecl, Keyword, Punctuator}, parser::{ - function::FormalParameters, function::FunctionBody, statement::BindingIdentifier, + function::FormalParameters, + function::FunctionBody, + statement::{BindingIdentifier, LexError, Position}, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, }, }; @@ -65,6 +67,24 @@ where cursor.expect(Punctuator::CloseBlock, "function declaration")?; + // It is a Syntax Error if any element of the BoundNames of FormalParameters + // also occurs in the LexicallyDeclaredNames of FunctionBody. + // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors + { + let lexically_declared_names = body.lexically_declared_names(); + for param in params.as_ref() { + if lexically_declared_names.contains(param.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of formal parameter `{}`", param.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Ok(FunctionDecl::new(name, params, body)) } } diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 2489ac58fb..d77eb58bde 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -41,13 +41,14 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser use crate::{ syntax::{ ast::{node, Keyword, Node, Punctuator}, - lexer::{Error as LexError, InputElement, TokenKind}, + lexer::{Error as LexError, InputElement, Position, TokenKind}, parser::expression::await_expr::AwaitExpression, }, BoaProfiler, }; use labelled_stm::LabelledStatement; +use std::collections::HashSet; use std::io::Read; /// Statement parsing. @@ -289,6 +290,53 @@ where while cursor.next_if(Punctuator::Semicolon)?.is_some() {} } + // Handle any redeclarations + // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors + { + let mut lexically_declared_names: HashSet<&str> = HashSet::new(); + let mut var_declared_names: HashSet<&str> = HashSet::new(); + + // TODO: Use more helpful positions in errors when spans are added to Nodes + for item in &items { + match item { + Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => { + for decl in decl_list.as_ref() { + // if name in VarDeclaredNames or can't be added to + // LexicallyDeclaredNames, raise an error + if var_declared_names.contains(decl.name()) + || !lexically_declared_names.insert(decl.name()) + { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of variable `{}`", decl.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + } + } + Node::VarDeclList(decl_list) => { + for decl in decl_list.as_ref() { + // if name in LexicallyDeclaredNames, raise an error + if lexically_declared_names.contains(decl.name()) { + return Err(ParseError::lex(LexError::Syntax( + format!("Redeclaration of variable `{}`", decl.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, + ))); + } + // otherwise, add to VarDeclaredNames + var_declared_names.insert(decl.name()); + } + } + _ => (), + } + } + } + items.sort_by(Node::hoistable_order); Ok(items.into())