Skip to content

Commit

Permalink
Fix some HoistableDeclaration parsing errors (#2532)
Browse files Browse the repository at this point in the history
This Pull Request hanges the following:

- Add early errors for invalid `yield` and `await` usage in function parameters.
- Add missing function types to hoistable ordering.
- Do not attempt to parse `async` with a following line terminator as an async function.
  • Loading branch information
raskad committed Jan 20, 2023
1 parent 0d59929 commit f19467a
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 26 deletions.
23 changes: 17 additions & 6 deletions boa_ast/src/statement_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,23 @@ impl StatementListItem {
pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering {
match (a, b) {
(
Self::Declaration(Declaration::Function(_)),
Self::Declaration(Declaration::Function(_)),
) => Ordering::Equal,
(_, Self::Declaration(Declaration::Function(_))) => Ordering::Greater,
(Self::Declaration(Declaration::Function(_)), _) => Ordering::Less,

_,
Self::Declaration(
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_),
),
) => Ordering::Greater,
(
Self::Declaration(
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_),
),
_,
) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
Expand Down
15 changes: 13 additions & 2 deletions boa_parser/src/parser/expression/primary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,25 @@ where
}
TokenKind::Keyword((Keyword::Async, contain_escaped_char)) => {
let contain_escaped_char = *contain_escaped_char;
let skip_n = if cursor.peek_is_line_terminator(0, interner).or_abrupt()? {
2
} else {
1
};
let is_line_terminator = cursor
.peek_is_line_terminator(skip_n, interner)?
.unwrap_or(true);

match cursor.peek(1, interner)?.map(Token::kind) {
Some(TokenKind::Keyword((Keyword::Function, _))) if contain_escaped_char => {
Some(TokenKind::Keyword((Keyword::Function, _)))
if !is_line_terminator && contain_escaped_char =>
{
Err(Error::general(
"Keyword must not contain escaped characters",
tok_position,
))
}
Some(TokenKind::Keyword((Keyword::Function, _))) => {
Some(TokenKind::Keyword((Keyword::Function, _))) if !is_line_terminator => {
cursor.advance(interner);
match cursor.peek(1, interner)?.map(Token::kind) {
Some(TokenKind::Punctuator(Punctuator::Mul)) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ impl CallableDeclaration for AsyncFunctionDeclaration {
fn body_allow_await(&self) -> bool {
true
}
fn parameters_await_is_early_error(&self) -> bool {
true
}
}

impl<R> TokenParser<R> for AsyncFunctionDeclaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ impl CallableDeclaration for AsyncGeneratorDeclaration {
fn body_allow_await(&self) -> bool {
true
}
fn parameters_await_is_early_error(&self) -> bool {
true
}
fn parameters_yield_is_early_error(&self) -> bool {
true
}
}

impl<R> TokenParser<R> for AsyncGeneratorDeclaration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ impl CallableDeclaration for GeneratorDeclaration {
fn body_allow_await(&self) -> bool {
false
}
fn parameters_yield_is_early_error(&self) -> bool {
true
}
}

impl<R> TokenParser<R> for GeneratorDeclaration
Expand Down
35 changes: 32 additions & 3 deletions boa_parser/src/parser/statement/declaration/hoistable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ trait CallableDeclaration {
fn parameters_allow_await(&self) -> bool;
fn body_allow_yield(&self) -> bool;
fn body_allow_await(&self) -> bool;
fn parameters_yield_is_early_error(&self) -> bool {
false
}
fn parameters_await_is_early_error(&self) -> bool {
false
}
}

// This is a helper function to not duplicate code in the individual callable declaration parsers.
Expand Down Expand Up @@ -176,7 +182,7 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(

cursor.expect(Punctuator::CloseBlock, c.error_context(), interner)?;

// Early Error: If the source code matching FormalParameters is strict mode code,
// If the source text matched by FormalParameters is strict mode code,
// the Early Error rules for UniqueFormalParameters : FormalParameters are applied.
if (cursor.strict_mode() || body.strict()) && params.has_duplicates() {
return Err(Error::lex(LexError::Syntax(
Expand All @@ -185,7 +191,7 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(
)));
}

// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of FormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(Error::lex(LexError::Syntax(
Expand Down Expand Up @@ -215,19 +221,42 @@ fn parse_callable_declaration<R: Read, C: CallableDeclaration>(

// 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
name_in_lexically_declared_names(
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

// It is a Syntax Error if FormalParameters Contains SuperProperty is true.
// It is a Syntax Error if FunctionBody Contains SuperProperty is true.
// It is a Syntax Error if FormalParameters Contains SuperCall is true.
// It is a Syntax Error if FunctionBody Contains SuperCall is true.
if contains(&body, ContainsSymbol::Super) || contains(&params, ContainsSymbol::Super) {
return Err(Error::lex(LexError::Syntax(
"invalid super usage".into(),
params_start_position,
)));
}

if c.parameters_yield_is_early_error() {
// It is a Syntax Error if FormalParameters Contains YieldExpression is true.
if contains(&params, ContainsSymbol::YieldExpression) {
return Err(Error::lex(LexError::Syntax(
"invalid yield usage in generator function parameters".into(),
params_start_position,
)));
}
}

if c.parameters_await_is_early_error() {
// It is a Syntax Error if FormalParameters Contains AwaitExpression is true.
if contains(&params, ContainsSymbol::AwaitExpression) {
return Err(Error::lex(LexError::Syntax(
"invalid await usage in generator function parameters".into(),
params_start_position,
)));
}
}

Ok((name, params, body))
}
41 changes: 27 additions & 14 deletions boa_parser/src/parser/statement/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,34 @@ where
));
}
TokenKind::Keyword((Keyword::Async, false)) => {
let next_token = cursor.peek(1, interner).or_abrupt()?;
match next_token.kind() {
TokenKind::Keyword((Keyword::Function, true)) => {
return Err(Error::general(
"Keyword must not contain escaped characters",
next_token.span().start(),
));
}
TokenKind::Keyword((Keyword::Function, false)) => {
return Err(Error::general(
"expected statement",
next_token.span().start(),
));
let skip_n = if cursor.peek_is_line_terminator(0, interner).or_abrupt()? {
2
} else {
1
};
let is_line_terminator = cursor
.peek_is_line_terminator(skip_n, interner)?
.unwrap_or(true);

if is_line_terminator {
{}
} else {
let next_token = cursor.peek(1, interner).or_abrupt()?;
match next_token.kind() {
TokenKind::Keyword((Keyword::Function, true)) => {
return Err(Error::general(
"Keyword must not contain escaped characters",
next_token.span().start(),
));
}
TokenKind::Keyword((Keyword::Function, false)) => {
return Err(Error::general(
"expected statement",
next_token.span().start(),
));
}
_ => {}
}
_ => {}
}
}
TokenKind::Keyword((Keyword::Let, false)) => {
Expand Down
11 changes: 10 additions & 1 deletion boa_parser/src/parser/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,17 @@ where
.parse(cursor, interner)
.map(ast::StatementListItem::from),
TokenKind::Keyword((Keyword::Async, _)) => {
let skip_n = if cursor.peek_is_line_terminator(0, interner).or_abrupt()? {
2
} else {
1
};
let is_line_terminator = cursor
.peek_is_line_terminator(skip_n, interner)?
.unwrap_or(true);

match cursor.peek(1, interner)?.map(Token::kind) {
Some(TokenKind::Keyword((Keyword::Function, _))) => {
Some(TokenKind::Keyword((Keyword::Function, _))) if !is_line_terminator => {
Declaration::new(self.allow_yield, self.allow_await)
.parse(cursor, interner)
.map(ast::StatementListItem::from)
Expand Down

0 comments on commit f19467a

Please sign in to comment.