[parser] Enforce module-specific identifier restriction
Restrict the use of the `await` token as an identifier when parsing
source text as module code.
From
http://www.ecma-international.org/ecma-262/6.0/#sec-future-reserved-words:
> 11.6.2.2 Future Reserved Words
>
> The following tokens are reserved for used as keywords in future
> language extensions.
>
> Syntax
>
> FutureReservedWord ::
> enum
> await
>
> await is only treated as a FutureReservedWord when Module is the goal
> symbol of the syntactic grammar.
BUG=v8:4767
LOG=N
R=adamk@chromium.org
Review-Url: https://codereview.chromium.org/1723313002
Cr-Commit-Position: refs/heads/master@{#35914}
diff --git a/src/ast/scopes.h b/src/ast/scopes.h
index c3f7d59..d35a202 100644
--- a/src/ast/scopes.h
+++ b/src/ast/scopes.h
@@ -243,7 +243,7 @@
// Set the language mode flag (unless disabled by a global flag).
void SetLanguageMode(LanguageMode language_mode) {
- DCHECK(!is_module_scope());
+ DCHECK(!is_module_scope() || is_strict(language_mode));
language_mode_ = language_mode;
}
diff --git a/src/parsing/parser-base.h b/src/parsing/parser-base.h
index d273910..7202c98 100644
--- a/src/parsing/parser-base.h
+++ b/src/parsing/parser-base.h
@@ -105,6 +105,7 @@
ast_value_factory_(ast_value_factory),
log_(log),
mode_(PARSE_EAGERLY), // Lazy mode must be set explicitly.
+ parsing_module_(false),
stack_limit_(stack_limit),
zone_(zone),
scanner_(scanner),
@@ -519,9 +520,9 @@
bool peek_any_identifier() {
Token::Value next = peek();
- return next == Token::IDENTIFIER || next == Token::FUTURE_RESERVED_WORD ||
- next == Token::FUTURE_STRICT_RESERVED_WORD || next == Token::LET ||
- next == Token::STATIC || next == Token::YIELD;
+ return next == Token::IDENTIFIER || next == Token::AWAIT ||
+ next == Token::ENUM || next == Token::FUTURE_STRICT_RESERVED_WORD ||
+ next == Token::LET || next == Token::STATIC || next == Token::YIELD;
}
bool CheckContextualKeyword(Vector<const char> keyword) {
@@ -983,6 +984,7 @@
AstValueFactory* ast_value_factory_; // Not owned.
ParserRecorder* log_;
Mode mode_;
+ bool parsing_module_;
uintptr_t stack_limit_;
private:
@@ -1057,7 +1059,8 @@
case Token::IDENTIFIER:
*message = MessageTemplate::kUnexpectedTokenIdentifier;
break;
- case Token::FUTURE_RESERVED_WORD:
+ case Token::AWAIT:
+ case Token::ENUM:
*message = MessageTemplate::kUnexpectedReserved;
break;
case Token::LET:
@@ -1132,7 +1135,7 @@
ParserBase<Traits>::ParseAndClassifyIdentifier(ExpressionClassifier* classifier,
bool* ok) {
Token::Value next = Next();
- if (next == Token::IDENTIFIER) {
+ if (next == Token::IDENTIFIER || (next == Token::AWAIT && !parsing_module_)) {
IdentifierT name = this->GetSymbol(scanner());
// When this function is used to read a formal parameter, we don't always
// know whether the function is going to be strict or sloppy. Indeed for
@@ -1196,7 +1199,7 @@
ParserBase<Traits>::ParseIdentifierOrStrictReservedWord(
bool is_generator, bool* is_strict_reserved, bool* ok) {
Token::Value next = Next();
- if (next == Token::IDENTIFIER) {
+ if (next == Token::IDENTIFIER || (next == Token::AWAIT && !parsing_module_)) {
*is_strict_reserved = false;
} else if (next == Token::FUTURE_STRICT_RESERVED_WORD || next == Token::LET ||
next == Token::STATIC || (next == Token::YIELD && !is_generator)) {
@@ -1212,14 +1215,13 @@
return name;
}
-
template <class Traits>
typename ParserBase<Traits>::IdentifierT
ParserBase<Traits>::ParseIdentifierName(bool* ok) {
Token::Value next = Next();
- if (next != Token::IDENTIFIER && next != Token::FUTURE_RESERVED_WORD &&
- next != Token::LET && next != Token::STATIC && next != Token::YIELD &&
- next != Token::FUTURE_STRICT_RESERVED_WORD &&
+ if (next != Token::IDENTIFIER && next != Token::ENUM &&
+ next != Token::AWAIT && next != Token::LET && next != Token::STATIC &&
+ next != Token::YIELD && next != Token::FUTURE_STRICT_RESERVED_WORD &&
next != Token::ESCAPED_KEYWORD &&
next != Token::ESCAPED_STRICT_RESERVED_WORD && !Token::IsKeyword(next)) {
this->ReportUnexpectedToken(next);
@@ -1316,6 +1318,7 @@
case Token::LET:
case Token::STATIC:
case Token::YIELD:
+ case Token::AWAIT:
case Token::ESCAPED_STRICT_RESERVED_WORD:
case Token::FUTURE_STRICT_RESERVED_WORD: {
// Using eval or arguments in this context is OK even in strict mode.
@@ -1683,8 +1686,8 @@
*is_computed_name);
}
- if (Token::IsIdentifier(name_token, language_mode(),
- this->is_generator()) &&
+ if (Token::IsIdentifier(name_token, language_mode(), this->is_generator(),
+ parsing_module_) &&
(peek() == Token::COMMA || peek() == Token::RBRACE ||
peek() == Token::ASSIGN)) {
// PropertyDefinition
@@ -2866,6 +2869,7 @@
case Token::STATIC:
case Token::LET: // Yes, you can do let let = ... in sloppy mode
case Token::YIELD:
+ case Token::AWAIT:
return true;
default:
return false;
diff --git a/src/parsing/parser.cc b/src/parsing/parser.cc
index bf71619..25126e1 100644
--- a/src/parsing/parser.cc
+++ b/src/parsing/parser.cc
@@ -923,7 +923,8 @@
ZoneList<Statement*>* body = new(zone()) ZoneList<Statement*>(16, zone());
bool ok = true;
int beg_pos = scanner()->location().beg_pos;
- if (info->is_module()) {
+ parsing_module_ = info->is_module();
+ if (parsing_module_) {
ParseModuleItemList(body, &ok);
} else {
// Don't count the mode in the use counters--give the program a chance
@@ -1358,7 +1359,7 @@
// Keep track of the first reserved word encountered in case our
// caller needs to report an error.
if (!reserved_loc->IsValid() &&
- !Token::IsIdentifier(name_tok, STRICT, false)) {
+ !Token::IsIdentifier(name_tok, STRICT, false, parsing_module_)) {
*reserved_loc = scanner()->location();
}
const AstRawString* local_name = ParseIdentifierName(CHECK_OK);
@@ -1409,7 +1410,8 @@
if (CheckContextualKeyword(CStrVector("as"))) {
local_name = ParseIdentifierName(CHECK_OK);
}
- if (!Token::IsIdentifier(scanner()->current_token(), STRICT, false)) {
+ if (!Token::IsIdentifier(scanner()->current_token(), STRICT, false,
+ parsing_module_)) {
*ok = false;
ReportMessage(MessageTemplate::kUnexpectedReserved);
return NULL;
@@ -4621,7 +4623,7 @@
}
PreParser::PreParseResult result = reusable_preparser_->PreParseLazyFunction(
language_mode(), function_state_->kind(), scope_->has_simple_parameters(),
- logger, bookmark, use_counts_);
+ parsing_module_, logger, bookmark, use_counts_);
if (pre_parse_timer_ != NULL) {
pre_parse_timer_->Stop();
}
diff --git a/src/parsing/preparser.cc b/src/parsing/preparser.cc
index c0db7ef..8712fff 100644
--- a/src/parsing/preparser.cc
+++ b/src/parsing/preparser.cc
@@ -38,8 +38,10 @@
PreParserIdentifier PreParserTraits::GetSymbol(Scanner* scanner) {
- if (scanner->current_token() == Token::FUTURE_RESERVED_WORD) {
- return PreParserIdentifier::FutureReserved();
+ if (scanner->current_token() == Token::ENUM) {
+ return PreParserIdentifier::Enum();
+ } else if (scanner->current_token() == Token::AWAIT) {
+ return PreParserIdentifier::Await();
} else if (scanner->current_token() ==
Token::FUTURE_STRICT_RESERVED_WORD) {
return PreParserIdentifier::FutureStrictReserved();
@@ -100,7 +102,9 @@
PreParser::PreParseResult PreParser::PreParseLazyFunction(
LanguageMode language_mode, FunctionKind kind, bool has_simple_parameters,
- ParserRecorder* log, Scanner::BookmarkScope* bookmark, int* use_counts) {
+ bool parsing_module, ParserRecorder* log, Scanner::BookmarkScope* bookmark,
+ int* use_counts) {
+ parsing_module_ = parsing_module;
log_ = log;
use_counts_ = use_counts;
// Lazy functions always have trivial outer scopes (no with/catch scopes).
@@ -603,7 +607,8 @@
if (starts_with_identifier && expr.IsIdentifier() && peek() == Token::COLON) {
// Expression is a single identifier, and not, e.g., a parenthesized
// identifier.
- DCHECK(!expr.AsIdentifier().IsFutureReserved());
+ DCHECK(!expr.AsIdentifier().IsEnum());
+ DCHECK(!parsing_module_ || !expr.AsIdentifier().IsAwait());
DCHECK(is_sloppy(language_mode()) ||
!IsFutureStrictReserved(expr.AsIdentifier()));
Consume(Token::COLON);
diff --git a/src/parsing/preparser.h b/src/parsing/preparser.h
index ecea789..3fd3ce8 100644
--- a/src/parsing/preparser.h
+++ b/src/parsing/preparser.h
@@ -55,6 +55,12 @@
static PreParserIdentifier Constructor() {
return PreParserIdentifier(kConstructorIdentifier);
}
+ static PreParserIdentifier Enum() {
+ return PreParserIdentifier(kEnumIdentifier);
+ }
+ static PreParserIdentifier Await() {
+ return PreParserIdentifier(kAwaitIdentifier);
+ }
bool IsEval() const { return type_ == kEvalIdentifier; }
bool IsArguments() const { return type_ == kArgumentsIdentifier; }
bool IsEvalOrArguments() const { return IsEval() || IsArguments(); }
@@ -64,7 +70,8 @@
bool IsYield() const { return type_ == kYieldIdentifier; }
bool IsPrototype() const { return type_ == kPrototypeIdentifier; }
bool IsConstructor() const { return type_ == kConstructorIdentifier; }
- bool IsFutureReserved() const { return type_ == kFutureReservedIdentifier; }
+ bool IsEnum() const { return type_ == kEnumIdentifier; }
+ bool IsAwait() const { return type_ == kAwaitIdentifier; }
bool IsFutureStrictReserved() const {
return type_ == kFutureStrictReservedIdentifier ||
type_ == kLetIdentifier || type_ == kStaticIdentifier ||
@@ -91,7 +98,9 @@
kArgumentsIdentifier,
kUndefinedIdentifier,
kPrototypeIdentifier,
- kConstructorIdentifier
+ kConstructorIdentifier,
+ kEnumIdentifier,
+ kAwaitIdentifier
};
explicit PreParserIdentifier(Type type) : type_(type) {}
@@ -974,12 +983,21 @@
// during parsing.
PreParseResult PreParseProgram(int* materialized_literals = 0,
bool is_module = false) {
- Scope* scope = NewScope(scope_, is_module ? MODULE_SCOPE : SCRIPT_SCOPE);
+ Scope* scope = NewScope(scope_, SCRIPT_SCOPE);
+
+ // ModuleDeclarationInstantiation for Source Text Module Records creates a
+ // new Module Environment Record whose outer lexical environment record is
+ // the global scope.
+ if (is_module) {
+ scope = NewScope(scope, MODULE_SCOPE);
+ }
+
PreParserFactory factory(NULL);
FunctionState top_scope(&function_state_, &scope_, scope, kNormalFunction,
&factory);
bool ok = true;
int start_position = scanner()->peek_location().beg_pos;
+ parsing_module_ = is_module;
ParseStatementList(Token::EOS, &ok);
if (stack_overflow()) return kPreParseStackOverflow;
if (!ok) {
@@ -1002,9 +1020,12 @@
// keyword and parameters, and have consumed the initial '{'.
// At return, unless an error occurred, the scanner is positioned before the
// the final '}'.
- PreParseResult PreParseLazyFunction(
- LanguageMode language_mode, FunctionKind kind, bool has_simple_parameters,
- ParserRecorder* log, Scanner::BookmarkScope* bookmark, int* use_counts);
+ PreParseResult PreParseLazyFunction(LanguageMode language_mode,
+ FunctionKind kind,
+ bool has_simple_parameters,
+ bool parsing_module, ParserRecorder* log,
+ Scanner::BookmarkScope* bookmark,
+ int* use_counts);
private:
friend class PreParserTraits;
diff --git a/src/parsing/scanner.cc b/src/parsing/scanner.cc
index 698cb5e..faec88b 100644
--- a/src/parsing/scanner.cc
+++ b/src/parsing/scanner.cc
@@ -1135,6 +1135,8 @@
// Keyword Matcher
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
+ KEYWORD_GROUP('a') \
+ KEYWORD("await", Token::AWAIT) \
KEYWORD_GROUP('b') \
KEYWORD("break", Token::BREAK) \
KEYWORD_GROUP('c') \
@@ -1150,7 +1152,7 @@
KEYWORD("do", Token::DO) \
KEYWORD_GROUP('e') \
KEYWORD("else", Token::ELSE) \
- KEYWORD("enum", Token::FUTURE_RESERVED_WORD) \
+ KEYWORD("enum", Token::ENUM) \
KEYWORD("export", Token::EXPORT) \
KEYWORD("extends", Token::EXTENDS) \
KEYWORD_GROUP('f') \
@@ -1196,7 +1198,6 @@
KEYWORD_GROUP('y') \
KEYWORD("yield", Token::YIELD)
-
static Token::Value KeywordOrIdentifierToken(const uint8_t* input,
int input_length, bool escaped) {
DCHECK(input_length >= 1);
diff --git a/src/parsing/token.h b/src/parsing/token.h
index fae9ea8..12d7103 100644
--- a/src/parsing/token.h
+++ b/src/parsing/token.h
@@ -148,10 +148,12 @@
T(IDENTIFIER, NULL, 0) \
\
/* Future reserved words (ECMA-262, section 7.6.1.2). */ \
- T(FUTURE_RESERVED_WORD, NULL, 0) \
T(FUTURE_STRICT_RESERVED_WORD, NULL, 0) \
+ /* `await` is a reserved word in module code only */ \
+ K(AWAIT, "await", 0) \
K(CLASS, "class", 0) \
K(CONST, "const", 0) \
+ K(ENUM, "enum", 0) \
K(EXPORT, "export", 0) \
K(EXTENDS, "extends", 0) \
K(IMPORT, "import", 0) \
@@ -173,7 +175,6 @@
T(TEMPLATE_SPAN, NULL, 0) \
T(TEMPLATE_TAIL, NULL, 0)
-
class Token {
public:
// All token values.
@@ -197,7 +198,7 @@
}
static bool IsIdentifier(Value tok, LanguageMode language_mode,
- bool is_generator) {
+ bool is_generator, bool is_module) {
switch (tok) {
case IDENTIFIER:
return true;
@@ -208,6 +209,8 @@
return is_sloppy(language_mode);
case YIELD:
return !is_generator && is_sloppy(language_mode);
+ case AWAIT:
+ return !is_module;
default:
return false;
}
diff --git a/test/cctest/test-parsing.cc b/test/cctest/test-parsing.cc
index d8e5580..a28c235 100644
--- a/test/cctest/test-parsing.cc
+++ b/test/cctest/test-parsing.cc
@@ -1542,11 +1542,10 @@
uintptr_t stack_limit = isolate->stack_guard()->real_climit();
int preparser_materialized_literals = -1;
int parser_materialized_literals = -2;
- bool test_preparser = !is_module;
// Preparse the data.
i::CompleteParserRecorder log;
- if (test_preparser) {
+ {
i::Scanner scanner(isolate->unicode_cache());
i::GenericStringUtf16CharacterStream stream(source, 0, source->length());
i::Zone zone(CcTest::i_isolate()->allocator());
@@ -1603,7 +1602,7 @@
CHECK(false);
}
- if (test_preparser && !preparse_error) {
+ if (!preparse_error) {
v8::base::OS::Print(
"Parser failed on:\n"
"\t%s\n"
@@ -1614,7 +1613,7 @@
CHECK(false);
}
// Check that preparser and parser produce the same error.
- if (test_preparser) {
+ {
i::Handle<i::String> preparser_message =
FormatMessage(log.ErrorMessageData());
if (!i::String::Equals(message_string, preparser_message)) {
@@ -1629,7 +1628,7 @@
CHECK(false);
}
}
- } else if (test_preparser && preparse_error) {
+ } else if (preparse_error) {
v8::base::OS::Print(
"Preparser failed on:\n"
"\t%s\n"
@@ -1646,8 +1645,7 @@
"However, parser and preparser succeeded",
source->ToCString().get());
CHECK(false);
- } else if (test_preparser &&
- preparser_materialized_literals != parser_materialized_literals) {
+ } else if (preparser_materialized_literals != parser_materialized_literals) {
v8::base::OS::Print(
"Preparser materialized literals (%d) differ from Parser materialized "
"literals (%d) on:\n"
@@ -5476,6 +5474,8 @@
"export { static } from 'm.js'",
"export { let } from 'm.js'",
"var a; export { a as b, a as c };",
+ "var a; export { a as await };",
+ "var a; export { a as enum };",
"import 'somemodule.js';",
"import { } from 'm.js';",
@@ -5601,6 +5601,8 @@
"import { y as yield } from 'm.js'",
"import { s as static } from 'm.js'",
"import { l as let } from 'm.js'",
+ "import { a as await } from 'm.js';",
+ "import { a as enum } from 'm.js';",
"import { x }, def from 'm.js';",
"import def, def2 from 'm.js';",
"import * as x, def from 'm.js';",
@@ -5670,6 +5672,142 @@
}
}
+TEST(ModuleAwaitReserved) {
+ // clang-format off
+ const char* kErrorSources[] = {
+ "await;",
+ "await: ;",
+ "var await;",
+ "var [await] = [];",
+ "var { await } = {};",
+ "var { x: await } = {};",
+ "{ var await; }",
+ "let await;",
+ "let [await] = [];",
+ "let { await } = {};",
+ "let { x: await } = {};",
+ "{ let await; }",
+ "const await = null;",
+ "const [await] = [];",
+ "const { await } = {};",
+ "const { x: await } = {};",
+ "{ const await = null; }",
+ "function await() {}",
+ "function f(await) {}",
+ "function* await() {}",
+ "function* g(await) {}",
+ "(function await() {});",
+ "(function (await) {});",
+ "(function* await() {});",
+ "(function* (await) {});",
+ "(await) => {};",
+ "await => {};",
+ "class await {}",
+ "class C { constructor(await) {} }",
+ "class C { m(await) {} }",
+ "class C { static m(await) {} }",
+ "class C { *m(await) {} }",
+ "class C { static *m(await) {} }",
+ "(class await {})",
+ "(class { constructor(await) {} });",
+ "(class { m(await) {} });",
+ "(class { static m(await) {} });",
+ "(class { *m(await) {} });",
+ "(class { static *m(await) {} });",
+ "({ m(await) {} });",
+ "({ *m(await) {} });",
+ "({ set p(await) {} });",
+ "try {} catch (await) {}",
+ "try {} catch (await) {} finally {}",
+ NULL
+ };
+ // clang-format on
+ const char* context_data[][2] = {{"", ""}, {NULL, NULL}};
+
+ RunModuleParserSyncTest(context_data, kErrorSources, kError);
+}
+
+TEST(ModuleAwaitReservedPreParse) {
+ const char* context_data[][2] = {{"", ""}, {NULL, NULL}};
+ const char* error_data[] = {"function f() { var await = 0; }", NULL};
+
+ RunModuleParserSyncTest(context_data, error_data, kError);
+}
+
+TEST(ModuleAwaitPermitted) {
+ // clang-format off
+ const char* kValidSources[] = {
+ "({}).await;",
+ "({ await: null });",
+ "({ await() {} });",
+ "({ get await() {} });",
+ "({ set await(x) {} });",
+ "(class { await() {} });",
+ "(class { static await() {} });",
+ "(class { *await() {} });",
+ "(class { static *await() {} });",
+ NULL
+ };
+ // clang-format on
+ const char* context_data[][2] = {{"", ""}, {NULL, NULL}};
+
+ RunModuleParserSyncTest(context_data, kValidSources, kSuccess);
+}
+
+TEST(EnumReserved) {
+ // clang-format off
+ const char* kErrorSources[] = {
+ "enum;",
+ "enum: ;",
+ "var enum;",
+ "var [enum] = [];",
+ "var { enum } = {};",
+ "var { x: enum } = {};",
+ "{ var enum; }",
+ "let enum;",
+ "let [enum] = [];",
+ "let { enum } = {};",
+ "let { x: enum } = {};",
+ "{ let enum; }",
+ "const enum = null;",
+ "const [enum] = [];",
+ "const { enum } = {};",
+ "const { x: enum } = {};",
+ "{ const enum = null; }",
+ "function enum() {}",
+ "function f(enum) {}",
+ "function* enum() {}",
+ "function* g(enum) {}",
+ "(function enum() {});",
+ "(function (enum) {});",
+ "(function* enum() {});",
+ "(function* (enum) {});",
+ "(enum) => {};",
+ "enum => {};",
+ "class enum {}",
+ "class C { constructor(enum) {} }",
+ "class C { m(enum) {} }",
+ "class C { static m(enum) {} }",
+ "class C { *m(enum) {} }",
+ "class C { static *m(enum) {} }",
+ "(class enum {})",
+ "(class { constructor(enum) {} });",
+ "(class { m(enum) {} });",
+ "(class { static m(enum) {} });",
+ "(class { *m(enum) {} });",
+ "(class { static *m(enum) {} });",
+ "({ m(enum) {} });",
+ "({ *m(enum) {} });",
+ "({ set p(enum) {} });",
+ "try {} catch (enum) {}",
+ "try {} catch (enum) {} finally {}",
+ NULL
+ };
+ // clang-format on
+ const char* context_data[][2] = {{"", ""}, {NULL, NULL}};
+
+ RunModuleParserSyncTest(context_data, kErrorSources, kError);
+}
TEST(ModuleParsingInternals) {
i::Isolate* isolate = CcTest::i_isolate();