Commit d8bccfe9 authored by conradw's avatar conradw Committed by Commit bot

[strong] Implement static restrictions on switch statement

Implements the strong mode proposal's restrictions on the syntax of the
switch statement. Also fixes a minor bug with empty statements in strong
mode and improves StrongUndefinedArrow parser synch tests.

BUG=v8:3956
LOG=N

Review URL: https://codereview.chromium.org/1084983002

Cr-Commit-Position: refs/heads/master@{#27885}
parent 71a19439
......@@ -161,6 +161,7 @@ var kMessages = {
strong_arguments: ["In strong mode, 'arguments' is deprecated, use '...args' instead"],
strong_undefined: ["In strong mode, binding or assigning to 'undefined' is deprecated"],
strong_direct_eval: ["In strong mode, direct calls to eval are deprecated"],
strong_switch_fallthrough : ["In strong mode, switch fall-through is deprecated, terminate each case with 'break', 'continue', 'return' or 'throw'"],
strong_equal: ["In strong mode, '==' and '!=' are deprecated, use '===' and '!==' instead"],
strong_delete: ["In strong mode, 'delete' is deprecated, use maps or sets instead"],
strong_var: ["In strong mode, 'var' is deprecated, use 'let' or 'const' instead"],
......
......@@ -1821,13 +1821,23 @@ Statement* Parser::ParseSubStatement(ZoneList<const AstRawString*>* labels,
return ParseForStatement(labels, ok);
case Token::CONTINUE:
return ParseContinueStatement(ok);
case Token::BREAK:
return ParseBreakStatement(labels, ok);
case Token::RETURN:
return ParseReturnStatement(ok);
case Token::THROW:
case Token::TRY: {
// These statements must have their labels preserved in an enclosing
// block
if (labels == NULL) {
return ParseStatementAsUnlabelled(labels, ok);
} else {
Block* result =
factory()->NewBlock(labels, 1, false, RelocInfo::kNoPosition);
Target target(&this->target_stack_, result);
Statement* statement = ParseStatementAsUnlabelled(labels, CHECK_OK);
if (result) result->AddStatement(statement, zone());
return result;
}
}
case Token::WITH:
return ParseWithStatement(labels, ok);
......@@ -1835,23 +1845,6 @@ Statement* Parser::ParseSubStatement(ZoneList<const AstRawString*>* labels,
case Token::SWITCH:
return ParseSwitchStatement(labels, ok);
case Token::THROW:
return ParseThrowStatement(ok);
case Token::TRY: {
// NOTE: It is somewhat complicated to have labels on
// try-statements. When breaking out of a try-finally statement,
// one must take great care not to treat it as a
// fall-through. It is much easier just to wrap the entire
// try-statement in a statement block and put the labels there
Block* result =
factory()->NewBlock(labels, 1, false, RelocInfo::kNoPosition);
Target target(&this->target_stack_, result);
TryStatement* statement = ParseTryStatement(CHECK_OK);
if (result) result->AddStatement(statement, zone());
return result;
}
case Token::FUNCTION: {
// FunctionDeclaration is only allowed in the context of SourceElements
// (Ecma 262 5th Edition, clause 14):
......@@ -1892,6 +1885,30 @@ Statement* Parser::ParseSubStatement(ZoneList<const AstRawString*>* labels,
}
}
Statement* Parser::ParseStatementAsUnlabelled(
ZoneList<const AstRawString*>* labels, bool* ok) {
switch (peek()) {
case Token::CONTINUE:
return ParseContinueStatement(ok);
case Token::BREAK:
return ParseBreakStatement(labels, ok);
case Token::RETURN:
return ParseReturnStatement(ok);
case Token::THROW:
return ParseThrowStatement(ok);
case Token::TRY:
return ParseTryStatement(ok);
default:
UNREACHABLE();
return NULL;
}
}
VariableProxy* Parser::NewUnresolved(const AstRawString* name,
VariableMode mode) {
......@@ -2847,13 +2864,19 @@ CaseClause* Parser::ParseCaseClause(bool* default_seen_ptr, bool* ok) {
int pos = position();
ZoneList<Statement*>* statements =
new(zone()) ZoneList<Statement*>(5, zone());
Statement* stat = NULL;
while (peek() != Token::CASE &&
peek() != Token::DEFAULT &&
peek() != Token::RBRACE) {
Statement* stat = ParseStatementListItem(CHECK_OK);
stat = ParseStatementListItem(CHECK_OK);
statements->Add(stat, zone());
}
if (is_strong(language_mode()) && stat != NULL && !stat->IsJump() &&
peek() != Token::RBRACE) {
ReportMessageAt(scanner()->location(), "strong_switch_fallthrough");
*ok = false;
return NULL;
}
return factory()->NewCaseClause(label, statements, pos);
}
......@@ -4470,7 +4493,6 @@ void Parser::CheckConflictingVarDeclarations(Scope* scope, bool* ok) {
// ----------------------------------------------------------------------------
// Parser support
bool Parser::TargetStackContainsLabel(const AstRawString* label) {
for (Target* t = target_stack_; t != NULL; t = t->previous()) {
if (ContainsLabel(t->statement()->labels(), label)) return true;
......
......@@ -924,6 +924,8 @@ class Parser : public ParserBase<ParserTraits> {
ZoneList<ImportDeclaration*>* ParseNamedImports(int pos, bool* ok);
Statement* ParseStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseSubStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseStatementAsUnlabelled(ZoneList<const AstRawString*>* labels,
bool* ok);
Statement* ParseFunctionDeclaration(ZoneList<const AstRawString*>* names,
bool* ok);
Statement* ParseClassDeclaration(ZoneList<const AstRawString*>* names,
......
......@@ -235,6 +235,11 @@ PreParser::Statement PreParser::ParseStatement(bool* ok) {
// Statement ::
// EmptyStatement
// ...
if (peek() == Token::SEMICOLON) {
Next();
return Statement::Default();
}
return ParseSubStatement(ok);
}
......@@ -395,15 +400,16 @@ PreParser::Statement PreParser::ParseBlock(bool* ok) {
// (ECMA-262, 3rd, 12.2)
//
Expect(Token::LBRACE, CHECK_OK);
Statement final = Statement::Default();
while (peek() != Token::RBRACE) {
if (is_strict(language_mode())) {
ParseStatementListItem(CHECK_OK);
final = ParseStatementListItem(CHECK_OK);
} else {
ParseStatement(CHECK_OK);
final = ParseStatement(CHECK_OK);
}
}
Expect(Token::RBRACE, ok);
return Statement::Default();
return final;
}
......@@ -545,7 +551,8 @@ PreParser::Statement PreParser::ParseExpressionOrLabelledStatement(bool* ok) {
DCHECK(is_sloppy(language_mode()) ||
!IsFutureStrictReserved(expr.AsIdentifier()));
Consume(Token::COLON);
return ParseStatement(ok);
Statement statement = ParseStatement(ok);
return statement.IsJumpStatement() ? Statement::Default() : statement;
// Preparsing is disabled for extensions (because the extension details
// aren't passed to lazily compiled functions), so we don't
// accept "native function" in the preparser.
......@@ -571,12 +578,16 @@ PreParser::Statement PreParser::ParseIfStatement(bool* ok) {
Expect(Token::LPAREN, CHECK_OK);
ParseExpression(true, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
ParseSubStatement(CHECK_OK);
Statement stat = ParseSubStatement(CHECK_OK);
if (peek() == Token::ELSE) {
Next();
ParseSubStatement(CHECK_OK);
Statement else_stat = ParseSubStatement(CHECK_OK);
stat = (stat.IsJumpStatement() && else_stat.IsJumpStatement()) ?
Statement::Jump() : Statement::Default();
} else {
stat = Statement::Default();
}
return Statement::Default();
return stat;
}
......@@ -594,7 +605,7 @@ PreParser::Statement PreParser::ParseContinueStatement(bool* ok) {
ParseIdentifier(kAllowRestrictedIdentifiers, CHECK_OK);
}
ExpectSemicolon(CHECK_OK);
return Statement::Default();
return Statement::Jump();
}
......@@ -612,7 +623,7 @@ PreParser::Statement PreParser::ParseBreakStatement(bool* ok) {
ParseIdentifier(kAllowRestrictedIdentifiers, CHECK_OK);
}
ExpectSemicolon(CHECK_OK);
return Statement::Default();
return Statement::Jump();
}
......@@ -647,7 +658,7 @@ PreParser::Statement PreParser::ParseReturnStatement(bool* ok) {
ParseExpression(true, CHECK_OK);
}
ExpectSemicolon(CHECK_OK);
return Statement::Default();
return Statement::Jump();
}
......@@ -691,12 +702,19 @@ PreParser::Statement PreParser::ParseSwitchStatement(bool* ok) {
}
Expect(Token::COLON, CHECK_OK);
token = peek();
Statement statement = Statement::Jump();
while (token != Token::CASE &&
token != Token::DEFAULT &&
token != Token::RBRACE) {
ParseStatementListItem(CHECK_OK);
statement = ParseStatementListItem(CHECK_OK);
token = peek();
}
if (is_strong(language_mode()) && !statement.IsJumpStatement() &&
token != Token::RBRACE) {
ReportMessageAt(scanner()->location(), "strong_switch_fallthrough");
*ok = false;
return Statement::Default();
}
}
Expect(Token::RBRACE, ok);
return Statement::Default();
......@@ -827,7 +845,7 @@ PreParser::Statement PreParser::ParseThrowStatement(bool* ok) {
}
ParseExpression(true, CHECK_OK);
ExpectSemicolon(ok);
return Statement::Default();
return Statement::Jump();
}
......
......@@ -1104,6 +1104,10 @@ class PreParserStatement {
return PreParserStatement(kUnknownStatement);
}
static PreParserStatement Jump() {
return PreParserStatement(kJumpStatement);
}
static PreParserStatement FunctionDeclaration() {
return PreParserStatement(kFunctionDeclaration);
}
......@@ -1139,9 +1143,14 @@ class PreParserStatement {
return code_ == kFunctionDeclaration;
}
bool IsJumpStatement() {
return code_ == kJumpStatement;
}
private:
enum Type {
kUnknownStatement,
kJumpStatement,
kStringLiteralExpressionStatement,
kUseStrictExpressionStatement,
kUseStrongExpressionStatement,
......
......@@ -5805,7 +5805,7 @@ TEST(StrongEmptySubStatements) {
const char* strict_context_data[][2] = {{"'use strict';", ""}, {NULL}};
const char* strong_context_data[][2] = {{"'use strong';", ""}, {NULL}};
const char* data[] = {
const char* data_error[] = {
"if (1);",
"if (1) {} else;",
"while (1);",
......@@ -5818,15 +5818,24 @@ TEST(StrongEmptySubStatements) {
"for (const x of []);",
NULL};
const char* data_success[] = {
"if (1) {} else {}",
"switch(1) {}",
"1+1;;",
"1+1; ;",
NULL};
static const ParserFlag always_flags[] = {
kAllowStrongMode,
};
RunParserSyncTest(sloppy_context_data, data, kSuccess, NULL, 0, always_flags,
arraysize(always_flags));
RunParserSyncTest(strict_context_data, data, kSuccess, NULL, 0, always_flags,
arraysize(always_flags));
RunParserSyncTest(strong_context_data, data, kError, NULL, 0, always_flags,
arraysize(always_flags));
RunParserSyncTest(sloppy_context_data, data_error, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strict_context_data, data_error, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strong_context_data, data_error, kError, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strong_context_data, data_success, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
}
......@@ -5975,31 +5984,93 @@ TEST(StrongUndefinedArrow) {
TEST(StrongDirectEval) {
const char* context_data[][2] = {{"", ""}, {NULL}};
const char* sloppy_context_data[][2] = {{"", ""}, {NULL}};
const char* strong_context_data[][2] = {{"'use strong';", ""}, {NULL}};
const char* error_data[] = {
"'use strong'; eval();",
"'use strong'; eval([]);",
"'use strong'; (eval)();",
"'use strong'; (((eval)))();",
"'use strong'; eval('function f() {}');",
"'use strong'; function f() {eval()}",
"eval();",
"eval([]);",
"(eval)();",
"(((eval)))();",
"eval('function f() {}');",
"function f() {eval()}",
NULL};
const char* success_data[] = {
"'use strong'; eval;",
"'use strong'; eval`foo`;",
"'use strong'; let foo = eval; foo();",
"'use strong'; (1, eval)();",
"eval;",
"eval`foo`;",
"let foo = eval; foo();",
"(1, eval)();",
NULL};
static const ParserFlag always_flags[] = {
kAllowStrongMode
};
RunParserSyncTest(context_data, error_data, kError, NULL, 0,
RunParserSyncTest(sloppy_context_data, error_data, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(context_data, success_data, kSuccess, NULL, 0,
RunParserSyncTest(strong_context_data, error_data, kError, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strong_context_data, success_data, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
}
TEST(StrongSwitchFallthrough) {
const char* sloppy_context_data[][2] = {
{"function f() { foo:for(;;) { switch(1) {", "};}}"},
{NULL, NULL}
};
const char* strong_context_data[][2] = {
{"function f() { 'use strong'; foo:for(;;) { switch(1) {", "};}}"},
{NULL, NULL}
};
const char* data_success[] = {
"",
"case 1:",
"case 1: case 2:",
"case 1: break;",
"default: throw new TypeError();",
"case 1: case 2: null",
"case 1: case 2: default: 1+1",
"case 1: break; case 2: return; default:",
"case 1: break foo; case 2: return; default:",
"case 1: case 2: break; case 3: continue; case 4: default:",
"case 1: case 2: break; case 3: continue foo; case 4: default:",
"case 1: case 2: {{return;}} case 3: default:",
"case 1: case 2: case 3: default: {1+1;{continue;}}",
"case 1: case 2: {1+1;{1+1;{continue;}}} case 3: default:",
"case 1: if (1) break; else continue; case 2: case 3: default:",
"case 1: case 2: if (1) {{break;}} else break; case 3: default:",
"case 1: if (1) break; else {if (1) break; else break;} case 2: default:",
"case 1: if (1) {if (1) break; else break;} else break; case 2: default:",
NULL};
const char* data_error[] = {
"case 1: case 2: (function(){return}); default:",
"case 1: 1+1; case 2:",
"case 1: bar: break bar; case 2: break;",
"case 1: bar:return; case 2:",
"case 1: bar:{ continue;} case 2:",
"case 1: break; case 2: bar:{ throw new TypeError() } default:",
"case 1: case 2: { bar:{ { break;} } } default: break;",
"case 1: if (1) break; else {}; case 2: default:",
"case 1: case 2: if (1) break; default:",
"case 1: case 2: if (1) break; else 0; default:",
"case 1: case 2: if (1) 0; else break; default:",
"case 1: case 2: case 3: if (1) {} default:",
"case 1: bar:if (1) break; else continue; case 2: case 3: default:",
NULL};
static const ParserFlag always_flags[] = {
kAllowStrongMode
};
RunParserSyncTest(strong_context_data, data_success, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(sloppy_context_data, data_error, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strong_context_data, data_error, kError, NULL, 0,
always_flags, arraysize(always_flags));
}
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --strong-mode
"use strict";
function CheckSwitch() {
let jumpStatements = [
"break; ",
"continue; ",
"break foo; ",
"continue foo; ",
"return; ",
"throw new TypeError(); ",
"if(1) break; else continue; ",
"if(1) {1+1; {break;}} else continue; "
]
let otherStatements = [
"null; ",
"1+1; ",
"{} ",
"for(;false;) {break;} ",
"for(;false;) {1+1; {throw new TypeError();}} ",
"(function(){return});",
"(function(){throw new TypeError();});",
"{break; 1+1;} ",
"if(1) break; ",
"if(1) break; else 1+1; ",
"if(1) 1+1; else break; ",
]
let successContexts = [
["switch(1) {case 1: ", "case 2: }"],
["switch(1) {case 1: case 2: ", "default: }"],
["switch(1) {case 1: case 2: ", "default: {}}"],
["switch(1) {case 1: case 2: ", "default: 1+1}"],
["switch(1) {case 1: break; case 2: ", "default: }"],
["switch(1) {case 1: case 2: break; case 3: ", "case 4: default: }"],
["switch(1) {case 1: if(1) break; else {", "} default: break;}"]
]
let strongThrowContexts = [
["switch(1) {case 1: 1+1; case 2: ", "}"],
["switch(1) {case 1: bar: break foo; case 2: ", "}"],
["switch(1) {case 1: bar:", " case 2: }"],
["switch(1) {case 1: bar:{ ", "} case 2: }"],
["switch(1) {case 1: bar:{ ", "} default: break;}"],
["switch(1) {case 1: { bar:{ { ", "} } } default: break;}"],
["switch(1) {case 1: { { { ", "} 1+1;} } default: break;}"],
["switch(1) {case 1: if(1) {", "} default: break;}"],
["switch(1) {case 1: bar:if(1) break; else {", "} default: break;}"]
]
let sloppy_wrap = ["function f() { foo:for(;;) {", "}}"];
let strong_wrap = ["function f() { 'use strong'; foo:for(;;) {", "}}"];
for (let context of successContexts) {
let sloppy_prefix = sloppy_wrap[0] + context[0];
let sloppy_suffix = context[1] + sloppy_wrap[1];
let strong_prefix = strong_wrap[0] + context[0];
let strong_suffix = context[1] + strong_wrap[1];
for (let code of jumpStatements) {
assertDoesNotThrow(strong_wrap[0] + "switch(1) {case 1: " + code + "}}}");
assertDoesNotThrow(strong_prefix + code + strong_suffix);
assertDoesNotThrow(strong_prefix + "{ 1+1; " + code + "}" +
strong_suffix);
assertDoesNotThrow(strong_prefix + "{ 1+1; { 1+1; " + code + "}}" +
strong_suffix);
assertDoesNotThrow(strong_prefix + "if(1) " + code + "else break;" +
strong_suffix);
assertDoesNotThrow(strong_prefix + "if(1) " + code +
"else if (1) break; else " + code + strong_suffix);
}
for (let code of otherStatements) {
assertDoesNotThrow(sloppy_prefix + code + sloppy_suffix);
assertThrows(strong_prefix + code + strong_suffix, SyntaxError);
}
}
for (let context of strongThrowContexts) {
let sloppy_prefix = sloppy_wrap[0] + context[0];
let sloppy_suffix = context[1] + sloppy_wrap[1];
let strong_prefix = strong_wrap[0] + context[0];
let strong_suffix = context[1] + strong_wrap[1];
for (let code of jumpStatements.concat(otherStatements)) {
assertDoesNotThrow(sloppy_prefix + code + sloppy_suffix);
assertThrows(strong_prefix + code + strong_suffix, SyntaxError);
}
}
for (let code of otherStatements) {
assertDoesNotThrow("switch(1) {default: " + code + "}");
assertDoesNotThrow("switch(1) {case 1: " + code + "}");
assertDoesNotThrow("switch(1) {case 1: default: " + code + "}");
assertDoesNotThrow("switch(1) {case 1: break; default: " + code + "}");
assertDoesNotThrow("switch(1) {case 1: " + code + "break; default: }");
}
}
CheckSwitch();
assertDoesNotThrow("'use strong'; switch(1) {}");
assertDoesNotThrow("'use strong'; switch(1) {case 1:}");
assertDoesNotThrow("'use strong'; switch(1) {default:}");
assertDoesNotThrow("'use strong'; switch(1) {case 1: case 2: default:}");
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