Skip to content

Commit

Permalink
Add early errors for 'eval' or 'arguments' in parameters (#2515)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Add early errors for functions to make sure that 'eval' or 'arguments' cannot be used as binding identifiers in function parameters. When the function body contains a strict directive, this also has to be accounted for.
- Fix early errors for function identifiers to make sure they cannot be 'eval' or 'arguments' when a function body contains a strict directive.
  • Loading branch information
raskad committed Jan 7, 2023
1 parent 616b7a4 commit f998a1c
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 105 deletions.
11 changes: 11 additions & 0 deletions boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub enum ContainsSymbol {
This,
/// A method definition.
MethodDefinition,
/// The BindingIdentifier "eval" or "arguments".
EvalOrArguments,
}

/// Returns `true` if the node contains the given symbol.
Expand All @@ -66,6 +68,15 @@ where
impl<'ast> Visitor<'ast> for ContainsVisitor {
type BreakTy = ();

fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::EvalOrArguments
&& (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS)
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
}

fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ use crate::{
parser::{
expression::BindingIdentifier,
function::{FormalParameters, FunctionBody},
name_in_lexically_declared_names, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser,
name_in_lexically_declared_names, Cursor, OrAbrupt, ParseResult, TokenParser,
},
Error,
};
use boa_ast::{
expression::Identifier,
function::AsyncFunction,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol},
Keyword, Position, Punctuator,
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
Expand All @@ -31,20 +31,15 @@ use std::io::Read;
#[derive(Debug, Clone, Copy)]
pub(super) struct AsyncFunctionExpression {
name: Option<Identifier>,
allow_yield: AllowYield,
}

impl AsyncFunctionExpression {
/// Creates a new `AsyncFunctionExpression` parser.
pub(super) fn new<N, Y>(name: N, allow_yield: Y) -> Self
pub(super) fn new<N>(name: N) -> Self
where
N: Into<Option<Identifier>>,
Y: Into<AllowYield>,
{
Self {
name: name.into(),
allow_yield: allow_yield.into(),
}
Self { name: name.into() }
}
}

Expand All @@ -63,26 +58,20 @@ where
interner,
)?;

let (name, has_binding_identifier) = match cursor.peek(0, interner).or_abrupt()?.kind() {
TokenKind::Punctuator(Punctuator::OpenParen) => (self.name, false),
_ => (
Some(BindingIdentifier::new(self.allow_yield, true).parse(cursor, interner)?),
true,
),
};

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) {
return Err(Error::lex(LexError::Syntax(
"Unexpected eval or arguments in strict mode".into(),
cursor
.peek(0, interner)?
.map_or_else(|| Position::new(1, 1), |token| token.span().end()),
)));
let token = cursor.peek(0, interner).or_abrupt()?;
let (name, name_span) = match token.kind() {
TokenKind::Identifier(_)
| TokenKind::Keyword((
Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of,
_,
)) => {
let span = token.span();
let name = BindingIdentifier::new(false, true).parse(cursor, interner)?;

(Some(name), span)
}
}
_ => (None, token.span()),
};

let params_start_position = cursor
.expect(Punctuator::OpenParen, "async function expression", interner)?
Expand Down Expand Up @@ -124,6 +113,28 @@ where
)));
}

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if (cursor.strict_mode() || body.strict())
&& [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym())
{
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
name_span.start(),
)));
}
}

// Catch early error for BindingIdentifier, because strictness of the functions body is also
// relevant for the function parameters.
if body.strict() && contains(&params, ContainsSymbol::EvalOrArguments) {
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
params_start_position,
)));
}

// 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
Expand All @@ -133,7 +144,7 @@ where
params_start_position,
)?;

let function = AsyncFunction::new(name, params, body, has_binding_identifier);
let function = AsyncFunction::new(name.or(self.name), params, body, name.is_some());

if contains(&function, ContainsSymbol::Super) {
return Err(Error::lex(LexError::Syntax(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use boa_ast::{
expression::Identifier,
function::AsyncGenerator,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol},
Keyword, Position, Punctuator,
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
Expand Down Expand Up @@ -72,26 +72,20 @@ where
interner,
)?;

let (name, has_binding_identifier) = match cursor.peek(0, interner).or_abrupt()?.kind() {
TokenKind::Punctuator(Punctuator::OpenParen) => (self.name, false),
_ => (
Some(BindingIdentifier::new(true, true).parse(cursor, interner)?),
true,
),
};

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict
// mode code, it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) {
return Err(Error::lex(LexError::Syntax(
"Unexpected eval or arguments in strict mode".into(),
cursor
.peek(0, interner)?
.map_or_else(|| Position::new(1, 1), |token| token.span().end()),
)));
let token = cursor.peek(0, interner).or_abrupt()?;
let (name, name_span) = match token.kind() {
TokenKind::Identifier(_)
| TokenKind::Keyword((
Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of,
_,
)) => {
let span = token.span();
let name = BindingIdentifier::new(true, true).parse(cursor, interner)?;

(Some(name), span)
}
}
_ => (None, token.span()),
};

let params_start_position = cursor
.expect(
Expand Down Expand Up @@ -157,6 +151,28 @@ where
)));
}

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if (cursor.strict_mode() || body.strict())
&& [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym())
{
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
name_span.start(),
)));
}
}

// Catch early error for BindingIdentifier, because strictness of the functions body is also
// relevant for the function parameters.
if body.strict() && contains(&params, ContainsSymbol::EvalOrArguments) {
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
params_start_position,
)));
}

// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of FunctionBody.
name_in_lexically_declared_names(
Expand All @@ -165,7 +181,7 @@ where
params_start_position,
)?;

let function = AsyncGenerator::new(name, params, body, has_binding_identifier);
let function = AsyncGenerator::new(name.or(self.name), params, body, name.is_some());

if contains(&function, ContainsSymbol::Super) {
return Err(Error::lex(LexError::Syntax(
Expand Down
45 changes: 29 additions & 16 deletions boa_parser/src/parser/expression/primary/function_expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use boa_ast::{
expression::Identifier,
function::Function,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol},
Keyword, Position, Punctuator,
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
Expand Down Expand Up @@ -61,28 +61,19 @@ where
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let _timer = Profiler::global().start_event("FunctionExpression", "Parsing");

let (name, has_binding_identifier) = match cursor.peek(0, interner).or_abrupt()?.kind() {
let token = cursor.peek(0, interner).or_abrupt()?;
let (name, name_span) = match token.kind() {
TokenKind::Identifier(_)
| TokenKind::Keyword((
Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of,
_,
)) => {
let span = token.span();
let name = BindingIdentifier::new(false, false).parse(cursor, interner)?;

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) {
return Err(Error::lex(LexError::Syntax(
"Unexpected eval or arguments in strict mode".into(),
cursor
.peek(0, interner)?
.map_or_else(|| Position::new(1, 1), |token| token.span().end()),
)));
}

(Some(name), true)
(Some(name), span)
}
_ => (self.name, false),
_ => (None, token.span()),
};

let params_start_position = cursor
Expand Down Expand Up @@ -117,6 +108,28 @@ where
)));
}

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if (cursor.strict_mode() || body.strict())
&& [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym())
{
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
name_span.start(),
)));
}
}

// Catch early error for BindingIdentifier, because strictness of the functions body is also
// relevant for the function parameters.
if body.strict() && contains(&params, ContainsSymbol::EvalOrArguments) {
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
params_start_position,
)));
}

// 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
Expand All @@ -127,7 +140,7 @@ where
)?;

let function =
Function::new_with_binding_identifier(name, params, body, has_binding_identifier);
Function::new_with_binding_identifier(name.or(self.name), params, body, name.is_some());

if contains(&function, ContainsSymbol::Super) {
return Err(Error::lex(LexError::Syntax(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use boa_ast::{
expression::Identifier,
function::Generator,
operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol},
Position, Punctuator,
Keyword, Punctuator,
};
use boa_interner::{Interner, Sym};
use boa_profiler::Profiler;
Expand Down Expand Up @@ -67,27 +67,20 @@ where
interner,
)?;

let (name, has_binding_identifier) = match cursor.peek(0, interner).or_abrupt()?.kind() {
TokenKind::Punctuator(Punctuator::OpenParen) => (self.name, false),
_ => (
Some(BindingIdentifier::new(true, false).parse(cursor, interner)?),
true,
),
};

// If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
// https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors
if let Some(name) = name {
if cursor.strict_mode() && [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym()) {
return Err(Error::lex(LexError::Syntax(
"Unexpected eval or arguments in strict mode".into(),
cursor
.peek(0, interner)?
.map_or_else(|| Position::new(1, 1), |token| token.span().end()),
)));
let token = cursor.peek(0, interner).or_abrupt()?;
let (name, name_span) = match token.kind() {
TokenKind::Identifier(_)
| TokenKind::Keyword((
Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of,
_,
)) => {
let span = token.span();
let name = BindingIdentifier::new(true, false).parse(cursor, interner)?;

(Some(name), span)
}
}
_ => (None, token.span()),
};

let params_start_position = cursor
.expect(Punctuator::OpenParen, "generator expression", interner)?
Expand Down Expand Up @@ -123,6 +116,28 @@ where
)));
}

// Early Error: If BindingIdentifier is present and the source code matching BindingIdentifier is strict mode code,
// it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments".
if let Some(name) = name {
if (cursor.strict_mode() || body.strict())
&& [Sym::EVAL, Sym::ARGUMENTS].contains(&name.sym())
{
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
name_span.start(),
)));
}
}

// Catch early error for BindingIdentifier, because strictness of the functions body is also
// relevant for the function parameters.
if body.strict() && contains(&params, ContainsSymbol::EvalOrArguments) {
return Err(Error::lex(LexError::Syntax(
"unexpected identifier 'eval' or 'arguments' in strict mode".into(),
params_start_position,
)));
}

// It is a Syntax Error if any element of the BoundNames of FormalParameters
// also occurs in the LexicallyDeclaredNames of GeneratorBody.
// https://tc39.es/ecma262/#sec-generator-function-definitions-static-semantics-early-errors
Expand All @@ -141,7 +156,7 @@ where
)));
}

let function = Generator::new(name, params, body, has_binding_identifier);
let function = Generator::new(name.or(self.name), params, body, name.is_some());

if contains(&function, ContainsSymbol::Super) {
return Err(Error::lex(LexError::Syntax(
Expand Down
Loading

0 comments on commit f998a1c

Please sign in to comment.