Skip to content

Commit

Permalink
Merge 4eab2db into e60d081
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Lancaster committed Oct 4, 2020
2 parents e60d081 + 4eab2db commit 7f93367
Show file tree
Hide file tree
Showing 14 changed files with 312 additions and 43 deletions.
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.

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

0 comments on commit 7f93367

Please sign in to comment.