Commit efe5b72d authored by mike's avatar mike Committed by Commit bot

[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}
parent 791c0400
......@@ -243,7 +243,7 @@ class Scope: public ZoneObject {
// 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;
}
......
......@@ -105,6 +105,7 @@ class ParserBase : public Traits {
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 @@ class ParserBase : public Traits {
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 @@ class ParserBase : public Traits {
AstValueFactory* ast_value_factory_; // Not owned.
ParserRecorder* log_;
Mode mode_;
bool parsing_module_;
uintptr_t stack_limit_;
private:
......@@ -1057,7 +1059,8 @@ void ParserBase<Traits>::GetUnexpectedTokenMessage(
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 @@ typename ParserBase<Traits>::IdentifierT
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 @@ typename ParserBase<Traits>::IdentifierT
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 @@ ParserBase<Traits>::ParseIdentifierOrStrictReservedWord(
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 @@ ParserBase<Traits>::ParsePrimaryExpression(ExpressionClassifier* classifier,
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 @@ ParserBase<Traits>::ParsePropertyDefinition(
*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 @@ bool ParserBase<Traits>::IsNextLetKeyword() {
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;
......
......@@ -923,7 +923,8 @@ FunctionLiteral* Parser::DoParseProgram(ParseInfo* info) {
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 @@ void* Parser::ParseExportClause(ZoneList<const AstRawString*>* export_names,
// 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 @@ ZoneList<ImportDeclaration*>* Parser::ParseNamedImports(int pos, bool* ok) {
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 Parser::ParseLazyFunctionBodyWithPreParser(
}
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();
}
......
......@@ -38,8 +38,10 @@ void PreParserTraits::ReportMessageAt(int start_pos, int end_pos,
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 @@ PreParserExpression PreParserTraits::ParseFunctionLiteral(
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 @@ PreParser::Statement PreParser::ParseExpressionOrLabelledStatement(
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);
......
......@@ -55,6 +55,12 @@ class PreParserIdentifier {
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 @@ class PreParserIdentifier {
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 @@ class PreParserIdentifier {
kArgumentsIdentifier,
kUndefinedIdentifier,
kPrototypeIdentifier,
kConstructorIdentifier
kConstructorIdentifier,
kEnumIdentifier,
kAwaitIdentifier
};
explicit PreParserIdentifier(Type type) : type_(type) {}
......@@ -974,12 +983,21 @@ class PreParser : public ParserBase<PreParserTraits> {
// 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 @@ class PreParser : public ParserBase<PreParserTraits> {
// 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;
......
......@@ -1135,6 +1135,8 @@ uc32 Scanner::ScanUnicodeEscape() {
// 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 @@ uc32 Scanner::ScanUnicodeEscape() {
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 @@ uc32 Scanner::ScanUnicodeEscape() {
KEYWORD_GROUP('y') \
KEYWORD("yield", Token::YIELD)
static Token::Value KeywordOrIdentifierToken(const uint8_t* input,
int input_length, bool escaped) {
DCHECK(input_length >= 1);
......
......@@ -148,10 +148,12 @@ namespace internal {
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 @@ namespace internal {
T(TEMPLATE_SPAN, NULL, 0) \
T(TEMPLATE_TAIL, NULL, 0)
class Token {
public:
// All token values.
......@@ -197,7 +198,7 @@ class Token {
}
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 @@ class Token {
return is_sloppy(language_mode);
case YIELD:
return !is_generator && is_sloppy(language_mode);
case AWAIT:
return !is_module;
default:
return false;
}
......
......@@ -1542,11 +1542,10 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
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 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
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 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
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 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
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 @@ void TestParserSyncWithFlags(i::Handle<i::String> source,
"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 @@ TEST(BasicImportExportParsing) {
"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 @@ TEST(ImportExportParsingErrors) {
"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(ModuleTopLevelFunctionDecl) {
}
}
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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment