Commit 2907c726 authored by bakkot's avatar bakkot Committed by Commit bot

Add errors for declarations which conflict with catch parameters.

Catch parameters are largely treated as lexical declarations in the
block which contains their body for the purposes of early syntax errors,
with some exceptions outlined in B.3.5. This patch introduces most of
those errors, except those from `eval('for (var e of ...);')` inside of
a catch with a simple parameter named 'e'.

Note that annex B.3.5 allows var declarations to conflict with simple
catch parameters, except when the variable declaration is the init of a
for-of statement.

BUG=v8:5112,v8:4231

Review-Url: https://codereview.chromium.org/2109733003
Cr-Commit-Position: refs/heads/master@{#37462}
parent 3ee6b808
......@@ -141,7 +141,7 @@ Scope::Scope(Zone* zone, Scope* inner_scope,
zone_(zone) {
SetDefaults(CATCH_SCOPE, NULL, Handle<ScopeInfo>::null());
AddInnerScope(inner_scope);
++num_var_or_const_;
++num_var_;
num_heap_slots_ = Context::MIN_CONTEXT_SLOTS;
Variable* variable = variables_.Declare(this,
catch_variable_name,
......@@ -185,7 +185,7 @@ void Scope::SetDefaults(ScopeType scope_type, Scope* outer_scope,
force_eager_compilation_ = false;
force_context_allocation_ = (outer_scope != NULL && !is_function_scope())
? outer_scope->has_forced_context_allocation() : false;
num_var_or_const_ = 0;
num_var_ = 0;
num_stack_slots_ = 0;
num_heap_slots_ = 0;
num_global_slots_ = 0;
......@@ -339,8 +339,7 @@ Scope* Scope::FinalizeBlockScope() {
DCHECK(temps_.is_empty());
DCHECK(params_.is_empty());
if (num_var_or_const() > 0 ||
(is_declaration_scope() && calls_sloppy_eval())) {
if (num_var() > 0 || (is_declaration_scope() && calls_sloppy_eval())) {
return this;
}
......@@ -512,7 +511,7 @@ Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
// introduced during variable allocation, and TEMPORARY variables are
// allocated via NewTemporary().
DCHECK(IsDeclaredVariableMode(mode));
++num_var_or_const_;
++num_var_;
return variables_.Declare(this, name, mode, kind, init_flag,
maybe_assigned_flag);
}
......@@ -611,6 +610,25 @@ Declaration* Scope::CheckConflictingVarDeclarations() {
return NULL;
}
Declaration* Scope::CheckLexDeclarationsConflictingWith(
ZoneList<const AstRawString*>* names) {
int length = names->length();
for (int i = 0; i < length; ++i) {
Variable* var = LookupLocal(names->at(i));
if (var != nullptr && IsLexicalVariableMode(var->mode())) {
// Conflict; find and return its declaration.
const AstRawString* name = names->at(i);
int decls_length = decls_.length();
for (int j = 0; j < decls_length; ++j) {
if (decls_[i]->proxy()->raw_name() == name) {
return decls_[i];
}
}
DCHECK(false);
}
}
return nullptr;
}
class VarAndOrder {
public:
......
......@@ -236,6 +236,14 @@ class Scope: public ZoneObject {
// scope over a let binding of the same name.
Declaration* CheckConflictingVarDeclarations();
// Check if the scope has a conflicting lexical declaration that has a name in
// the given list. This is used to catch patterns like
// `try{}catch(e){let e;}`,
// which is an error even though the two 'e's are declared in different
// scopes.
Declaration* CheckLexDeclarationsConflictingWith(
ZoneList<const AstRawString*>* names);
// ---------------------------------------------------------------------------
// Scope-specific info.
......@@ -491,6 +499,12 @@ class Scope: public ZoneObject {
// The ModuleDescriptor for this scope; only for module scopes.
ModuleDescriptor* module() const { return module_descriptor_; }
AstRawString* catch_variable_name() const {
DCHECK(is_catch_scope());
DCHECK(num_var() == 1);
return static_cast<AstRawString*>(variables_.Start()->key);
}
// ---------------------------------------------------------------------------
// Variable allocation.
......@@ -501,8 +515,8 @@ class Scope: public ZoneObject {
ZoneList<Variable*>* context_locals,
ZoneList<Variable*>* context_globals);
// Current number of var or const locals.
int num_var_or_const() { return num_var_or_const_; }
// Current number of var locals.
int num_var() const { return num_var_; }
// Result of variable allocation.
int num_stack_slots() const { return num_stack_slots_; }
......@@ -673,7 +687,7 @@ class Scope: public ZoneObject {
bool is_declaration_scope_;
// Computed as variables are declared.
int num_var_or_const_;
int num_var_;
// Computed via AllocateVariables; function, block and catch scopes only.
int num_stack_slots_;
......
......@@ -1929,11 +1929,10 @@ Statement* Parser::ParseStatementAsUnlabelled(
VariableProxy* Parser::NewUnresolved(const AstRawString* name,
VariableMode mode) {
// If we are inside a function, a declaration of a var/const variable is a
// If we are inside a function, a declaration of a 'var' variable is a
// truly local variable, and the scope of the variable is always the function
// scope.
// Let/const variables in harmony mode are always added to the immediately
// enclosing scope.
// Let/const variables are always added to the immediately enclosing scope.
Scope* scope =
IsLexicalVariableMode(mode) ? scope_ : scope_->DeclarationScope();
return scope->NewUnresolved(factory(), name, Variable::NORMAL,
......@@ -2326,11 +2325,11 @@ Block* Parser::ParseVariableStatement(VariableDeclarationContext var_context,
// VariableStatement ::
// VariableDeclarations ';'
// The scope of a var/const declared variable anywhere inside a function
// The scope of a var declared variable anywhere inside a function
// is the entire function (ECMA-262, 3rd, 10.1.3, and 12.2). Thus we can
// transform a source-level var/const declaration into a (Function)
// Scope declaration, and rewrite the source-level initialization into an
// assignment statement. We use a block to collect multiple assignments.
// transform a source-level var declaration into a (Function) Scope
// declaration, and rewrite the source-level initialization into an assignment
// statement. We use a block to collect multiple assignments.
//
// We mark the block as initializer block because we don't want the
// rewriter to add a '.result' assignment to such a block (to get compliant
......@@ -3028,6 +3027,8 @@ TryStatement* Parser::ParseTryStatement(bool* ok) {
Expect(Token::RPAREN, CHECK_OK);
ZoneList<const AstRawString*> bound_names(1, zone());
if (!is_simple) {
DeclarationDescriptor descriptor;
descriptor.declaration_kind = DeclarationDescriptor::NORMAL;
......@@ -3045,20 +3046,34 @@ TryStatement* Parser::ParseTryStatement(bool* ok) {
Block* init_block =
factory()->NewBlock(nullptr, 8, true, kNoSourcePosition);
PatternRewriter::DeclareAndInitializeVariables(
init_block, &descriptor, &decl, nullptr, CHECK_OK);
init_block, &descriptor, &decl, &bound_names, CHECK_OK);
catch_block->statements()->Add(init_block, zone());
} else {
bound_names.Add(name, zone());
}
// TODO(adamk): This should call ParseBlock in order to properly
// add an additional block scope for the catch body.
Expect(Token::LBRACE, CHECK_OK);
while (peek() != Token::RBRACE) {
Statement* stat = ParseStatementListItem(CHECK_OK);
if (stat && !stat->IsEmpty()) {
catch_block->statements()->Add(stat, zone());
Block* inner_block = ParseBlock(nullptr, CHECK_OK);
catch_block->statements()->Add(inner_block, zone());
// Check for `catch(e) { let e; }` and similar errors.
Scope* inner_block_scope = inner_block->scope();
if (inner_block_scope != nullptr) {
Declaration* decl =
inner_block_scope->CheckLexDeclarationsConflictingWith(
&bound_names);
if (decl != nullptr) {
const AstRawString* name = decl->proxy()->raw_name();
int position = decl->proxy()->position();
Scanner::Location location =
position == kNoSourcePosition
? Scanner::Location::invalid()
: Scanner::Location(position, position + 1);
ParserTraits::ReportMessageAt(
location, MessageTemplate::kVarRedeclaration, name);
*ok = false;
return nullptr;
}
}
Consume(Token::RBRACE);
}
block_scope->set_end_position(scanner()->location().end_pos);
block_scope = block_scope->FinalizeBlockScope();
......@@ -3558,7 +3573,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
bool* ok) {
int stmt_pos = peek_position();
Statement* init = NULL;
ZoneList<const AstRawString*> lexical_bindings(1, zone());
ZoneList<const AstRawString*> bound_names(1, zone());
bool bound_names_are_lexical = false;
// Create an in-between scope for let-bound iteration variables.
Scope* for_scope = NewScope(scope_, BLOCK_SCOPE);
......@@ -3609,10 +3625,12 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
}
Block* init_block = nullptr;
bound_names_are_lexical =
IsLexicalVariableMode(parsing_result.descriptor.mode);
// special case for legacy for (var/const x =.... in)
if (!IsLexicalVariableMode(parsing_result.descriptor.mode) &&
decl.pattern->IsVariableProxy() && decl.initializer != nullptr) {
// special case for legacy for (var ... = ... in ...)
if (!bound_names_are_lexical && decl.pattern->IsVariableProxy() &&
decl.initializer != nullptr) {
DCHECK(!allow_harmony_for_in());
++use_counts_[v8::Isolate::kForInInitializer];
const AstRawString* name =
......@@ -3685,11 +3703,44 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
descriptor.initialization_pos = kNoSourcePosition;
decl.initializer = factory()->NewVariableProxy(temp);
bool is_for_var_of =
mode == ForEachStatement::ITERATE &&
parsing_result.descriptor.mode == VariableMode::VAR;
PatternRewriter::DeclareAndInitializeVariables(
each_initialization_block, &descriptor, &decl,
IsLexicalVariableMode(descriptor.mode) ? &lexical_bindings
: nullptr,
bound_names_are_lexical || is_for_var_of ? &bound_names
: nullptr,
CHECK_OK);
// Annex B.3.5 prohibits the form
// `try {} catch(e) { for (var e of {}); }`
// So if we are parsing a statement like `for (var ... of ...)`
// we need to walk up the scope chain and look for catch scopes
// which have a simple binding, then compare their binding against
// all of the names declared in the init of the for-of we're
// parsing.
if (is_for_var_of) {
Scope* catch_scope = scope_;
while (catch_scope != nullptr &&
!catch_scope->is_declaration_scope()) {
if (catch_scope->is_catch_scope()) {
auto name = catch_scope->catch_variable_name();
if (name !=
ast_value_factory()
->dot_catch_string()) { // i.e. is a simple binding
if (bound_names.Contains(name)) {
ParserTraits::ReportMessageAt(
parsing_result.bindings_loc,
MessageTemplate::kVarRedeclaration, name);
*ok = false;
return nullptr;
}
}
}
catch_scope = catch_scope->outer_scope();
}
}
}
body_block->statements()->Add(each_initialization_block, zone());
......@@ -3704,18 +3755,17 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
body_block->set_scope(body_scope);
// Create a TDZ for any lexically-bound names.
if (IsLexicalVariableMode(parsing_result.descriptor.mode)) {
if (bound_names_are_lexical) {
DCHECK_NULL(init_block);
init_block =
factory()->NewBlock(nullptr, 1, false, kNoSourcePosition);
for (int i = 0; i < lexical_bindings.length(); ++i) {
for (int i = 0; i < bound_names.length(); ++i) {
// TODO(adamk): This needs to be some sort of special
// INTERNAL variable that's invisible to the debugger
// but visible to everything else.
VariableProxy* tdz_proxy =
NewUnresolved(lexical_bindings[i], LET);
VariableProxy* tdz_proxy = NewUnresolved(bound_names[i], LET);
Declaration* tdz_decl = factory()->NewVariableDeclaration(
tdz_proxy, LET, scope_, kNoSourcePosition);
Variable* tdz_var = Declare(
......@@ -3742,11 +3792,10 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
return final_loop;
}
} else {
bound_names_are_lexical =
IsLexicalVariableMode(parsing_result.descriptor.mode);
init = parsing_result.BuildInitializationBlock(
IsLexicalVariableMode(parsing_result.descriptor.mode)
? &lexical_bindings
: nullptr,
CHECK_OK);
bound_names_are_lexical ? &bound_names : nullptr, CHECK_OK);
}
} else {
int lhs_beg_pos = peek_position();
......@@ -3827,7 +3876,7 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
// If there are let bindings, then condition and the next statement of the
// for loop must be parsed in a new scope.
Scope* inner_scope = scope_;
if (lexical_bindings.length() > 0) {
if (bound_names_are_lexical && bound_names.length() > 0) {
inner_scope = NewScope(for_scope, BLOCK_SCOPE);
inner_scope->set_start_position(scanner()->location().beg_pos);
}
......@@ -3849,11 +3898,11 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
}
Statement* result = NULL;
if (lexical_bindings.length() > 0) {
if (bound_names_are_lexical && bound_names.length() > 0) {
BlockState block_state(&scope_, for_scope);
result = DesugarLexicalBindingsInForStatement(
inner_scope, parsing_result.descriptor.mode, &lexical_bindings, loop,
init, cond, next, body, CHECK_OK);
inner_scope, parsing_result.descriptor.mode, &bound_names, loop, init,
cond, next, body, CHECK_OK);
for_scope->set_end_position(scanner()->location().end_pos);
} else {
for_scope->set_end_position(scanner()->location().end_pos);
......
......@@ -164,7 +164,7 @@ void Parser::PatternRewriter::VisitVariableProxy(VariableProxy* pattern) {
Scope* declaration_scope = IsLexicalVariableMode(descriptor_->mode)
? descriptor_->scope
: descriptor_->scope->DeclarationScope();
if (declaration_scope->num_var_or_const() > kMaxNumFunctionLocals) {
if (declaration_scope->num_var() > kMaxNumFunctionLocals) {
parser_->ReportMessage(MessageTemplate::kTooManyVariables);
*ok_ = false;
return;
......
......@@ -170,8 +170,5 @@ for (var v = 0; v < varbinds.length; ++v) {
TestNoConflict('(function (x) {' + varbinds[v] + '})();');
}
// Test conflicting catch/function bindings.
TestNoConflict('try {} catch(x) {' + funbind + '}');
// Test conflicting parameter/function bindings.
TestNoConflict('(function (x) {' + funbind + '})();');
......@@ -170,8 +170,5 @@ for (var v = 0; v < varbinds.length; ++v) {
TestNoConflict('(function (x) {' + varbinds[v] + '})();');
}
// Test conflicting catch/function bindings.
TestNoConflict('try {} catch(x) {' + funbind + '}');
// Test conflicting parameter/function bindings.
TestNoConflict('(function (x) {' + funbind + '})();');
......@@ -455,6 +455,45 @@
assertEquals(4, f());
})();
// B.3.5 interacts with B.3.3 to allow this.
(function hoistingThroughSimpleCatch() {
assertEquals(undefined, f);
try {
throw 0;
} catch(f) {
{
assertEquals(4, f());
function f() {
return 4;
}
assertEquals(4, f());
}
}
assertEquals(4, f());
})();
(function noHoistingThroughComplexCatch() {
try {
throw 0;
} catch({f}) {
{
assertEquals(4, f());
function f() {
return 4;
}
assertEquals(4, f());
}
}
assertThrows(()=>f, ReferenceError);
})();
// Test that hoisting from blocks does happen in global scope
function globalHoisted() { return 0; }
{
......
// Copyright 2016 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.
function checkIsRedeclarationError(code) {
try {
eval(`
checkIsRedeclarationError : {
break checkIsRedeclarationError;
${code}
}
`);
assertUnreachable();
} catch(e) {
assertInstanceof(e, SyntaxError );
assertTrue( e.toString().indexOf("has already been declared") >= 0 );
}
}
function checkIsNotRedeclarationError(code) {
assertDoesNotThrow(()=>eval(`
checkIsNotRedeclarationError_label : {
break checkIsNotRedeclarationError_label;
${code}
}
`));
}
let lexical_e = [
'let e',
'let {e} = 0',
'let [e] = 0',
'let {f:e} = 0',
'let [[[], e]] = 0',
'const e = 0',
'const {e} = 0',
'const [e] = 0',
'const {f:e} = 0',
'const [[[], e]] = 0',
'function e(){}',
'function* e(){}',
];
let not_lexical_e = [
'var e',
'var {e} = 0',
'let {} = 0',
'let {e:f} = 0',
'{ function e(){} }'
];
// Check that lexical declarations cannot override a simple catch parameter
for (let declaration of lexical_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch(e) {
${declaration}
}
`);
}
// Check that lexical declarations cannot override a complex catch parameter
for (let declaration of lexical_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch({e}) {
${declaration}
}
`);
}
// Check that non-lexical declarations can override a simple catch parameter
for (let declaration of not_lexical_e) {
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
${declaration}
}
`);
}
// Check that the above error does not occur if a declaration scope is between
// the catch and the loop.
for (let declaration of lexical_e) {
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
(()=>{${declaration}})();
}
`);
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
(function(){${declaration}})();
}
`);
}
// Copyright 2016 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.
function checkIsRedeclarationError(code) {
try {
eval(`
checkIsRedeclarationError : {
break checkIsRedeclarationError;
${code}
}
`);
assertUnreachable();
} catch(e) {
assertInstanceof(e, SyntaxError );
assertTrue( e.toString().indexOf("has already been declared") >= 0 );
}
}
function checkIsNotRedeclarationError(code) {
assertDoesNotThrow(()=>eval(`
checkIsNotRedeclarationError_label : {
break checkIsNotRedeclarationError_label;
${code}
}
`));
}
let var_e = [
'var e',
'var {e}',
'var [e]',
'var {f:e}',
'var [[[], e]]'
];
let not_var_e = [
'var f',
'var {}',
'var {e:f}',
'e',
'{e}',
'let e',
'const e',
'let {e}',
'const {e}',
'let [e]',
'const [e]',
'let {f:e}',
'const {f:e}'
];
// Check that `for (var ... of ...)` cannot redeclare a simple catch variable
// but `for (var ... in ...)` can.
for (let binding of var_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch(e) {
for (${binding} of []);
}
`);
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
for (${binding} in []);
}
`);
}
// Check that the above error occurs even for nested catches.
for (let binding of var_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch(e) {
try {
throw 1;
} catch(f) {
try {
throw 2;
} catch({}) {
for (${binding} of []);
}
}
}
`);
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
try {
throw 1;
} catch(f) {
try {
throw 2;
} catch({}) {
for (${binding} in []);
}
}
}
`);
}
// Check that the above error does not occur if a declaration scope is between
// the catch and the loop.
for (let binding of var_e) {
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
(()=>{for (${binding} of []);})();
}
`);
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
(function(){for (${binding} of []);})();
}
`);
}
// Check that there is no error when not declaring a var named e.
for (let binding of not_var_e) {
checkIsNotRedeclarationError(`
try {
throw 0;
} catch(e) {
for (${binding} of []);
}
`);
}
// Check that there is an error for both for-in and for-of when redeclaring
// a non-simple catch parameter
for (let binding of var_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch({e}) {
for (${binding} of []);
}
`);
checkIsRedeclarationError(`
try {
throw 0;
} catch({e}) {
for (${binding} in []);
}
`);
}
// Check that the above error occurs even for nested catches.
for (let binding of var_e) {
checkIsRedeclarationError(`
try {
throw 0;
} catch({e}) {
try {
throw 1;
} catch(f) {
try {
throw 2;
} catch({}) {
for (${binding} of []);
}
}
}
`);
checkIsRedeclarationError(`
try {
throw 0;
} catch({e}) {
try {
throw 1;
} catch(f) {
try {
throw 2;
} catch({}) {
for (${binding} in []);
}
}
}
`);
}
......@@ -245,8 +245,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=4231
'language/eval-code/direct/var-env-lower-lex-catch-non-strict': [FAIL],
'language/statements/try/early-catch-lex': [FAIL],
'language/statements/try/early-catch-var': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=4951
'language/expressions/assignment/dstr-array-elem-iter-rtrn-close': [FAIL],
......@@ -331,9 +329,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=1569
'language/module-code/*': [SKIP],
# https://bugs.chromium.org/p/v8/issues/detail?id=5112
'language/statements/try/scope-catch-block-lex-open': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=5012
'intl402/Intl/getCanonicalLocales/*': [FAIL],
......
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