[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:
parent
77d3ae0e11
commit
1eddcf5b71
15
src/ast.cc
15
src/ast.cc
@ -59,24 +59,27 @@ bool Expression::IsUndefinedLiteral(Isolate* isolate) const {
|
||||
}
|
||||
|
||||
|
||||
VariableProxy::VariableProxy(Zone* zone, Variable* var, int position)
|
||||
: Expression(zone, position),
|
||||
VariableProxy::VariableProxy(Zone* zone, Variable* var, int start_position,
|
||||
int end_position)
|
||||
: Expression(zone, start_position),
|
||||
bit_field_(IsThisField::encode(var->is_this()) |
|
||||
IsAssignedField::encode(false) |
|
||||
IsResolvedField::encode(false)),
|
||||
variable_feedback_slot_(FeedbackVectorICSlot::Invalid()),
|
||||
raw_name_(var->raw_name()) {
|
||||
raw_name_(var->raw_name()),
|
||||
end_position_(end_position) {
|
||||
BindTo(var);
|
||||
}
|
||||
|
||||
|
||||
VariableProxy::VariableProxy(Zone* zone, const AstRawString* name, bool is_this,
|
||||
int position)
|
||||
: Expression(zone, position),
|
||||
int start_position, int end_position)
|
||||
: Expression(zone, start_position),
|
||||
bit_field_(IsThisField::encode(is_this) | IsAssignedField::encode(false) |
|
||||
IsResolvedField::encode(false)),
|
||||
variable_feedback_slot_(FeedbackVectorICSlot::Invalid()),
|
||||
raw_name_(name) {}
|
||||
raw_name_(name),
|
||||
end_position_(end_position) {}
|
||||
|
||||
|
||||
void VariableProxy::BindTo(Variable* var) {
|
||||
|
25
src/ast.h
25
src/ast.h
@ -1631,6 +1631,8 @@ class VariableProxy FINAL : public Expression {
|
||||
bit_field_ = IsResolvedField::update(bit_field_, true);
|
||||
}
|
||||
|
||||
int end_position() const { return end_position_; }
|
||||
|
||||
// Bind this proxy to the variable var.
|
||||
void BindTo(Variable* var);
|
||||
|
||||
@ -1653,10 +1655,11 @@ class VariableProxy FINAL : public Expression {
|
||||
}
|
||||
|
||||
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,
|
||||
int position);
|
||||
int start_position, int end_position);
|
||||
|
||||
class IsThisField : public BitField8<bool, 0, 1> {};
|
||||
class IsAssignedField : public BitField8<bool, 1, 1> {};
|
||||
@ -1670,6 +1673,10 @@ class VariableProxy FINAL : public Expression {
|
||||
const AstRawString* raw_name_; // 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,
|
||||
int pos = RelocInfo::kNoPosition) {
|
||||
return new (zone_) VariableProxy(zone_, var, pos);
|
||||
int start_position = RelocInfo::kNoPosition,
|
||||
int end_position = RelocInfo::kNoPosition) {
|
||||
return new (zone_) VariableProxy(zone_, var, start_position, end_position);
|
||||
}
|
||||
|
||||
VariableProxy* NewVariableProxy(const AstRawString* name,
|
||||
bool is_this,
|
||||
int position = RelocInfo::kNoPosition) {
|
||||
return new (zone_) VariableProxy(zone_, name, is_this, position);
|
||||
VariableProxy* NewVariableProxy(const AstRawString* name, bool is_this,
|
||||
int start_position = RelocInfo::kNoPosition,
|
||||
int end_position = RelocInfo::kNoPosition) {
|
||||
return new (zone_)
|
||||
VariableProxy(zone_, name, is_this, start_position, end_position);
|
||||
}
|
||||
|
||||
Property* NewProperty(Expression* obj, Expression* key, int pos) {
|
||||
|
@ -168,6 +168,7 @@ var kMessages = {
|
||||
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_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"],
|
||||
malformed_arrow_function_parameter_list: ["Malformed arrow function parameter list"],
|
||||
generator_poison_pill: ["'caller' and 'arguments' properties may not be accessed on generator functions."],
|
||||
|
@ -706,7 +706,9 @@ Literal* ParserTraits::ExpressionFromLiteral(Token::Value token, int pos,
|
||||
|
||||
|
||||
Expression* ParserTraits::ExpressionFromIdentifier(const AstRawString* name,
|
||||
int pos, Scope* scope,
|
||||
int start_position,
|
||||
int end_position,
|
||||
Scope* scope,
|
||||
AstNodeFactory* factory) {
|
||||
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
|
||||
// pick the names of the parameters.
|
||||
return parser_->parsing_lazy_arrow_parameters_
|
||||
? factory->NewVariableProxy(name, false, pos)
|
||||
: scope->NewUnresolved(factory, name, pos);
|
||||
? factory->NewVariableProxy(name, false, start_position,
|
||||
end_position)
|
||||
: scope->NewUnresolved(factory, name, start_position,
|
||||
end_position);
|
||||
}
|
||||
|
||||
|
||||
@ -1776,11 +1780,13 @@ VariableProxy* Parser::NewUnresolved(const AstRawString* name,
|
||||
// scope.
|
||||
// Let/const variables in harmony mode are always added to the immediately
|
||||
// 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();
|
||||
DCHECK(proxy->raw_name() != NULL);
|
||||
const AstRawString* name = proxy->raw_name();
|
||||
@ -1806,10 +1812,14 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
|
||||
if (var == NULL) {
|
||||
// Declare the name.
|
||||
var = declaration_scope->DeclareLocal(
|
||||
name, mode, declaration->initialization(), kNotAssigned);
|
||||
} else if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(var->mode())
|
||||
|| ((mode == CONST_LEGACY || var->mode() == CONST_LEGACY) &&
|
||||
!declaration_scope->is_script_scope())) {
|
||||
name, mode, declaration->initialization(),
|
||||
declaration->IsFunctionDeclaration() ? Variable::FUNCTION
|
||||
: Variable::NORMAL,
|
||||
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
|
||||
// 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
|
||||
@ -1829,7 +1839,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
|
||||
// ES5 16 for a definition of early errors.
|
||||
ParserTraits::ReportMessage("var_redeclaration", name);
|
||||
*ok = false;
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
Expression* expression = NewThrowTypeError(
|
||||
"var_redeclaration", name, declaration->position());
|
||||
@ -1903,6 +1913,7 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
|
||||
if (resolve && var != NULL) {
|
||||
proxy->BindTo(var);
|
||||
}
|
||||
return var;
|
||||
}
|
||||
|
||||
|
||||
@ -2023,7 +2034,7 @@ Statement* Parser::ParseClassDeclaration(ZoneList<const AstRawString*>* names,
|
||||
Declaration* declaration =
|
||||
factory()->NewVariableDeclaration(proxy, mode, scope_, pos);
|
||||
Declare(declaration, true, CHECK_OK);
|
||||
proxy->var()->set_initializer_position(pos);
|
||||
proxy->var()->set_initializer_position(position());
|
||||
|
||||
Token::Value init_op =
|
||||
is_strong(language_mode()) ? Token::INIT_CONST : Token::INIT_LET;
|
||||
@ -2232,7 +2243,9 @@ Block* Parser::ParseVariableDeclarations(
|
||||
VariableProxy* proxy = NewUnresolved(name, mode);
|
||||
Declaration* declaration =
|
||||
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++;
|
||||
if (declaration_scope->num_var_or_const() > kMaxNumFunctionLocals) {
|
||||
ReportMessage("too_many_variables");
|
||||
@ -2286,11 +2299,11 @@ Block* Parser::ParseVariableDeclarations(
|
||||
fni_->RemoveLastFunction();
|
||||
}
|
||||
if (decl_props != NULL) *decl_props = kHasInitializers;
|
||||
}
|
||||
|
||||
// Record the end position of the initializer.
|
||||
if (proxy->is_resolved()) {
|
||||
proxy->var()->set_initializer_position(position());
|
||||
// End position of the initializer is after the assignment expression.
|
||||
var->set_initializer_position(scanner()->location().end_pos);
|
||||
} else {
|
||||
// End position of the initializer is after the variable.
|
||||
var->set_initializer_position(position());
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
catch_variable = catch_scope->DeclareLocal(name, VAR, kCreatedInitialized);
|
||||
catch_variable = catch_scope->DeclareLocal(name, VAR, kCreatedInitialized,
|
||||
Variable::NORMAL);
|
||||
BlockState block_state(&scope_, catch_scope);
|
||||
catch_block = ParseBlock(NULL, CHECK_OK);
|
||||
|
||||
@ -3087,16 +3101,16 @@ Statement* Parser::DesugarLetBindingsInForStatement(
|
||||
// make statement: let x = temp_x.
|
||||
for (int i = 0; i < names->length(); i++) {
|
||||
VariableProxy* proxy = NewUnresolved(names->at(i), LET);
|
||||
Declaration* declaration =
|
||||
factory()->NewVariableDeclaration(proxy, LET, scope_, pos);
|
||||
Declaration* declaration = factory()->NewVariableDeclaration(
|
||||
proxy, LET, scope_, RelocInfo::kNoPosition);
|
||||
Declare(declaration, true, CHECK_OK);
|
||||
inner_vars.Add(declaration->proxy()->var(), zone());
|
||||
VariableProxy* temp_proxy = factory()->NewVariableProxy(temps.at(i));
|
||||
Assignment* assignment = factory()->NewAssignment(
|
||||
Token::INIT_LET, proxy, temp_proxy, pos);
|
||||
Statement* assignment_statement = factory()->NewExpressionStatement(
|
||||
assignment, pos);
|
||||
proxy->var()->set_initializer_position(pos);
|
||||
Statement* assignment_statement =
|
||||
factory()->NewExpressionStatement(assignment, RelocInfo::kNoPosition);
|
||||
proxy->var()->set_initializer_position(init->position());
|
||||
inner_block->AddStatement(assignment_statement, zone());
|
||||
}
|
||||
|
||||
@ -3244,7 +3258,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
CHECK_OK);
|
||||
bool accept_OF = decl_props == kHasNoInitializers;
|
||||
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 (!*ok) return nullptr;
|
||||
@ -3255,7 +3270,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
Expression* enumerable = ParseExpression(true, 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);
|
||||
InitializeForEachStatement(loop, each, enumerable, body);
|
||||
Block* result =
|
||||
@ -3282,7 +3298,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
bool accept_IN = name != NULL && decl_props != kHasInitializers;
|
||||
bool accept_OF = decl_props == kHasNoInitializers;
|
||||
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 (!*ok) return nullptr;
|
||||
@ -3304,7 +3321,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
// implementing stack allocated block scoped variables.
|
||||
Variable* temp = scope_->DeclarationScope()->NewTemporary(
|
||||
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 =
|
||||
factory()->NewForEachStatement(mode, labels, stmt_pos);
|
||||
Target target(&this->target_stack_, loop);
|
||||
@ -3315,7 +3333,8 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
scope_ = for_scope;
|
||||
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);
|
||||
Block* body_block =
|
||||
factory()->NewBlock(NULL, 3, false, RelocInfo::kNoPosition);
|
||||
@ -4099,6 +4118,8 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name,
|
||||
bool has_seen_constructor = false;
|
||||
|
||||
Expect(Token::LBRACE, CHECK_OK);
|
||||
int body_beg_pos = scanner()->location().beg_pos;
|
||||
|
||||
const bool has_extends = extends != nullptr;
|
||||
while (peek() != Token::RBRACE) {
|
||||
if (Check(Token::SEMICOLON)) continue;
|
||||
@ -4138,7 +4159,7 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name,
|
||||
if (name != NULL) {
|
||||
DCHECK_NOT_NULL(proxy);
|
||||
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,
|
||||
|
@ -542,7 +542,8 @@ class ParserTraits {
|
||||
int end_pos);
|
||||
Literal* ExpressionFromLiteral(Token::Value token, int pos, Scanner* scanner,
|
||||
AstNodeFactory* factory);
|
||||
Expression* ExpressionFromIdentifier(const AstRawString* name, int pos,
|
||||
Expression* ExpressionFromIdentifier(const AstRawString* name,
|
||||
int start_position, int end_position,
|
||||
Scope* scope, AstNodeFactory* factory);
|
||||
Expression* ExpressionFromString(int pos, Scanner* scanner,
|
||||
AstNodeFactory* factory);
|
||||
@ -799,7 +800,7 @@ class Parser : public ParserBase<ParserTraits> {
|
||||
|
||||
// Parser support
|
||||
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);
|
||||
BreakableStatement* LookupBreakTarget(const AstRawString* label, bool* ok);
|
||||
|
@ -1408,8 +1408,8 @@ class PreParserTraits {
|
||||
}
|
||||
|
||||
static PreParserExpression ExpressionFromIdentifier(
|
||||
PreParserIdentifier name, int pos, Scope* scope,
|
||||
PreParserFactory* factory) {
|
||||
PreParserIdentifier name, int start_position, int end_position,
|
||||
Scope* scope, PreParserFactory* factory) {
|
||||
return PreParserExpression::FromIdentifier(name);
|
||||
}
|
||||
|
||||
@ -1859,14 +1859,15 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
|
||||
// '(' Expression ')'
|
||||
// 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();
|
||||
Token::Value token = peek();
|
||||
switch (token) {
|
||||
case Token::THIS: {
|
||||
Consume(Token::THIS);
|
||||
scope_->RecordThisUsage();
|
||||
result = this->ThisExpression(scope_, factory(), pos);
|
||||
result = this->ThisExpression(scope_, factory(), beg_pos);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1875,7 +1876,8 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
|
||||
case Token::FALSE_LITERAL:
|
||||
case Token::NUMBER:
|
||||
Next();
|
||||
result = this->ExpressionFromLiteral(token, pos, scanner(), factory());
|
||||
result =
|
||||
this->ExpressionFromLiteral(token, beg_pos, scanner(), factory());
|
||||
break;
|
||||
|
||||
case Token::IDENTIFIER:
|
||||
@ -1885,13 +1887,14 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
|
||||
case Token::FUTURE_STRICT_RESERVED_WORD: {
|
||||
// Using eval or arguments in this context is OK even in strict mode.
|
||||
IdentifierT name = ParseIdentifier(kAllowEvalOrArguments, CHECK_OK);
|
||||
result = this->ExpressionFromIdentifier(name, pos, scope_, factory());
|
||||
result = this->ExpressionFromIdentifier(name, beg_pos, end_pos, scope_,
|
||||
factory());
|
||||
break;
|
||||
}
|
||||
|
||||
case Token::STRING: {
|
||||
Consume(Token::STRING);
|
||||
result = this->ExpressionFromString(pos, scanner(), factory());
|
||||
result = this->ExpressionFromString(beg_pos, scanner(), factory());
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1918,7 +1921,7 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
|
||||
// for which an empty parameter list "()" is valid input.
|
||||
Consume(Token::RPAREN);
|
||||
result = this->ParseArrowFunctionLiteral(
|
||||
pos, this->EmptyArrowParamList(), CHECK_OK);
|
||||
beg_pos, this->EmptyArrowParamList(), CHECK_OK);
|
||||
} else {
|
||||
// Heuristically try to detect immediately called functions before
|
||||
// seeing the call parentheses.
|
||||
@ -1953,8 +1956,8 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
|
||||
|
||||
case Token::TEMPLATE_SPAN:
|
||||
case Token::TEMPLATE_TAIL:
|
||||
result =
|
||||
this->ParseTemplateLiteral(Traits::NoTemplateTag(), pos, CHECK_OK);
|
||||
result = this->ParseTemplateLiteral(Traits::NoTemplateTag(), beg_pos,
|
||||
CHECK_OK);
|
||||
break;
|
||||
|
||||
case Token::MOD:
|
||||
@ -2100,7 +2103,8 @@ ParserBase<Traits>::ParsePropertyDefinition(ObjectLiteralCheckerBase* checker,
|
||||
bool is_generator = allow_harmony_object_literals_ && Check(Token::MUL);
|
||||
|
||||
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(
|
||||
&name, &is_get, &is_set, &name_is_static, is_computed_name,
|
||||
CHECK_OK_CUSTOM(EmptyObjectLiteralProperty));
|
||||
@ -2195,7 +2199,8 @@ ParserBase<Traits>::ParsePropertyDefinition(ObjectLiteralCheckerBase* checker,
|
||||
this->is_generator())) {
|
||||
DCHECK(!*is_computed_name);
|
||||
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(
|
||||
name_expression, value, ObjectLiteralProperty::COMPUTED, false, false);
|
||||
|
||||
|
@ -263,7 +263,12 @@ bool Scope::Analyze(CompilationInfo* info) {
|
||||
// Allocate the variables.
|
||||
{
|
||||
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
|
||||
@ -461,7 +466,7 @@ Variable* Scope::DeclareParameter(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) {
|
||||
DCHECK(!already_resolved());
|
||||
// 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().
|
||||
DCHECK(IsDeclaredVariableMode(mode));
|
||||
++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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
static const char* Header(ScopeType scope_type) {
|
||||
switch (scope_type) {
|
||||
@ -1050,6 +1069,31 @@ bool Scope::ResolveVariable(CompilationInfo* info, VariableProxy* proxy,
|
||||
switch (binding_kind) {
|
||||
case BOUND:
|
||||
// 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;
|
||||
|
||||
case BOUND_EVAL_SHADOWED:
|
||||
|
21
src/scopes.h
21
src/scopes.h
@ -6,6 +6,7 @@
|
||||
#define V8_SCOPES_H_
|
||||
|
||||
#include "src/ast.h"
|
||||
#include "src/pending-compilation-error-handler.h"
|
||||
#include "src/zone.h"
|
||||
|
||||
namespace v8 {
|
||||
@ -130,7 +131,7 @@ class Scope: public ZoneObject {
|
||||
// Declare a local variable in this scope. If the variable has been
|
||||
// declared before, the previously declared variable is returned.
|
||||
Variable* DeclareLocal(const AstRawString* name, VariableMode mode,
|
||||
InitializationFlag init_flag,
|
||||
InitializationFlag init_flag, Variable::Kind kind,
|
||||
MaybeAssignedFlag maybe_assigned_flag = kNotAssigned);
|
||||
|
||||
// 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.
|
||||
VariableProxy* NewUnresolved(AstNodeFactory* factory,
|
||||
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
|
||||
// the same name because they may be removed selectively via
|
||||
// RemoveUnresolved().
|
||||
DCHECK(!already_resolved());
|
||||
VariableProxy* proxy = factory->NewVariableProxy(name, false, position);
|
||||
VariableProxy* proxy =
|
||||
factory->NewVariableProxy(name, false, start_position, end_position);
|
||||
unresolved_.Add(proxy, zone_);
|
||||
return proxy;
|
||||
}
|
||||
@ -317,6 +320,12 @@ class Scope: public ZoneObject {
|
||||
// Does any inner scope access "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.
|
||||
|
||||
@ -470,6 +479,10 @@ class Scope: public ZoneObject {
|
||||
return params_.Contains(variables_.Lookup(name));
|
||||
}
|
||||
|
||||
// Error handling.
|
||||
void ReportMessage(int start_position, int end_position, const char* message,
|
||||
const AstRawString* arg);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Debugging.
|
||||
|
||||
@ -696,6 +709,8 @@ class Scope: public ZoneObject {
|
||||
|
||||
AstValueFactory* ast_value_factory_;
|
||||
Zone* zone_;
|
||||
|
||||
PendingCompilationErrorHandler pending_error_handler_;
|
||||
};
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
@ -18,7 +18,7 @@ namespace internal {
|
||||
|
||||
class Variable: public ZoneObject {
|
||||
public:
|
||||
enum Kind { NORMAL, THIS, NEW_TARGET, ARGUMENTS };
|
||||
enum Kind { NORMAL, FUNCTION, THIS, NEW_TARGET, ARGUMENTS };
|
||||
|
||||
enum Location {
|
||||
// Before and during variable allocation, a variable whose location is
|
||||
@ -98,6 +98,7 @@ class Variable: public ZoneObject {
|
||||
return initialization_flag_ == kNeedsInitialization;
|
||||
}
|
||||
|
||||
bool is_function() const { return kind_ == FUNCTION; }
|
||||
bool is_this() const { return kind_ == THIS; }
|
||||
bool is_new_target() const { return kind_ == NEW_TARGET; }
|
||||
bool is_arguments() const { return kind_ == ARGUMENTS; }
|
||||
|
188
test/mjsunit/strong/declaration-after-use.js
Normal file
188
test/mjsunit/strong/declaration-after-use.js
Normal 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();
|
||||
})();
|
Loading…
Reference in New Issue
Block a user