Infer whether a variable is assigned in inner functions
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 26 Jun 2014 11:59:42 +0000 (11:59 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 26 Jun 2014 11:59:42 +0000 (11:59 +0000)
R=titzer@chromium.org
BUG=

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

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22039 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/ast-value-factory.h
src/ast.cc
src/ast.h
src/parser.cc
src/parser.h
src/preparser.h
src/scopes.cc
src/scopes.h
src/variables.cc
src/variables.h
test/cctest/test-parsing.cc

index 51ee27714f72536861554a43d02a9b45c1ddf716..e70271f96ed3f76c5e5dbb7f241846ddff66cb76 100644 (file)
@@ -278,6 +278,10 @@ class AstValueFactory {
   }
 
   const AstRawString* GetOneByteString(Vector<const uint8_t> literal);
+  const AstRawString* GetOneByteString(const char* string) {
+    return GetOneByteString(Vector<const uint8_t>(
+        reinterpret_cast<const uint8_t*>(string), StrLength(string)));
+  }
   const AstRawString* GetTwoByteString(Vector<const uint16_t> literal);
   const AstRawString* GetString(Handle<String> literal);
   const AstConsString* NewConsString(const AstString* left,
index bf16b9c076b8786ce1cb4a2eecf4963f79d70fe9..5939eb186eeda720db76a8459292e7ed6fb5a34d 100644 (file)
@@ -64,8 +64,7 @@ VariableProxy::VariableProxy(Zone* zone, Variable* var, int position)
       name_(var->raw_name()),
       var_(NULL),  // Will be set by the call to BindTo.
       is_this_(var->is_this()),
-      is_trivial_(false),
-      is_lvalue_(false),
+      is_assigned_(false),
       interface_(var->interface()) {
   BindTo(var);
 }
@@ -80,8 +79,7 @@ VariableProxy::VariableProxy(Zone* zone,
       name_(name),
       var_(NULL),
       is_this_(is_this),
-      is_trivial_(false),
-      is_lvalue_(false),
+      is_assigned_(false),
       interface_(interface) {
 }
 
@@ -97,7 +95,7 @@ void VariableProxy::BindTo(Variable* var) {
   // eval() etc.  Const-ness and variable declarations are a complete mess
   // in JS. Sigh...
   var_ = var;
-  var->set_is_used(true);
+  var->set_is_used();
 }
 
 
index 23f9eef477a9a318769ad67256b650e23ca11372..1396bf4aaabfa28dec0d90bacbcdd7b533e2147f 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -1628,17 +1628,14 @@ class VariableProxy V8_FINAL : public Expression {
 
   bool IsArguments() const { return var_ != NULL && var_->is_arguments(); }
 
-  bool IsLValue() const { return is_lvalue_; }
-
   Handle<String> name() const { return name_->string(); }
   const AstRawString* raw_name() const { return name_; }
   Variable* var() const { return var_; }
   bool is_this() const { return is_this_; }
   Interface* interface() const { return interface_; }
 
-
-  void MarkAsTrivial() { is_trivial_ = true; }
-  void MarkAsLValue() { is_lvalue_ = true; }
+  bool is_assigned() const { return is_assigned_; }
+  void set_is_assigned() { is_assigned_ = true; }
 
   // Bind this proxy to the variable var. Interfaces must match.
   void BindTo(Variable* var);
@@ -1655,10 +1652,7 @@ class VariableProxy V8_FINAL : public Expression {
   const AstRawString* name_;
   Variable* var_;  // resolved variable, or NULL
   bool is_this_;
-  bool is_trivial_;
-  // True if this variable proxy is being used in an assignment
-  // or with a increment/decrement operator.
-  bool is_lvalue_;
+  bool is_assigned_;
   Interface* interface_;
 };
 
index 80cbb2daa3d8705298296300b0f16e91f181a6d6..03f3c7f990d25f9cdae647fde95a68a8215b9428 100644 (file)
@@ -453,11 +453,10 @@ void ParserTraits::CheckPossibleEvalCall(Expression* expression,
 }
 
 
-Expression* ParserTraits::MarkExpressionAsLValue(Expression* expression) {
-  VariableProxy* proxy = expression != NULL
-      ? expression->AsVariableProxy()
-      : NULL;
-  if (proxy != NULL) proxy->MarkAsLValue();
+Expression* ParserTraits::MarkExpressionAsAssigned(Expression* expression) {
+  VariableProxy* proxy =
+      expression != NULL ? expression->AsVariableProxy() : NULL;
+  if (proxy != NULL) proxy->set_is_assigned();
   return expression;
 }
 
@@ -593,8 +592,7 @@ Expression* ParserTraits::NewThrowError(
   Zone* zone = parser_->zone();
   int argc = arg != NULL ? 1 : 0;
   const AstRawString* type =
-      parser_->ast_value_factory_->GetOneByteString(Vector<const uint8_t>(
-          reinterpret_cast<const uint8_t*>(message), StrLength(message)));
+      parser_->ast_value_factory_->GetOneByteString(message);
   ZoneList<const AstRawString*>* array =
       new (zone) ZoneList<const AstRawString*>(argc, zone);
   if (arg != NULL) {
@@ -1722,6 +1720,8 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
       Expression* expression = NewThrowTypeError(
           "var_redeclaration", name, declaration->position());
       declaration_scope->SetIllegalRedeclaration(expression);
+    } else if (mode == VAR) {
+      var->set_maybe_assigned();
     }
   }
 
index 3d44d585ca069c9ba82bd05069cda0d0bd176730..c05b2d9cb330be6b82b6a193fe7404ff17bc6423 100644 (file)
@@ -468,9 +468,8 @@ class ParserTraits {
   void CheckPossibleEvalCall(Expression* expression, Scope* scope);
 
   // Determine if the expression is a variable proxy and mark it as being used
-  // in an assignment or with a increment/decrement operator. This is currently
-  // used on for the statically checking assignments to harmony const bindings.
-  static Expression* MarkExpressionAsLValue(Expression* expression);
+  // in an assignment or with a increment/decrement operator.
+  static Expression* MarkExpressionAsAssigned(Expression* expression);
 
   // Returns true if we have a binary expression between two numeric
   // literals. In that case, *x will be changed to an expression which is the
index 499240e63acc6b43bdcefd74edc183113600f432..67990872c95a695d788b15273d13174f8cdc9f4e 100644 (file)
@@ -973,10 +973,10 @@ class PreParserTraits {
   static void CheckPossibleEvalCall(PreParserExpression expression,
                                     PreParserScope* scope) {}
 
-  static PreParserExpression MarkExpressionAsLValue(
+  static PreParserExpression MarkExpressionAsAssigned(
       PreParserExpression expression) {
     // TODO(marja): To be able to produce the same errors, the preparser needs
-    // to start tracking which expressions are variables and which are lvalues.
+    // to start tracking which expressions are variables and which are assigned.
     return expression;
   }
 
@@ -1753,7 +1753,7 @@ ParserBase<Traits>::ParseAssignmentExpression(bool accept_IN, bool* ok) {
 
   expression = this->CheckAndRewriteReferenceExpression(
       expression, lhs_location, "invalid_lhs_in_assignment", CHECK_OK);
-  expression = this->MarkExpressionAsLValue(expression);
+  expression = this->MarkExpressionAsAssigned(expression);
 
   Token::Value op = Next();  // Get assignment operator.
   int pos = position();
@@ -1915,7 +1915,7 @@ ParserBase<Traits>::ParseUnaryExpression(bool* ok) {
     ExpressionT expression = this->ParseUnaryExpression(CHECK_OK);
     expression = this->CheckAndRewriteReferenceExpression(
         expression, lhs_location, "invalid_lhs_in_prefix_op", CHECK_OK);
-    this->MarkExpressionAsLValue(expression);
+    this->MarkExpressionAsAssigned(expression);
 
     return factory()->NewCountOperation(op,
                                         true /* prefix */,
@@ -1940,7 +1940,7 @@ ParserBase<Traits>::ParsePostfixExpression(bool* ok) {
       Token::IsCountOp(peek())) {
     expression = this->CheckAndRewriteReferenceExpression(
         expression, lhs_location, "invalid_lhs_in_postfix_op", CHECK_OK);
-    expression = this->MarkExpressionAsLValue(expression);
+    expression = this->MarkExpressionAsAssigned(expression);
 
     Token::Value next = Next();
     expression =
index 2b3c07a64f95d0ebabff2c4a1572ab9d24ecdaf4..6806ee95cf4aeb8fc87c1306f1c0e7fcf51a9d1e 100644 (file)
@@ -170,6 +170,7 @@ void Scope::SetDefaults(ScopeType scope_type,
   strict_mode_ = outer_scope != NULL ? outer_scope->strict_mode_ : SLOPPY;
   outer_scope_calls_sloppy_eval_ = false;
   inner_scope_calls_eval_ = false;
+  inner_scope_contains_with_ = false;
   force_eager_compilation_ = false;
   force_context_allocation_ = (outer_scope != NULL && !is_function_scope())
       ? outer_scope->has_forced_context_allocation() : false;
@@ -194,6 +195,7 @@ Scope* Scope::DeserializeScopeChain(Context* context, Scope* global_scope,
   Scope* current_scope = NULL;
   Scope* innermost_scope = NULL;
   bool contains_with = false;
+  bool inner_contains_with = false;
   while (!context->IsNativeContext()) {
     if (context->IsWithContext()) {
       Scope* with_scope = new(zone) Scope(current_scope,
@@ -243,7 +245,11 @@ Scope* Scope::DeserializeScopeChain(Context* context, Scope* global_scope,
           global_scope->ast_value_factory_->GetString(Handle<String>(name)),
           global_scope->ast_value_factory_, zone);
     }
-    if (contains_with) current_scope->RecordWithStatement();
+    if (inner_contains_with) current_scope->inner_scope_contains_with_ = true;
+    if (contains_with) {
+      current_scope->RecordWithStatement();
+      inner_contains_with = true;
+    }
     if (innermost_scope == NULL) innermost_scope = current_scope;
 
     // Forget about a with when we move to a context for a different function.
@@ -819,9 +825,15 @@ static void PrintVar(int indent, Variable* var) {
     PrintName(var->raw_name());
     PrintF(";  // ");
     PrintLocation(var);
+    bool comma = !var->IsUnallocated();
     if (var->has_forced_context_allocation()) {
-      if (!var->IsUnallocated()) PrintF(", ");
+      if (comma) PrintF(", ");
       PrintF("forced context allocation");
+      comma = true;
+    }
+    if (var->maybe_assigned()) {
+      if (comma) PrintF(", ");
+      PrintF("maybe assigned");
     }
     PrintF("\n");
   }
@@ -875,6 +887,9 @@ void Scope::Print(int n) {
   }
   if (scope_inside_with_) Indent(n1, "// scope inside 'with'\n");
   if (scope_contains_with_) Indent(n1, "// scope contains 'with'\n");
+  if (inner_scope_contains_with_) {
+    Indent(n1, "// inner scope contains 'with'\n");
+  }
   if (scope_calls_eval_) Indent(n1, "// scope calls 'eval'\n");
   if (outer_scope_calls_sloppy_eval_) {
     Indent(n1, "// outer scope calls 'eval' in sloppy context\n");
@@ -951,7 +966,7 @@ Variable* Scope::NonLocal(const AstRawString* name, VariableMode mode) {
 }
 
 
-Variable* Scope::LookupRecursive(const AstRawString* name,
+Variable* Scope::LookupRecursive(VariableProxy* proxy,
                                  BindingKind* binding_kind,
                                  AstNodeFactory<AstNullVisitor>* factory) {
   ASSERT(binding_kind != NULL);
@@ -963,7 +978,7 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
   }
 
   // Try to find the variable in this scope.
-  Variable* var = LookupLocal(name);
+  Variable* var = LookupLocal(proxy->raw_name());
 
   // We found a variable and we are done. (Even if there is an 'eval' in
   // this scope which introduces the same variable again, the resulting
@@ -977,11 +992,11 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
   // if any. We can do this for all scopes, since the function variable is
   // only present - if at all - for function scopes.
   *binding_kind = UNBOUND;
-  var = LookupFunctionVar(name, factory);
+  var = LookupFunctionVar(proxy->raw_name(), factory);
   if (var != NULL) {
     *binding_kind = BOUND;
   } else if (outer_scope_ != NULL) {
-    var = outer_scope_->LookupRecursive(name, binding_kind, factory);
+    var = outer_scope_->LookupRecursive(proxy, binding_kind, factory);
     if (*binding_kind == BOUND && (is_function_scope() || is_with_scope())) {
       var->ForceContextAllocation();
     }
@@ -997,6 +1012,7 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
     // the associated variable has to be marked as potentially being accessed
     // from inside of an inner with scope (the property may not be in the 'with'
     // object).
+    if (var != NULL && proxy->is_assigned()) var->set_maybe_assigned();
     *binding_kind = DYNAMIC_LOOKUP;
     return NULL;
   } else if (calls_sloppy_eval()) {
@@ -1025,7 +1041,7 @@ bool Scope::ResolveVariable(CompilationInfo* info,
 
   // Otherwise, try to resolve the variable.
   BindingKind binding_kind;
-  Variable* var = LookupRecursive(proxy->raw_name(), &binding_kind, factory);
+  Variable* var = LookupRecursive(proxy, &binding_kind, factory);
   switch (binding_kind) {
     case BOUND:
       // We found a variable binding.
@@ -1064,9 +1080,10 @@ bool Scope::ResolveVariable(CompilationInfo* info,
   }
 
   ASSERT(var != NULL);
+  if (proxy->is_assigned()) var->set_maybe_assigned();
 
   if (FLAG_harmony_scoping && strict_mode() == STRICT &&
-      var->is_const_mode() && proxy->IsLValue()) {
+      var->is_const_mode() && proxy->is_assigned()) {
     // Assignment to const. Throw a syntax error.
     MessageLocation location(
         info->script(), proxy->position(), proxy->position());
@@ -1140,7 +1157,7 @@ bool Scope::ResolveVariablesRecursively(
 }
 
 
-bool Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
+void Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
   if (outer_scope_calls_sloppy_eval) {
     outer_scope_calls_sloppy_eval_ = true;
   }
@@ -1148,16 +1165,18 @@ bool Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
   bool calls_sloppy_eval =
       this->calls_sloppy_eval() || outer_scope_calls_sloppy_eval_;
   for (int i = 0; i < inner_scopes_.length(); i++) {
-    Scope* inner_scope = inner_scopes_[i];
-    if (inner_scope->PropagateScopeInfo(calls_sloppy_eval)) {
+    Scope* inner = inner_scopes_[i];
+    inner->PropagateScopeInfo(calls_sloppy_eval);
+    if (inner->scope_calls_eval_ || inner->inner_scope_calls_eval_) {
       inner_scope_calls_eval_ = true;
     }
-    if (inner_scope->force_eager_compilation_) {
+    if (inner->scope_contains_with_ || inner->inner_scope_contains_with_) {
+      inner_scope_contains_with_ = true;
+    }
+    if (inner->force_eager_compilation_) {
       force_eager_compilation_ = true;
     }
   }
-
-  return scope_calls_eval_ || inner_scope_calls_eval_;
 }
 
 
@@ -1174,7 +1193,8 @@ bool Scope::MustAllocate(Variable* var) {
        is_block_scope() ||
        is_module_scope() ||
        is_global_scope())) {
-    var->set_is_used(true);
+    var->set_is_used();
+    if (scope_calls_eval_ || inner_scope_calls_eval_) var->set_maybe_assigned();
   }
   // Global variables do not need to be allocated.
   return !var->IsGlobalObjectProperty() && var->is_used();
index c313a400d2683335e7643130408bd30bbe8d1173..a382234748b4697895524abd1e752c1e7d40159f 100644 (file)
@@ -474,6 +474,7 @@ class Scope: public ZoneObject {
   // Computed via PropagateScopeInfo.
   bool outer_scope_calls_sloppy_eval_;
   bool inner_scope_calls_eval_;
+  bool inner_scope_contains_with_;
   bool force_eager_compilation_;
   bool force_context_allocation_;
 
@@ -551,7 +552,7 @@ class Scope: public ZoneObject {
   // Lookup a variable reference given by name recursively starting with this
   // scope. If the code is executed because of a call to 'eval', the context
   // parameter should be set to the calling context of 'eval'.
-  Variable* LookupRecursive(const AstRawString* name,
+  Variable* LookupRecursive(VariableProxy* proxy,
                             BindingKind* binding_kind,
                             AstNodeFactory<AstNullVisitor>* factory);
   MUST_USE_RESULT
@@ -563,7 +564,7 @@ class Scope: public ZoneObject {
                                    AstNodeFactory<AstNullVisitor>* factory);
 
   // Scope analysis.
-  bool PropagateScopeInfo(bool outer_scope_calls_sloppy_eval);
+  void PropagateScopeInfo(bool outer_scope_calls_sloppy_eval);
   bool HasTrivialContext() const;
 
   // Predicates.
index 301393849740b3242ec9fa7d4c5198d58cf9c34c..8a234e46a0af9ba785478d185c669ee4f6538334 100644 (file)
@@ -50,6 +50,7 @@ Variable::Variable(Scope* scope,
     is_valid_ref_(is_valid_ref),
     force_context_allocation_(false),
     is_used_(false),
+    maybe_assigned_(false),
     initialization_flag_(initialization_flag),
     interface_(interface) {
   // Var declared variables never need initialization.
index fdea0eedf1f240e3120763324d2a8bf3e68b4f43..58089b36ed9699463763c9681fbd2670c313f0a2 100644 (file)
@@ -82,7 +82,9 @@ class Variable: public ZoneObject {
     force_context_allocation_ = true;
   }
   bool is_used() { return is_used_; }
-  void set_is_used(bool flag) { is_used_ = flag; }
+  void set_is_used() { is_used_ = true; }
+  bool maybe_assigned() { return maybe_assigned_; }
+  void set_maybe_assigned() { maybe_assigned_ = true; }
 
   int initializer_position() { return initializer_position_; }
   void set_initializer_position(int pos) { initializer_position_ = pos; }
@@ -157,6 +159,7 @@ class Variable: public ZoneObject {
   // Usage info.
   bool force_context_allocation_;  // set by variable resolver
   bool is_used_;
+  bool maybe_assigned_;
   InitializationFlag initialization_flag_;
 
   // Module type info.
index 857089ea0662ca374051c07ae000c8da59a04a77..b36621742ae2824c01b72a538145b6263d184c8c 100644 (file)
@@ -38,6 +38,7 @@
 #include "src/objects.h"
 #include "src/parser.h"
 #include "src/preparser.h"
+#include "src/rewriter.h"
 #include "src/scanner-character-streams.h"
 #include "src/token.h"
 #include "src/utils.h"
@@ -2614,3 +2615,133 @@ TEST(RegressionLazyFunctionWithErrorWithArg) {
              "}\n"
              "this_is_lazy();\n");
 }
+
+
+TEST(InnerAssignment) {
+  i::Isolate* isolate = CcTest::i_isolate();
+  i::Factory* factory = isolate->factory();
+  i::HandleScope scope(isolate);
+  LocalContext env;
+
+  const char* prefix = "function f() {";
+  const char* midfix = " function g() {";
+  const char* suffix = "}}";
+  struct { const char* source; bool assigned; bool strict; } outers[] = {
+    // Actual assignments.
+    { "var x; var x = 5;", true, false },
+    { "var x; { var x = 5; }", true, false },
+    { "'use strict'; let x; x = 6;", true, true },
+    { "var x = 5; function x() {}", true, false },
+    // Actual non-assignments.
+    { "var x;", false, false },
+    { "var x = 5;", false, false },
+    { "'use strict'; let x;", false, true },
+    { "'use strict'; let x = 6;", false, true },
+    { "'use strict'; var x = 0; { let x = 6; }", false, true },
+    { "'use strict'; var x = 0; { let x; x = 6; }", false, true },
+    { "'use strict'; let x = 0; { let x = 6; }", false, true },
+    { "'use strict'; let x = 0; { let x; x = 6; }", false, true },
+    { "var x; try {} catch (x) { x = 5; }", false, false },
+    { "function x() {}", false, false },
+    // Eval approximation.
+    { "var x; eval('');", true, false },
+    { "eval(''); var x;", true, false },
+    { "'use strict'; let x; eval('');", true, true },
+    { "'use strict'; eval(''); let x;", true, true },
+    // Non-assignments not recognized, because the analysis is approximative.
+    { "var x; var x;", true, false },
+    { "var x = 5; var x;", true, false },
+    { "var x; { var x; }", true, false },
+    { "var x; function x() {}", true, false },
+    { "function x() {}; var x;", true, false },
+    { "var x; try {} catch (x) { var x = 5; }", true, false },
+  };
+  struct { const char* source; bool assigned; bool with; } inners[] = {
+    // Actual assignments.
+    { "x = 1;", true, false },
+    { "x++;", true, false },
+    { "++x;", true, false },
+    { "x--;", true, false },
+    { "--x;", true, false },
+    { "{ x = 1; }", true, false },
+    { "'use strict'; { let x; }; x = 0;", true, false },
+    { "'use strict'; { const x = 1; }; x = 0;", true, false },
+    { "'use strict'; { function x() {} }; x = 0;", true, false },
+    { "with ({}) { x = 1; }", true, true },
+    { "eval('');", true, false },
+    { "'use strict'; { let y; eval('') }", true, false },
+    { "function h() { x = 0; }", true, false },
+    { "(function() { x = 0; })", true, false },
+    { "(function() { x = 0; })", true, false },
+    { "with ({}) (function() { x = 0; })", true, true },
+    // Actual non-assignments.
+    { "", false, false },
+    { "x;", false, false },
+    { "var x;", false, false },
+    { "var x = 8;", false, false },
+    { "var x; x = 8;", false, false },
+    { "'use strict'; let x;", false, false },
+    { "'use strict'; let x = 8;", false, false },
+    { "'use strict'; let x; x = 8;", false, false },
+    { "'use strict'; const x = 8;", false, false },
+    { "function x() {}", false, false },
+    { "function x() { x = 0; }", false, false },
+    { "function h(x) { x = 0; }", false, false },
+    { "'use strict'; { let x; x = 0; }", false, false },
+    { "{ var x; }; x = 0;", false, false },
+    { "with ({}) {}", false, true },
+    { "var x; { with ({}) { x = 1; } }", false, true },
+    { "try {} catch(x) { x = 0; }", false, false },
+    { "try {} catch(x) { with ({}) { x = 1; } }", false, true },
+    // Eval approximation.
+    { "eval('');", true, false },
+    { "function h() { eval(''); }", true, false },
+    { "(function() { eval(''); })", true, false },
+    // Shadowing not recognized because of eval approximation.
+    { "var x; eval('');", true, false },
+    { "'use strict'; let x; eval('');", true, false },
+    { "try {} catch(x) { eval(''); }", true, false },
+    { "function x() { eval(''); }", true, false },
+    { "(function(x) { eval(''); })", true, false },
+  };
+
+  int prefix_len = Utf8LengthHelper(prefix);
+  int midfix_len = Utf8LengthHelper(midfix);
+  int suffix_len = Utf8LengthHelper(suffix);
+  for (unsigned i = 0; i < ARRAY_SIZE(outers); ++i) {
+    const char* outer = outers[i].source;
+    int outer_len = Utf8LengthHelper(outer);
+    for (unsigned j = 0; j < ARRAY_SIZE(inners); ++j) {
+      if (outers[i].strict && inners[j].with) continue;
+      const char* inner = inners[j].source;
+      int inner_len = Utf8LengthHelper(inner);
+      int len = prefix_len + outer_len + midfix_len + inner_len + suffix_len;
+      i::ScopedVector<char> program(len + 1);
+      i::SNPrintF(program, "%s%s%s%s%s", prefix, outer, midfix, inner, suffix);
+      i::Handle<i::String> source =
+          factory->InternalizeUtf8String(program.start());
+      source->PrintOn(stdout);
+      printf("\n");
+
+      i::Handle<i::Script> script = factory->NewScript(source);
+      i::CompilationInfoWithZone info(script);
+      i::Parser parser(&info);
+      parser.set_allow_harmony_scoping(true);
+      CHECK(parser.Parse());
+      CHECK(i::Rewriter::Rewrite(&info));
+      CHECK(i::Scope::Analyze(&info));
+      CHECK(info.function() != NULL);
+
+      i::Scope* scope = info.function()->scope();
+      CHECK_EQ(scope->inner_scopes()->length(), 1);
+      i::Scope* inner_scope = scope->inner_scopes()->at(0);
+      const i::AstRawString* var_name =
+          info.ast_value_factory()->GetOneByteString("x");
+      i::Variable* var = inner_scope->Lookup(var_name);
+      bool expected = outers[i].assigned || inners[j].assigned;
+      CHECK(var != NULL);
+      CHECK(var->is_used() || !expected);
+      CHECK(var->maybe_assigned() == expected);
+    }
+  }
+}