[strong] Declaration-after-use errors.
authormarja <marja@chromium.org>
Thu, 26 Feb 2015 13:48:10 +0000 (05:48 -0800)
committerCommit bot <commit-bot@chromium.org>
Thu, 26 Feb 2015 13:48:19 +0000 (13:48 +0000)
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}

src/ast.cc
src/ast.h
src/messages.js
src/parser.cc
src/parser.h
src/preparser.h
src/scopes.cc
src/scopes.h
src/variables.h
test/mjsunit/strong/declaration-after-use.js [new file with mode: 0644]

index ac81e75..6bbbe64 100644 (file)
@@ -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) {
index 9b86d0b..3a273f3 100644 (file)
--- 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<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) {
index 9ec1223..19cc6ed 100644 (file)
@@ -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."],
index e136d96..89b934c 100644 (file)
@@ -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,
index 0bfd317..33b4f71 100644 (file)
@@ -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);
index 443ed0c..64f456b 100644 (file)
@@ -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);
 
index 3544964..97a8943 100644 (file)
@@ -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:
index c58d124..186530c 100644 (file)
@@ -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
index 1adeb1f..4696e05 100644 (file)
@@ -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 (file)
index 0000000..0087357
--- /dev/null
@@ -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();
+})();