[strong] Declaration-after-use errors.

We cannot yet detect use-before-declaration in general, because for that we'd
need to analyze the context when compiling. But we can detect an error case
where we first see a use, then a declaration.

For this, I also added end position tracking (needed for error messages) to
VariableProxy.

Note: the position naming is completely inconsistent: start_position &
end_position, position & end_position, pos & end_pos, beg_pos & end_pos, to name
a few. This doesn't fix all of it, but tries to unify towards start_position &
end_position whenever possible w/ minimal changes.

BUG=

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

Cr-Commit-Position: refs/heads/master@{#26880}
This commit is contained in:
marja 2015-02-26 05:48:10 -08:00 committed by Commit bot
parent 77d3ae0e11
commit 1eddcf5b71
10 changed files with 352 additions and 64 deletions

View File

@ -59,24 +59,27 @@ bool Expression::IsUndefinedLiteral(Isolate* isolate) const {
} }
VariableProxy::VariableProxy(Zone* zone, Variable* var, int position) VariableProxy::VariableProxy(Zone* zone, Variable* var, int start_position,
: Expression(zone, position), int end_position)
: Expression(zone, start_position),
bit_field_(IsThisField::encode(var->is_this()) | bit_field_(IsThisField::encode(var->is_this()) |
IsAssignedField::encode(false) | IsAssignedField::encode(false) |
IsResolvedField::encode(false)), IsResolvedField::encode(false)),
variable_feedback_slot_(FeedbackVectorICSlot::Invalid()), variable_feedback_slot_(FeedbackVectorICSlot::Invalid()),
raw_name_(var->raw_name()) { raw_name_(var->raw_name()),
end_position_(end_position) {
BindTo(var); BindTo(var);
} }
VariableProxy::VariableProxy(Zone* zone, const AstRawString* name, bool is_this, VariableProxy::VariableProxy(Zone* zone, const AstRawString* name, bool is_this,
int position) int start_position, int end_position)
: Expression(zone, position), : Expression(zone, start_position),
bit_field_(IsThisField::encode(is_this) | IsAssignedField::encode(false) | bit_field_(IsThisField::encode(is_this) | IsAssignedField::encode(false) |
IsResolvedField::encode(false)), IsResolvedField::encode(false)),
variable_feedback_slot_(FeedbackVectorICSlot::Invalid()), variable_feedback_slot_(FeedbackVectorICSlot::Invalid()),
raw_name_(name) {} raw_name_(name),
end_position_(end_position) {}
void VariableProxy::BindTo(Variable* var) { void VariableProxy::BindTo(Variable* var) {

View File

@ -1631,6 +1631,8 @@ class VariableProxy FINAL : public Expression {
bit_field_ = IsResolvedField::update(bit_field_, true); bit_field_ = IsResolvedField::update(bit_field_, true);
} }
int end_position() const { return end_position_; }
// Bind this proxy to the variable var. // Bind this proxy to the variable var.
void BindTo(Variable* var); void BindTo(Variable* var);
@ -1653,10 +1655,11 @@ class VariableProxy FINAL : public Expression {
} }
protected: protected:
VariableProxy(Zone* zone, Variable* var, int position); VariableProxy(Zone* zone, Variable* var, int start_position,
int end_position);
VariableProxy(Zone* zone, const AstRawString* name, bool is_this, VariableProxy(Zone* zone, const AstRawString* name, bool is_this,
int position); int start_position, int end_position);
class IsThisField : public BitField8<bool, 0, 1> {}; class IsThisField : public BitField8<bool, 0, 1> {};
class IsAssignedField : public BitField8<bool, 1, 1> {}; class IsAssignedField : public BitField8<bool, 1, 1> {};
@ -1670,6 +1673,10 @@ class VariableProxy FINAL : public Expression {
const AstRawString* raw_name_; // if !is_resolved_ const AstRawString* raw_name_; // if !is_resolved_
Variable* var_; // if is_resolved_ Variable* var_; // if is_resolved_
}; };
// Position is stored in the AstNode superclass, but VariableProxy needs to
// know its end position too (for error messages). It cannot be inferred from
// the variable name length because it can contain escapes.
int end_position_;
}; };
@ -3353,14 +3360,16 @@ class AstNodeFactory FINAL BASE_EMBEDDED {
} }
VariableProxy* NewVariableProxy(Variable* var, VariableProxy* NewVariableProxy(Variable* var,
int pos = RelocInfo::kNoPosition) { int start_position = RelocInfo::kNoPosition,
return new (zone_) VariableProxy(zone_, var, pos); int end_position = RelocInfo::kNoPosition) {
return new (zone_) VariableProxy(zone_, var, start_position, end_position);
} }
VariableProxy* NewVariableProxy(const AstRawString* name, VariableProxy* NewVariableProxy(const AstRawString* name, bool is_this,
bool is_this, int start_position = RelocInfo::kNoPosition,
int position = RelocInfo::kNoPosition) { int end_position = RelocInfo::kNoPosition) {
return new (zone_) VariableProxy(zone_, name, is_this, position); return new (zone_)
VariableProxy(zone_, name, is_this, start_position, end_position);
} }
Property* NewProperty(Expression* obj, Expression* key, int pos) { Property* NewProperty(Expression* obj, Expression* key, int pos) {

View File

@ -168,6 +168,7 @@ var kMessages = {
strong_var: ["Please don't use 'var' in strong mode, use 'let' or 'const' instead"], strong_var: ["Please don't use 'var' in strong mode, use 'let' or 'const' instead"],
strong_for_in: ["Please don't use 'for'-'in' loops in strong mode, use 'for'-'of' instead"], strong_for_in: ["Please don't use 'for'-'in' loops in strong mode, use 'for'-'of' instead"],
strong_empty: ["Please don't use empty sub-statements in strong mode, make them explicit with '{}' instead"], strong_empty: ["Please don't use empty sub-statements in strong mode, make them explicit with '{}' instead"],
strong_use_before_declaration: ["Please declare variable '", "%0", "' before use in strong mode"],
sloppy_lexical: ["Block-scoped declarations (let, const, function, class) not yet supported outside strict mode"], sloppy_lexical: ["Block-scoped declarations (let, const, function, class) not yet supported outside strict mode"],
malformed_arrow_function_parameter_list: ["Malformed arrow function parameter list"], malformed_arrow_function_parameter_list: ["Malformed arrow function parameter list"],
generator_poison_pill: ["'caller' and 'arguments' properties may not be accessed on generator functions."], generator_poison_pill: ["'caller' and 'arguments' properties may not be accessed on generator functions."],

View File

@ -706,7 +706,9 @@ Literal* ParserTraits::ExpressionFromLiteral(Token::Value token, int pos,
Expression* ParserTraits::ExpressionFromIdentifier(const AstRawString* name, Expression* ParserTraits::ExpressionFromIdentifier(const AstRawString* name,
int pos, Scope* scope, int start_position,
int end_position,
Scope* scope,
AstNodeFactory* factory) { AstNodeFactory* factory) {
if (parser_->fni_ != NULL) parser_->fni_->PushVariableName(name); if (parser_->fni_ != NULL) parser_->fni_->PushVariableName(name);
@ -715,8 +717,10 @@ Expression* ParserTraits::ExpressionFromIdentifier(const AstRawString* name,
// for Traits::DeclareArrowParametersFromExpression() to be able to // for Traits::DeclareArrowParametersFromExpression() to be able to
// pick the names of the parameters. // pick the names of the parameters.
return parser_->parsing_lazy_arrow_parameters_ return parser_->parsing_lazy_arrow_parameters_
? factory->NewVariableProxy(name, false, pos) ? factory->NewVariableProxy(name, false, start_position,
: scope->NewUnresolved(factory, name, pos); end_position)
: scope->NewUnresolved(factory, name, start_position,
end_position);
} }
@ -1776,11 +1780,13 @@ VariableProxy* Parser::NewUnresolved(const AstRawString* name,
// scope. // scope.
// Let/const variables in harmony mode are always added to the immediately // Let/const variables in harmony mode are always added to the immediately
// enclosing scope. // enclosing scope.
return DeclarationScope(mode)->NewUnresolved(factory(), name, position()); return DeclarationScope(mode)->NewUnresolved(factory(), name,
scanner()->location().beg_pos,
scanner()->location().end_pos);
} }
void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) { Variable* Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
VariableProxy* proxy = declaration->proxy(); VariableProxy* proxy = declaration->proxy();
DCHECK(proxy->raw_name() != NULL); DCHECK(proxy->raw_name() != NULL);
const AstRawString* name = proxy->raw_name(); const AstRawString* name = proxy->raw_name();
@ -1806,10 +1812,14 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
if (var == NULL) { if (var == NULL) {
// Declare the name. // Declare the name.
var = declaration_scope->DeclareLocal( var = declaration_scope->DeclareLocal(
name, mode, declaration->initialization(), kNotAssigned); name, mode, declaration->initialization(),
} else if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(var->mode()) declaration->IsFunctionDeclaration() ? Variable::FUNCTION
|| ((mode == CONST_LEGACY || var->mode() == CONST_LEGACY) && : Variable::NORMAL,
!declaration_scope->is_script_scope())) { kNotAssigned);
} else if (IsLexicalVariableMode(mode) ||
IsLexicalVariableMode(var->mode()) ||
((mode == CONST_LEGACY || var->mode() == CONST_LEGACY) &&
!declaration_scope->is_script_scope())) {
// The name was declared in this scope before; check for conflicting // The name was declared in this scope before; check for conflicting
// re-declarations. We have a conflict if either of the declarations is // re-declarations. We have a conflict if either of the declarations is
// not a var (in script scope, we also have to ignore legacy const for // not a var (in script scope, we also have to ignore legacy const for
@ -1829,7 +1839,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
// ES5 16 for a definition of early errors. // ES5 16 for a definition of early errors.
ParserTraits::ReportMessage("var_redeclaration", name); ParserTraits::ReportMessage("var_redeclaration", name);
*ok = false; *ok = false;
return; return nullptr;
} }
Expression* expression = NewThrowTypeError( Expression* expression = NewThrowTypeError(
"var_redeclaration", name, declaration->position()); "var_redeclaration", name, declaration->position());
@ -1903,6 +1913,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
if (resolve && var != NULL) { if (resolve && var != NULL) {
proxy->BindTo(var); proxy->BindTo(var);
} }
return var;
} }
@ -2023,7 +2034,7 @@ Statement* Parser::ParseClassDeclaration(ZoneList<const AstRawString*>* names,
Declaration* declaration = Declaration* declaration =
factory()->NewVariableDeclaration(proxy, mode, scope_, pos); factory()->NewVariableDeclaration(proxy, mode, scope_, pos);
Declare(declaration, true, CHECK_OK); Declare(declaration, true, CHECK_OK);
proxy->var()->set_initializer_position(pos); proxy->var()->set_initializer_position(position());
Token::Value init_op = Token::Value init_op =
is_strong(language_mode()) ? Token::INIT_CONST : Token::INIT_LET; is_strong(language_mode()) ? Token::INIT_CONST : Token::INIT_LET;
@ -2232,7 +2243,9 @@ Block* Parser::ParseVariableDeclarations(
VariableProxy* proxy = NewUnresolved(name, mode); VariableProxy* proxy = NewUnresolved(name, mode);
Declaration* declaration = Declaration* declaration =
factory()->NewVariableDeclaration(proxy, mode, scope_, pos); factory()->NewVariableDeclaration(proxy, mode, scope_, pos);
Declare(declaration, mode != VAR, CHECK_OK); Variable* var = Declare(declaration, mode != VAR, CHECK_OK);
DCHECK_NOT_NULL(var);
DCHECK(!proxy->is_resolved() || proxy->var() == var);
nvars++; nvars++;
if (declaration_scope->num_var_or_const() > kMaxNumFunctionLocals) { if (declaration_scope->num_var_or_const() > kMaxNumFunctionLocals) {
ReportMessage("too_many_variables"); ReportMessage("too_many_variables");
@ -2286,11 +2299,11 @@ Block* Parser::ParseVariableDeclarations(
fni_->RemoveLastFunction(); fni_->RemoveLastFunction();
} }
if (decl_props != NULL) *decl_props = kHasInitializers; if (decl_props != NULL) *decl_props = kHasInitializers;
} // End position of the initializer is after the assignment expression.
var->set_initializer_position(scanner()->location().end_pos);
// Record the end position of the initializer. } else {
if (proxy->is_resolved()) { // End position of the initializer is after the variable.
proxy->var()->set_initializer_position(position()); var->set_initializer_position(position());
} }
// Make sure that 'const x' and 'let x' initialize 'x' to undefined. // Make sure that 'const x' and 'let x' initialize 'x' to undefined.
@ -2795,7 +2808,8 @@ TryStatement* Parser::ParseTryStatement(bool* ok) {
Expect(Token::RPAREN, CHECK_OK); Expect(Token::RPAREN, CHECK_OK);
catch_variable = catch_scope->DeclareLocal(name, VAR, kCreatedInitialized); catch_variable = catch_scope->DeclareLocal(name, VAR, kCreatedInitialized,
Variable::NORMAL);
BlockState block_state(&scope_, catch_scope); BlockState block_state(&scope_, catch_scope);
catch_block = ParseBlock(NULL, CHECK_OK); catch_block = ParseBlock(NULL, CHECK_OK);
@ -3087,16 +3101,16 @@ Statement* Parser::DesugarLetBindingsInForStatement(
// make statement: let x = temp_x. // make statement: let x = temp_x.
for (int i = 0; i < names->length(); i++) { for (int i = 0; i < names->length(); i++) {
VariableProxy* proxy = NewUnresolved(names->at(i), LET); VariableProxy* proxy = NewUnresolved(names->at(i), LET);
Declaration* declaration = Declaration* declaration = factory()->NewVariableDeclaration(
factory()->NewVariableDeclaration(proxy, LET, scope_, pos); proxy, LET, scope_, RelocInfo::kNoPosition);
Declare(declaration, true, CHECK_OK); Declare(declaration, true, CHECK_OK);
inner_vars.Add(declaration->proxy()->var(), zone()); inner_vars.Add(declaration->proxy()->var(), zone());
VariableProxy* temp_proxy = factory()->NewVariableProxy(temps.at(i)); VariableProxy* temp_proxy = factory()->NewVariableProxy(temps.at(i));
Assignment* assignment = factory()->NewAssignment( Assignment* assignment = factory()->NewAssignment(
Token::INIT_LET, proxy, temp_proxy, pos); Token::INIT_LET, proxy, temp_proxy, pos);
Statement* assignment_statement = factory()->NewExpressionStatement( Statement* assignment_statement =
assignment, pos); factory()->NewExpressionStatement(assignment, RelocInfo::kNoPosition);
proxy->var()->set_initializer_position(pos); proxy->var()->set_initializer_position(init->position());
inner_block->AddStatement(assignment_statement, zone()); inner_block->AddStatement(assignment_statement, zone());
} }
@ -3244,7 +3258,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
CHECK_OK); CHECK_OK);
bool accept_OF = decl_props == kHasNoInitializers; bool accept_OF = decl_props == kHasNoInitializers;
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
int each_pos = position(); int each_beg_pos = scanner()->location().beg_pos;
int each_end_pos = scanner()->location().end_pos;
if (name != NULL && CheckInOrOf(accept_OF, &mode, ok)) { if (name != NULL && CheckInOrOf(accept_OF, &mode, ok)) {
if (!*ok) return nullptr; if (!*ok) return nullptr;
@ -3255,7 +3270,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
Expression* enumerable = ParseExpression(true, CHECK_OK); Expression* enumerable = ParseExpression(true, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK); Expect(Token::RPAREN, CHECK_OK);
VariableProxy* each = scope_->NewUnresolved(factory(), name, each_pos); VariableProxy* each =
scope_->NewUnresolved(factory(), name, each_beg_pos, each_end_pos);
Statement* body = ParseSubStatement(NULL, CHECK_OK); Statement* body = ParseSubStatement(NULL, CHECK_OK);
InitializeForEachStatement(loop, each, enumerable, body); InitializeForEachStatement(loop, each, enumerable, body);
Block* result = Block* result =
@ -3282,7 +3298,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
bool accept_IN = name != NULL && decl_props != kHasInitializers; bool accept_IN = name != NULL && decl_props != kHasInitializers;
bool accept_OF = decl_props == kHasNoInitializers; bool accept_OF = decl_props == kHasNoInitializers;
ForEachStatement::VisitMode mode; ForEachStatement::VisitMode mode;
int each_pos = position(); int each_beg_pos = scanner()->location().beg_pos;
int each_end_pos = scanner()->location().end_pos;
if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) { if (accept_IN && CheckInOrOf(accept_OF, &mode, ok)) {
if (!*ok) return nullptr; if (!*ok) return nullptr;
@ -3304,7 +3321,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
// implementing stack allocated block scoped variables. // implementing stack allocated block scoped variables.
Variable* temp = scope_->DeclarationScope()->NewTemporary( Variable* temp = scope_->DeclarationScope()->NewTemporary(
ast_value_factory()->dot_for_string()); ast_value_factory()->dot_for_string());
VariableProxy* temp_proxy = factory()->NewVariableProxy(temp, each_pos); VariableProxy* temp_proxy =
factory()->NewVariableProxy(temp, each_beg_pos, each_end_pos);
ForEachStatement* loop = ForEachStatement* loop =
factory()->NewForEachStatement(mode, labels, stmt_pos); factory()->NewForEachStatement(mode, labels, stmt_pos);
Target target(&this->target_stack_, loop); Target target(&this->target_stack_, loop);
@ -3315,7 +3333,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
scope_ = for_scope; scope_ = for_scope;
Expect(Token::RPAREN, CHECK_OK); Expect(Token::RPAREN, CHECK_OK);
VariableProxy* each = scope_->NewUnresolved(factory(), name, each_pos); VariableProxy* each =
scope_->NewUnresolved(factory(), name, each_beg_pos, each_end_pos);
Statement* body = ParseSubStatement(NULL, CHECK_OK); Statement* body = ParseSubStatement(NULL, CHECK_OK);
Block* body_block = Block* body_block =
factory()->NewBlock(NULL, 3, false, RelocInfo::kNoPosition); factory()->NewBlock(NULL, 3, false, RelocInfo::kNoPosition);
@ -4099,6 +4118,8 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name,
bool has_seen_constructor = false; bool has_seen_constructor = false;
Expect(Token::LBRACE, CHECK_OK); Expect(Token::LBRACE, CHECK_OK);
int body_beg_pos = scanner()->location().beg_pos;
const bool has_extends = extends != nullptr; const bool has_extends = extends != nullptr;
while (peek() != Token::RBRACE) { while (peek() != Token::RBRACE) {
if (Check(Token::SEMICOLON)) continue; if (Check(Token::SEMICOLON)) continue;
@ -4138,7 +4159,7 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name,
if (name != NULL) { if (name != NULL) {
DCHECK_NOT_NULL(proxy); DCHECK_NOT_NULL(proxy);
DCHECK_NOT_NULL(block_scope); DCHECK_NOT_NULL(block_scope);
proxy->var()->set_initializer_position(end_pos); proxy->var()->set_initializer_position(body_beg_pos);
} }
return factory()->NewClassLiteral(name, block_scope, proxy, extends, return factory()->NewClassLiteral(name, block_scope, proxy, extends,

View File

@ -542,7 +542,8 @@ class ParserTraits {
int end_pos); int end_pos);
Literal* ExpressionFromLiteral(Token::Value token, int pos, Scanner* scanner, Literal* ExpressionFromLiteral(Token::Value token, int pos, Scanner* scanner,
AstNodeFactory* factory); AstNodeFactory* factory);
Expression* ExpressionFromIdentifier(const AstRawString* name, int pos, Expression* ExpressionFromIdentifier(const AstRawString* name,
int start_position, int end_position,
Scope* scope, AstNodeFactory* factory); Scope* scope, AstNodeFactory* factory);
Expression* ExpressionFromString(int pos, Scanner* scanner, Expression* ExpressionFromString(int pos, Scanner* scanner,
AstNodeFactory* factory); AstNodeFactory* factory);
@ -799,7 +800,7 @@ class Parser : public ParserBase<ParserTraits> {
// Parser support // Parser support
VariableProxy* NewUnresolved(const AstRawString* name, VariableMode mode); VariableProxy* NewUnresolved(const AstRawString* name, VariableMode mode);
void Declare(Declaration* declaration, bool resolve, bool* ok); Variable* Declare(Declaration* declaration, bool resolve, bool* ok);
bool TargetStackContainsLabel(const AstRawString* label); bool TargetStackContainsLabel(const AstRawString* label);
BreakableStatement* LookupBreakTarget(const AstRawString* label, bool* ok); BreakableStatement* LookupBreakTarget(const AstRawString* label, bool* ok);

View File

@ -1408,8 +1408,8 @@ class PreParserTraits {
} }
static PreParserExpression ExpressionFromIdentifier( static PreParserExpression ExpressionFromIdentifier(
PreParserIdentifier name, int pos, Scope* scope, PreParserIdentifier name, int start_position, int end_position,
PreParserFactory* factory) { Scope* scope, PreParserFactory* factory) {
return PreParserExpression::FromIdentifier(name); return PreParserExpression::FromIdentifier(name);
} }
@ -1859,14 +1859,15 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
// '(' Expression ')' // '(' Expression ')'
// TemplateLiteral // TemplateLiteral
int pos = peek_position(); int beg_pos = scanner()->peek_location().beg_pos;
int end_pos = scanner()->peek_location().end_pos;
ExpressionT result = this->EmptyExpression(); ExpressionT result = this->EmptyExpression();
Token::Value token = peek(); Token::Value token = peek();
switch (token) { switch (token) {
case Token::THIS: { case Token::THIS: {
Consume(Token::THIS); Consume(Token::THIS);
scope_->RecordThisUsage(); scope_->RecordThisUsage();
result = this->ThisExpression(scope_, factory(), pos); result = this->ThisExpression(scope_, factory(), beg_pos);
break; break;
} }
@ -1875,7 +1876,8 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
case Token::FALSE_LITERAL: case Token::FALSE_LITERAL:
case Token::NUMBER: case Token::NUMBER:
Next(); Next();
result = this->ExpressionFromLiteral(token, pos, scanner(), factory()); result =
this->ExpressionFromLiteral(token, beg_pos, scanner(), factory());
break; break;
case Token::IDENTIFIER: case Token::IDENTIFIER:
@ -1885,13 +1887,14 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
case Token::FUTURE_STRICT_RESERVED_WORD: { case Token::FUTURE_STRICT_RESERVED_WORD: {
// Using eval or arguments in this context is OK even in strict mode. // Using eval or arguments in this context is OK even in strict mode.
IdentifierT name = ParseIdentifier(kAllowEvalOrArguments, CHECK_OK); IdentifierT name = ParseIdentifier(kAllowEvalOrArguments, CHECK_OK);
result = this->ExpressionFromIdentifier(name, pos, scope_, factory()); result = this->ExpressionFromIdentifier(name, beg_pos, end_pos, scope_,
factory());
break; break;
} }
case Token::STRING: { case Token::STRING: {
Consume(Token::STRING); Consume(Token::STRING);
result = this->ExpressionFromString(pos, scanner(), factory()); result = this->ExpressionFromString(beg_pos, scanner(), factory());
break; break;
} }
@ -1918,7 +1921,7 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
// for which an empty parameter list "()" is valid input. // for which an empty parameter list "()" is valid input.
Consume(Token::RPAREN); Consume(Token::RPAREN);
result = this->ParseArrowFunctionLiteral( result = this->ParseArrowFunctionLiteral(
pos, this->EmptyArrowParamList(), CHECK_OK); beg_pos, this->EmptyArrowParamList(), CHECK_OK);
} else { } else {
// Heuristically try to detect immediately called functions before // Heuristically try to detect immediately called functions before
// seeing the call parentheses. // seeing the call parentheses.
@ -1953,8 +1956,8 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
case Token::TEMPLATE_SPAN: case Token::TEMPLATE_SPAN:
case Token::TEMPLATE_TAIL: case Token::TEMPLATE_TAIL:
result = result = this->ParseTemplateLiteral(Traits::NoTemplateTag(), beg_pos,
this->ParseTemplateLiteral(Traits::NoTemplateTag(), pos, CHECK_OK); CHECK_OK);
break; break;
case Token::MOD: case Token::MOD:
@ -2100,7 +2103,8 @@ ParserBase<Traits>::ParsePropertyDefinition(ObjectLiteralCheckerBase* checker,
bool is_generator = allow_harmony_object_literals_ && Check(Token::MUL); bool is_generator = allow_harmony_object_literals_ && Check(Token::MUL);
Token::Value name_token = peek(); Token::Value name_token = peek();
int next_pos = peek_position(); int next_beg_pos = scanner()->peek_location().beg_pos;
int next_end_pos = scanner()->peek_location().end_pos;
ExpressionT name_expression = ParsePropertyName( ExpressionT name_expression = ParsePropertyName(
&name, &is_get, &is_set, &name_is_static, is_computed_name, &name, &is_get, &is_set, &name_is_static, is_computed_name,
CHECK_OK_CUSTOM(EmptyObjectLiteralProperty)); CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
@ -2195,7 +2199,8 @@ ParserBase<Traits>::ParsePropertyDefinition(ObjectLiteralCheckerBase* checker,
this->is_generator())) { this->is_generator())) {
DCHECK(!*is_computed_name); DCHECK(!*is_computed_name);
DCHECK(!is_static); DCHECK(!is_static);
value = this->ExpressionFromIdentifier(name, next_pos, scope_, factory()); value = this->ExpressionFromIdentifier(name, next_beg_pos, next_end_pos,
scope_, factory());
return factory()->NewObjectLiteralProperty( return factory()->NewObjectLiteralProperty(
name_expression, value, ObjectLiteralProperty::COMPUTED, false, false); name_expression, value, ObjectLiteralProperty::COMPUTED, false, false);

View File

@ -263,7 +263,12 @@ bool Scope::Analyze(CompilationInfo* info) {
// Allocate the variables. // Allocate the variables.
{ {
AstNodeFactory ast_node_factory(info->ast_value_factory()); AstNodeFactory ast_node_factory(info->ast_value_factory());
if (!top->AllocateVariables(info, &ast_node_factory)) return false; if (!top->AllocateVariables(info, &ast_node_factory)) {
DCHECK(top->pending_error_handler_.has_pending_error());
top->pending_error_handler_.ThrowPendingError(info->isolate(),
info->script());
return false;
}
} }
#ifdef DEBUG #ifdef DEBUG
@ -461,7 +466,7 @@ Variable* Scope::DeclareParameter(const AstRawString* name, VariableMode mode,
Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode, Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
InitializationFlag init_flag, InitializationFlag init_flag, Variable::Kind kind,
MaybeAssignedFlag maybe_assigned_flag) { MaybeAssignedFlag maybe_assigned_flag) {
DCHECK(!already_resolved()); DCHECK(!already_resolved());
// This function handles VAR, LET, and CONST modes. DYNAMIC variables are // This function handles VAR, LET, and CONST modes. DYNAMIC variables are
@ -469,7 +474,7 @@ Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
// explicitly, and TEMPORARY variables are allocated via NewTemporary(). // explicitly, and TEMPORARY variables are allocated via NewTemporary().
DCHECK(IsDeclaredVariableMode(mode)); DCHECK(IsDeclaredVariableMode(mode));
++num_var_or_const_; ++num_var_or_const_;
return variables_.Declare(this, name, mode, true, Variable::NORMAL, init_flag, return variables_.Declare(this, name, mode, true, kind, init_flag,
maybe_assigned_flag); maybe_assigned_flag);
} }
@ -768,6 +773,20 @@ void Scope::GetNestedScopeChain(Isolate* isolate,
} }
void Scope::ReportMessage(int start_position, int end_position,
const char* message, const AstRawString* arg) {
// Propagate the error to the topmost scope targeted by this scope analysis
// phase.
Scope* top = this;
while (!top->is_script_scope() && !top->outer_scope()->already_resolved()) {
top = top->outer_scope();
}
top->pending_error_handler_.ReportMessageAt(start_position, end_position,
message, arg, kReferenceError);
}
#ifdef DEBUG #ifdef DEBUG
static const char* Header(ScopeType scope_type) { static const char* Header(ScopeType scope_type) {
switch (scope_type) { switch (scope_type) {
@ -1050,6 +1069,31 @@ bool Scope::ResolveVariable(CompilationInfo* info, VariableProxy* proxy,
switch (binding_kind) { switch (binding_kind) {
case BOUND: case BOUND:
// We found a variable binding. // We found a variable binding.
if (is_strong(language_mode())) {
// Check for declaration-after use (for variables) in strong mode. Note
// that we can only do this in the case where we have seen the
// declaration. And we always allow referencing functions (for now).
// If both the use and the declaration are inside an eval scope
// (possibly indirectly), or one of them is, we need to check whether
// they are inside the same eval scope or different
// ones.
// TODO(marja,rossberg): Detect errors across different evals (depends
// on the future of eval in strong mode).
const Scope* eval_for_use = NearestOuterEvalScope();
const Scope* eval_for_declaration =
var->scope()->NearestOuterEvalScope();
if (proxy->position() != RelocInfo::kNoPosition &&
proxy->position() < var->initializer_position() &&
!var->is_function() && eval_for_use == eval_for_declaration) {
DCHECK(proxy->end_position() != RelocInfo::kNoPosition);
ReportMessage(proxy->position(), proxy->end_position(),
"strong_use_before_declaration", proxy->raw_name());
return false;
}
}
break; break;
case BOUND_EVAL_SHADOWED: case BOUND_EVAL_SHADOWED:

View File

@ -6,6 +6,7 @@
#define V8_SCOPES_H_ #define V8_SCOPES_H_
#include "src/ast.h" #include "src/ast.h"
#include "src/pending-compilation-error-handler.h"
#include "src/zone.h" #include "src/zone.h"
namespace v8 { namespace v8 {
@ -130,7 +131,7 @@ class Scope: public ZoneObject {
// Declare a local variable in this scope. If the variable has been // Declare a local variable in this scope. If the variable has been
// declared before, the previously declared variable is returned. // declared before, the previously declared variable is returned.
Variable* DeclareLocal(const AstRawString* name, VariableMode mode, Variable* DeclareLocal(const AstRawString* name, VariableMode mode,
InitializationFlag init_flag, InitializationFlag init_flag, Variable::Kind kind,
MaybeAssignedFlag maybe_assigned_flag = kNotAssigned); MaybeAssignedFlag maybe_assigned_flag = kNotAssigned);
// Declare an implicit global variable in this scope which must be a // Declare an implicit global variable in this scope which must be a
@ -142,12 +143,14 @@ class Scope: public ZoneObject {
// Create a new unresolved variable. // Create a new unresolved variable.
VariableProxy* NewUnresolved(AstNodeFactory* factory, VariableProxy* NewUnresolved(AstNodeFactory* factory,
const AstRawString* name, const AstRawString* name,
int position = RelocInfo::kNoPosition) { int start_position = RelocInfo::kNoPosition,
int end_position = RelocInfo::kNoPosition) {
// Note that we must not share the unresolved variables with // Note that we must not share the unresolved variables with
// the same name because they may be removed selectively via // the same name because they may be removed selectively via
// RemoveUnresolved(). // RemoveUnresolved().
DCHECK(!already_resolved()); DCHECK(!already_resolved());
VariableProxy* proxy = factory->NewVariableProxy(name, false, position); VariableProxy* proxy =
factory->NewVariableProxy(name, false, start_position, end_position);
unresolved_.Add(proxy, zone_); unresolved_.Add(proxy, zone_);
return proxy; return proxy;
} }
@ -317,6 +320,12 @@ class Scope: public ZoneObject {
// Does any inner scope access "this". // Does any inner scope access "this".
bool inner_uses_this() const { return inner_scope_uses_this_; } bool inner_uses_this() const { return inner_scope_uses_this_; }
const Scope* NearestOuterEvalScope() const {
if (is_eval_scope()) return this;
if (outer_scope() == nullptr) return nullptr;
return outer_scope()->NearestOuterEvalScope();
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Accessors. // Accessors.
@ -470,6 +479,10 @@ class Scope: public ZoneObject {
return params_.Contains(variables_.Lookup(name)); return params_.Contains(variables_.Lookup(name));
} }
// Error handling.
void ReportMessage(int start_position, int end_position, const char* message,
const AstRawString* arg);
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Debugging. // Debugging.
@ -696,6 +709,8 @@ class Scope: public ZoneObject {
AstValueFactory* ast_value_factory_; AstValueFactory* ast_value_factory_;
Zone* zone_; Zone* zone_;
PendingCompilationErrorHandler pending_error_handler_;
}; };
} } // namespace v8::internal } } // namespace v8::internal

View File

@ -18,7 +18,7 @@ namespace internal {
class Variable: public ZoneObject { class Variable: public ZoneObject {
public: public:
enum Kind { NORMAL, THIS, NEW_TARGET, ARGUMENTS }; enum Kind { NORMAL, FUNCTION, THIS, NEW_TARGET, ARGUMENTS };
enum Location { enum Location {
// Before and during variable allocation, a variable whose location is // Before and during variable allocation, a variable whose location is
@ -98,6 +98,7 @@ class Variable: public ZoneObject {
return initialization_flag_ == kNeedsInitialization; return initialization_flag_ == kNeedsInitialization;
} }
bool is_function() const { return kind_ == FUNCTION; }
bool is_this() const { return kind_ == THIS; } bool is_this() const { return kind_ == THIS; }
bool is_new_target() const { return kind_ == NEW_TARGET; } bool is_new_target() const { return kind_ == NEW_TARGET; }
bool is_arguments() const { return kind_ == ARGUMENTS; } bool is_arguments() const { return kind_ == ARGUMENTS; }

View File

@ -0,0 +1,188 @@
// 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 --harmony_rest_parameters --harmony_arrow_functions --harmony_classes --harmony-computed-property-names
// Note that it's essential for these tests that the reference is inside dead
// code (because we already produce ReferenceErrors for run-time unresolved
// variables and don't want to confuse those with strong mode errors). But the
// errors should *not* be inside lazy, unexecuted functions, since lazy parsing
// doesn't produce strong mode scoping errors).
// In addition, assertThrows will call eval and that changes variable binding
// types (see e.g., UNBOUND_EVAL_SHADOWED). We can avoid unwanted side effects
// by wrapping the code to be tested inside an outer function.
function assertThrowsHelper(code, error) {
"use strict";
let prologue = "(function outer() { ";
let epilogue = " })();";
assertThrows(prologue + code + epilogue, error);
}
(function DeclarationAfterUse() {
// Note that these tests only test cases where the declaration is found but is
// after the use. In particular, we cannot yet detect cases where the use can
// possibly bind to a global variable.
assertThrowsHelper("'use strong'; if (false) { x; let x = 0; }",
ReferenceError);
assertThrowsHelper(
"function f() { 'use strong'; if (false) { x; let x = 0; } } f();",
ReferenceError);
assertThrowsHelper(
"'use strong'; function f() { if (false) { x; } } let x = 0; f();",
ReferenceError);
assertThrowsHelper(
"function f() { 'use strong'; if (false) { x; } } var x = 0; f();",
ReferenceError);
assertThrowsHelper(
"function f() { 'use strong'; if (false) { x; } } var x; f();",
ReferenceError);
// Errors are also detected when the declaration and the use are in the same
// eval scope.
assertThrowsHelper("'use strong'; eval('x; let x = 0;')", ReferenceError);
// Use occurring in the initializer of the declaration:
assertThrowsHelper("'use strong'; if (false) { let x = x + 1; }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { let x = x; }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { let x = y, y = 4; }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { let x = function() { x; } }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { let x = a => { x; } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { function f() {}; let x = f(x); }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { const x = x; }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { const x = function() { x; } }",
ReferenceError);
assertThrowsHelper("'use strong'; if (false) { const x = a => { x; } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { function f() {}; const x = f(x); }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { for (let x = x; ; ) { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { for (const x = x; ; ) { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { for (let x = y, y; ; ) { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { for (const x = y, y = 0; ; ) { } }",
ReferenceError);
// Computed property names
assertThrowsHelper(
"'use strong'; if (false) { let o = { 'a': 'b', [o.a]: 'c'}; }",
ReferenceError);
})();
(function DeclarationAfterUseInClasses() {
assertThrowsHelper("'use strong'; if (false) { class C extends C { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { let C = class C2 extends C { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { let C = class C2 extends C2 { } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { let C = class C2 { constructor() { C; } } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { let C = class C2 { method() { C; } } }",
ReferenceError);
assertThrowsHelper(
"'use strong'; if (false) { let C = class C2 { " +
"static a() { return 'A'; } [C.a()]() { return 'B'; } }; }",
ReferenceError);
// TODO(marja, rossberg): More tests related to computed property names in
// classes + recognize more errors. This one is not recognized as an error
// yet:
// let C = class C2 {
// static a() { return 'A'; }
// [C2.a()]() { return 'B'; } << C2 should not be allowed here
// };
})();
(function UsesWhichAreFine() {
"use strong";
let var1 = 0;
var1;
let var2a = 0, var2b = var2a + 1, var2c = 2 + var2b;
for (let var3 = 0; var3 < 1; var3++) {
var3;
}
for (let var4a = 0, var4b = var4a; var4a + var4b < 4; var4a++, var4b++) {
var4a;
var4b;
}
let var5 = 5;
for (; var5 < 10; ++var5) { }
let arr = [1, 2];
for (let i of arr) {
i;
}
let var6 = [1, 2];
// The second var6 resolves to outside (not to the first var6).
for (let var6 of var6) { var6; }
try {
throw "error";
} catch (e) {
e;
}
function func1() { func1; this; }
func1();
func1;
function * func2() { func2; this; }
func2();
func2;
function func4(p, ...rest) { p; rest; this; func2; }
func4();
let func5 = (p1, p2) => { p1; p2; };
func5();
function func6() {
var1, var2a, var2b, var2c;
}
(function eval1() {
let var7 = 0; // Declaration position will be something large.
// But use position will be something small, however, this is not an error,
// since the use is inside an eval scope.
eval("var7;");
})();
class C1 { constructor() { C1; } }; new C1();
let C2 = class C3 { constructor() { C3; } }; new C2();
class C4 { method() { C4; method; } }; new C4();
let C5 = class C6 { method() { C6; method; } }; new C5();
})();