Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict Mode Lex/Parse #717

Merged
merged 22 commits into from
Oct 5, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
121 changes: 121 additions & 0 deletions boa/src/exec/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1383,3 +1383,124 @@ fn test_identifier_op() {
let scenario = "break = 1";
assert_eq!(&exec(scenario), "\"SyntaxError\": \"expected token \'identifier\', got \'=\' in binding identifier at line 1, col 7\"");
}

#[test]
fn test_strict_mode_octal() {
// Checks as per https://tc39.es/ecma262/#sec-literals-numeric-literals that 0 prefix
// octal number literal syntax is a syntax error in strict mode.

let scenario = r#"
'use strict';
var n = 023;
"#;

let mut engine = Context::new();

let string = dbg!(forward(&mut engine, scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}

#[test]
fn test_strict_mode_with() {
// Checks as per https://tc39.es/ecma262/#sec-with-statement-static-semantics-early-errors
// that a with statement is an error in strict mode code.

let scenario = r#"
'use strict';
function f(x, o) {
with (o) {
console.log(x);
}
}
"#;

let mut engine = Context::new();

let string = dbg!(forward(&mut engine, scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}

#[test]
fn test_strict_mode_delete() {
// Checks as per https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors
// that delete on a variable name is an error in strict mode code.

let scenario = r#"
'use strict';
let x = 10;
delete x;
"#;

let mut engine = Context::new();

let string = dbg!(forward(&mut engine, scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}

#[test]
fn test_strict_mode_reserved_name() {
// Checks that usage of a reserved keyword for an identifier name is
// an error in strict mode code as per https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript.

let test_cases = [
"var implements = 10;",
"var interface = 10;",
"var package = 10;",
"var private = 10;",
"var protected = 10;",
"var public = 10;",
"var static = 10;",
"var eval = 10;",
"var arguments = 10;",
"var let = 10;",
"var yield = 10;",
];

for case in test_cases.iter() {
let mut engine = Context::new();
let scenario = format!("'use strict'; \n {}", case);

let string = dbg!(forward(&mut engine, &scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}
}

#[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() {} }
"#;

let mut engine = Context::new();

let string = dbg!(forward(&mut engine, scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}

#[test]
fn test_strict_mode_dup_func_parameters() {
// Checks that a function cannot contain duplicate parameter
// names in strict mode code as per https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors.

let scenario = r#"
'use strict';
function f(a, b, b) {}
"#;

let mut engine = Context::new();

let string = dbg!(forward(&mut engine, scenario));

assert!(string.starts_with("Uncaught \"SyntaxError\": "));
}
12 changes: 12 additions & 0 deletions boa/src/syntax/lexer/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub(super) struct Cursor<R> {
iter: InnerIter<R>,
peeked: Option<Option<char>>,
pos: Position,
strict_mode: bool,
}

impl<R> Cursor<R> {
Expand Down Expand Up @@ -38,6 +39,16 @@ impl<R> Cursor<R> {
let current_line = self.pos.line_number();
self.pos = Position::new(current_line, 1);
}

#[inline]
pub(super) fn strict_mode(&self) -> bool {
self.strict_mode
}

#[inline]
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.strict_mode = strict_mode
}
}

impl<R> Cursor<R>
Expand All @@ -51,6 +62,7 @@ where
iter: InnerIter::new(inner.bytes()),
peeked: None,
pos: Position::new(1, 1),
strict_mode: false,
}
}

Expand Down
32 changes: 31 additions & 1 deletion boa/src/syntax/lexer/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,26 @@ use super::{Cursor, Error, Tokenizer};
use crate::{
profiler::BoaProfiler,
syntax::{
ast::{Position, Span},
ast::{Keyword, Position, Span},
lexer::{Token, TokenKind},
},
};
use std::io::Read;

const STRICT_FORBIDDEN_IDENTIFIERS: [&str; 11] = [
"eval",
"arguments",
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
"yield",
];

/// Identifier lexing.
///
/// More information:
Expand Down Expand Up @@ -49,8 +63,24 @@ impl<R> Tokenizer<R> for Identifier {
"null" => TokenKind::NullLiteral,
slice => {
if let Ok(keyword) = slice.parse() {
if cursor.strict_mode() && keyword == Keyword::With {
return Err(Error::Syntax(
"using 'with' statement not allowed in strict mode".into(),
start_pos,
));
}
TokenKind::Keyword(keyword)
} else {
if cursor.strict_mode() && STRICT_FORBIDDEN_IDENTIFIERS.contains(&slice) {
return Err(Error::Syntax(
format!(
"using future reserved keyword '{}' not allowed in strict mode",
slice
)
.into(),
start_pos,
));
}
TokenKind::identifier(slice)
}
}
Expand Down
17 changes: 11 additions & 6 deletions boa/src/syntax/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ impl<R> Lexer<R> {
self.goal_symbol
}

#[inline]
pub(super) fn strict_mode(&self) -> bool {
self.cursor.strict_mode()
}

#[inline]
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.cursor.set_strict_mode(strict_mode)
}

/// Creates a new lexer.
#[inline]
pub fn new(reader: R) -> Self
Expand Down Expand Up @@ -180,19 +190,14 @@ impl<R> Lexer<R> {
}
};

// TODO, setting strict mode on/off.
let strict_mode = false;

let token = match next_chr {
'\r' | '\n' | '\u{2028}' | '\u{2029}' => Ok(Token::new(
TokenKind::LineTerminator,
Span::new(start, self.cursor.pos()),
)),
'"' | '\'' => StringLiteral::new(next_chr).lex(&mut self.cursor, start),
'`' => TemplateLiteral.lex(&mut self.cursor, start),
_ if next_chr.is_digit(10) => {
NumberLiteral::new(next_chr, strict_mode).lex(&mut self.cursor, start)
}
_ if next_chr.is_digit(10) => NumberLiteral::new(next_chr).lex(&mut self.cursor, start),
_ if next_chr.is_alphabetic() || next_chr == '$' || next_chr == '_' => {
Identifier::new(next_chr).lex(&mut self.cursor, start)
}
Expand Down
9 changes: 4 additions & 5 deletions boa/src/syntax/lexer/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@ use std::{io::Read, str::FromStr};
#[derive(Debug, Clone, Copy)]
pub(super) struct NumberLiteral {
init: char,
strict_mode: bool,
}

impl NumberLiteral {
/// Creates a new string literal lexer.
pub(super) fn new(init: char, strict_mode: bool) -> Self {
Self { init, strict_mode }
pub(super) fn new(init: char) -> Self {
Self { init }
}
}

Expand Down Expand Up @@ -187,7 +186,7 @@ impl<R> Tokenizer<R> for NumberLiteral {
ch => {
if ch.is_digit(8) {
// LegacyOctalIntegerLiteral
if self.strict_mode {
if cursor.strict_mode() {
// LegacyOctalIntegerLiteral is forbidden with strict mode true.
return Err(Error::syntax(
"implicit octal literals are not allowed in strict mode",
Expand All @@ -205,7 +204,7 @@ impl<R> Tokenizer<R> for NumberLiteral {
// Indicates a numerical digit comes after then 0 but it isn't an octal digit
// so therefore this must be a number with an unneeded leading 0. This is
// forbidden in strict mode.
if self.strict_mode {
if cursor.strict_mode() {
return Err(Error::syntax(
"leading 0's are not allowed in strict mode",
start_pos,
Expand Down
10 changes: 10 additions & 0 deletions boa/src/syntax/parser/cursor/buffered_lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ where
self.lexer.lex_slash_token(start).map_err(|e| e.into())
}

#[inline]
pub(super) fn strict_mode(&self) -> bool {
self.lexer.strict_mode()
}

#[inline]
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.lexer.set_strict_mode(strict_mode)
}

/// Fills the peeking buffer with the next token.
///
/// It will not fill two line terminators one after the other.
Expand Down
10 changes: 10 additions & 0 deletions boa/src/syntax/parser/cursor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ where
self.buffered_lexer.peek(skip_n, true)
}

#[inline]
pub(super) fn strict_mode(&self) -> bool {
self.buffered_lexer.strict_mode()
}

#[inline]
pub(super) fn set_strict_mode(&mut self, strict_mode: bool) {
self.buffered_lexer.set_strict_mode(strict_mode)
}

/// Returns an error if the next token is not of kind `kind`.
///
/// Note: it will consume the next token only if the next token is the expected type.
Expand Down
2 changes: 1 addition & 1 deletion boa/src/syntax/parser/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo
{
type Output = Node;

fn parse(self, cursor: &mut Cursor<R>) -> ParseResult {
fn parse(self, cursor: &mut Cursor<R>)-> ParseResult {
Lan2u marked this conversation as resolved.
Show resolved Hide resolved
let _timer = BoaProfiler::global().start_event($profile, "Parsing");

if $goal.is_some() {
Expand Down
18 changes: 15 additions & 3 deletions boa/src/syntax/parser/expression/unary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
op::UnaryOp,
Keyword, Punctuator,
},
lexer::TokenKind,
lexer::{Error as LexError, TokenKind},
parser::{
expression::update::UpdateExpression, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
Expand Down Expand Up @@ -61,11 +61,23 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> ParseResult {
let _timer = BoaProfiler::global().start_event("UnaryExpression", "Parsing");

let tok = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?;
// TODO: can we avoid cloning?
let tok = cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.clone();
RageKnify marked this conversation as resolved.
Show resolved Hide resolved
match tok.kind() {
TokenKind::Keyword(Keyword::Delete) => {
cursor.next()?.expect("Delete keyword vanished"); // Consume the token.
Ok(node::UnaryOp::new(UnaryOp::Delete, self.parse(cursor)?).into())
let val = self.parse(cursor)?;

if cursor.strict_mode() {
if let Node::Identifier(_) = val {
return Err(ParseError::lex(LexError::Syntax(
"Delete <variable> statements not allowed in strict mode".into(),
tok.span().start(),
)));
}
}

Ok(node::UnaryOp::new(UnaryOp::Delete, val).into())
}
TokenKind::Keyword(Keyword::Void) => {
cursor.next()?.expect("Void keyword vanished"); // Consume the token.
Expand Down
21 changes: 18 additions & 3 deletions boa/src/syntax/parser/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,27 @@ where

fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("FunctionStatementList", "Parsing");

let global_strict_mode = cursor.strict_mode();
if let Some(tk) = cursor.peek(0)? {
if tk.kind() == &Punctuator::CloseBlock.into() {
return Ok(Vec::new().into());
match tk.kind() {
TokenKind::Punctuator(Punctuator::CloseBlock) => {
return Ok(Vec::new().into());
}
TokenKind::StringLiteral(string) | TokenKind::TemplateLiteral(string) => {
if string == &"use strict".into() {
cursor.set_strict_mode(true);
}
}
_ => {}
}
}

StatementList::new(self.allow_yield, self.allow_await, true, true).parse(cursor)
let stmlist =
StatementList::new(self.allow_yield, self.allow_await, true, true, true).parse(cursor);

// Reset strict mode back to the global scope.
cursor.set_strict_mode(global_strict_mode);
stmlist
}
}