diff --git a/src/ast.cc b/src/ast.cc index ac81e751af..6bbbe641cf 100644 --- a/src/ast.cc +++ b/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) { diff --git a/src/ast.h b/src/ast.h index 9b86d0b512..3a273f3865 100644 --- a/src/ast.h +++ b/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 {}; class IsAssignedField : public BitField8 {}; @@ -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) { diff --git a/src/messages.js b/src/messages.js index 9ec122370e..19cc6edc55 100644 --- a/src/messages.js +++ b/src/messages.js @@ -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."], diff --git a/src/parser.cc b/src/parser.cc index e136d9627b..89b934c2f8 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -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* 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* 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* 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* 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* 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* 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, diff --git a/src/parser.h b/src/parser.h index 0bfd317b9e..33b4f7107d 100644 --- a/src/parser.h +++ b/src/parser.h @@ -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 { // 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); diff --git a/src/preparser.h b/src/preparser.h index 443ed0c2f0..64f456b2d7 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -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::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::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::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::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::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::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::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); diff --git a/src/scopes.cc b/src/scopes.cc index 35449643ce..97a89431f3 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -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: diff --git a/src/scopes.h b/src/scopes.h index c58d124939..186530ceab 100644 --- a/src/scopes.h +++ b/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 diff --git a/src/variables.h b/src/variables.h index 1adeb1f0f4..4696e0543d 100644 --- a/src/variables.h +++ b/src/variables.h @@ -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; } diff --git a/test/mjsunit/strong/declaration-after-use.js b/test/mjsunit/strong/declaration-after-use.js new file mode 100644 index 0000000000..0087357000 --- /dev/null +++ b/test/mjsunit/strong/declaration-after-use.js @@ -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(); +})();