Statically check for assignments to const in harmony mode.
authorkeuchel@chromium.org <keuchel@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 5 Dec 2011 14:43:28 +0000 (14:43 +0000)
committerkeuchel@chromium.org <keuchel@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 5 Dec 2011 14:43:28 +0000 (14:43 +0000)
The ES.next draft rev 4 in section 11.13 reads:
It is a Syntax Error if the AssignmentExpression is contained in extended code
and the LeftHandSideExpression is an Identifier that does not statically resolve
to a declarative environment record binding or if the resolved binding is an
immutable binding.

This CL adds corresponding static checks for the immutable binding case.

TEST=mjsunit/harmony/block-const-assign

Review URL: http://codereview.chromium.org/8688007

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

src/ast.cc
src/ast.h
src/compiler.cc
src/messages.js
src/parser.cc
src/parser.h
src/scopes.cc
src/scopes.h
test/mjsunit/harmony/block-const-assign.js [new file with mode: 0644]

index 13e55894d58cb0560e3fcd39b89edb1735420652..079335622a9a4f89e58a0d46f3bd1e97c411f82d 100644 (file)
@@ -70,6 +70,7 @@ VariableProxy::VariableProxy(Isolate* isolate, Variable* var)
       var_(NULL),  // Will be set by the call to BindTo.
       is_this_(var->is_this()),
       is_trivial_(false),
+      is_lvalue_(false),
       position_(RelocInfo::kNoPosition) {
   BindTo(var);
 }
@@ -84,6 +85,7 @@ VariableProxy::VariableProxy(Isolate* isolate,
       var_(NULL),
       is_this_(is_this),
       is_trivial_(false),
+      is_lvalue_(false),
       position_(position) {
   // Names must be canonicalized for fast equality checks.
   ASSERT(name->IsSymbol());
index 805526af5e37425dead36dc6a8e7ccdc11ffcf5e..9b90d816d93ab3a54c9c571f81c70d1722621ee6 100644 (file)
--- a/src/ast.h
+++ b/src/ast.h
@@ -1159,12 +1159,17 @@ class VariableProxy: public Expression {
 
   bool IsArguments() { return var_ != NULL && var_->is_arguments(); }
 
+  bool IsLValue() {
+    return is_lvalue_;
+  }
+
   Handle<String> name() const { return name_; }
   Variable* var() const { return var_; }
   bool is_this() const { return is_this_; }
   int position() const { return position_; }
 
   void MarkAsTrivial() { is_trivial_ = true; }
+  void MarkAsLValue() { is_lvalue_ = true; }
 
   // Bind this proxy to the variable var.
   void BindTo(Variable* var);
@@ -1174,6 +1179,9 @@ class VariableProxy: public Expression {
   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_;
   int position_;
 };
 
index 16ccfa0cf4122b3dbae6f5f864e1501eb73e0af4..d2a4a0bfdc44c7daa2911c2e095e303a788f00d0 100644 (file)
@@ -398,7 +398,7 @@ static Handle<SharedFunctionInfo> MakeFunctionInfo(CompilationInfo* info) {
   FunctionLiteral* lit = info->function();
   LiveEditFunctionTracker live_edit_tracker(isolate, lit);
   if (!MakeCode(info)) {
-    isolate->StackOverflow();
+    if (!isolate->has_pending_exception()) isolate->StackOverflow();
     return Handle<SharedFunctionInfo>::null();
   }
 
index 5a3f12ee3a262fd5f82f4d43d75874cb402d7bf5..5310938a6612f75bdd6044b3715d48f879ad2273 100644 (file)
@@ -246,6 +246,7 @@ function FormatMessage(message) {
       "unprotected_const",            ["Illegal const declaration in unprotected statement context."],
       "cant_prevent_ext_external_array_elements", ["Cannot prevent extension of an object with external array elements"],
       "redef_external_array_element", ["Cannot redefine a property of an object with external array elements"],
+      "harmony_const_assign",         ["Assignment to constant variable."],
     ];
     var messages = { __proto__ : null };
     for (var i = 0; i < messagesDictionary.length; i += 2) {
index d834acb388ce965c786ce7d3d813d26c351857f5..7e648ede5a61dc030b58718e39e3d8c5a4db88ed 100644 (file)
@@ -2693,6 +2693,7 @@ Expression* Parser::ParseAssignmentExpression(bool accept_IN, bool* ok) {
     // Assignment to eval or arguments is disallowed in strict mode.
     CheckStrictModeLValue(expression, "strict_lhs_assignment", CHECK_OK);
   }
+  MarkAsLValue(expression);
 
   Token::Value op = Next();  // Get assignment operator.
   int pos = scanner().location().beg_pos;
@@ -2926,6 +2927,7 @@ Expression* Parser::ParseUnaryExpression(bool* ok) {
       // Prefix expression operand in strict mode may not be eval or arguments.
       CheckStrictModeLValue(expression, "strict_lhs_prefix", CHECK_OK);
     }
+    MarkAsLValue(expression);
 
     int position = scanner().location().beg_pos;
     return new(zone()) CountOperation(isolate(),
@@ -2961,6 +2963,7 @@ Expression* Parser::ParsePostfixExpression(bool* ok) {
       // Postfix expression operand in strict mode may not be eval or arguments.
       CheckStrictModeLValue(expression, "strict_lhs_prefix", CHECK_OK);
     }
+    MarkAsLValue(expression);
 
     Token::Value next = Next();
     int position = scanner().location().beg_pos;
@@ -4479,6 +4482,15 @@ Handle<String> Parser::ParseIdentifierName(bool* ok) {
 }
 
 
+void Parser::MarkAsLValue(Expression* expression) {
+  VariableProxy* proxy = expression != NULL
+      ? expression->AsVariableProxy()
+      : NULL;
+
+  if (proxy != NULL) proxy->MarkAsLValue();
+}
+
+
 // Checks LHS expression for assignment and prefix/postfix increment/decrement
 // in strict mode.
 void Parser::CheckStrictModeLValue(Expression* expression,
index 75f8e10931ce0766098e727c4353f5e9ed523aaa..146d7bb9af0c53ebf4c311807e3485a543c49e11 100644 (file)
@@ -661,6 +661,11 @@ class Parser {
                                                bool* is_set,
                                                bool* ok);
 
+  // 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.
+  void MarkAsLValue(Expression* expression);
+
   // Strict mode validation of LValue expressions
   void CheckStrictModeLValue(Expression* expression,
                              const char* error,
index e05ca172518cdf8cdc7abf4eb9ef697673bd1c9e..4a6d0a7fae2f4e2b2a2d01913d9ae6b07b2f56b0 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "bootstrapper.h"
 #include "compiler.h"
+#include "messages.h"
 #include "scopeinfo.h"
 
 #include "allocation-inl.h"
@@ -284,8 +285,25 @@ bool Scope::Analyze(CompilationInfo* info) {
   }
 #endif
 
+  if (FLAG_harmony_scoping) {
+    VariableProxy* proxy = scope->CheckAssignmentToConst();
+    if (proxy != NULL) {
+      // Found an assignment to const. Throw a syntax error.
+      MessageLocation location(info->script(),
+                               proxy->position(),
+                               proxy->position());
+      Isolate* isolate = info->isolate();
+      Factory* factory = isolate->factory();
+      Handle<JSArray> array = factory->NewJSArray(0);
+      Handle<Object> result =
+          factory->NewSyntaxError("harmony_const_assign", array);
+      isolate->Throw(*result, &location);
+      return false;
+    }
+  }
+
   info->SetScope(scope);
-  return true;  // Can not fail.
+  return true;
 }
 
 
@@ -554,6 +572,29 @@ Declaration* Scope::CheckConflictingVarDeclarations() {
 }
 
 
+VariableProxy* Scope::CheckAssignmentToConst() {
+  // Check this scope.
+  if (is_extended_mode()) {
+    for (int i = 0; i < unresolved_.length(); i++) {
+      ASSERT(unresolved_[i]->var() != NULL);
+      if (unresolved_[i]->var()->is_const_mode() &&
+          unresolved_[i]->IsLValue()) {
+        return unresolved_[i];
+      }
+    }
+  }
+
+  // Check inner scopes.
+  for (int i = 0; i < inner_scopes_.length(); i++) {
+    VariableProxy* proxy = inner_scopes_[i]->CheckAssignmentToConst();
+    if (proxy != NULL) return proxy;
+  }
+
+  // No assignments to const found.
+  return NULL;
+}
+
+
 void Scope::CollectStackAndContextLocals(ZoneList<Variable*>* stack_locals,
                                          ZoneList<Variable*>* context_locals) {
   ASSERT(stack_locals != NULL);
index 523a251fae2ffd18f5bb1148a2c17b575b6faf8b..af0449e93c7dea6895f06d42cf7a95ca7624071c 100644 (file)
@@ -187,6 +187,11 @@ class Scope: public ZoneObject {
   // scope over a let binding of the same name.
   Declaration* CheckConflictingVarDeclarations();
 
+  // For harmony block scoping mode: Check if the scope has variable proxies
+  // that are used as lvalues and point to const variables. Assumes that scopes
+  // have been analyzed and variables been resolved.
+  VariableProxy* CheckAssignmentToConst();
+
   // ---------------------------------------------------------------------------
   // Scope-specific info.
 
diff --git a/test/mjsunit/harmony/block-const-assign.js b/test/mjsunit/harmony/block-const-assign.js
new file mode 100644 (file)
index 0000000..8297a55
--- /dev/null
@@ -0,0 +1,131 @@
+// Copyright 2011 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --harmony-scoping
+
+// Test that we throw early syntax errors in harmony mode
+// when using an immutable binding in an assigment or with
+// prefix/postfix decrement/increment operators.
+// TODO(ES6): properly activate extended mode
+"use strict";
+
+
+// Function local const.
+function constDecl0(use) {
+  return "(function() { const constvar = 1; " + use + "; });";
+}
+
+
+function constDecl1(use) {
+  return "(function() { " + use + "; const constvar = 1; });";
+}
+
+
+// Function local const, assign from eval.
+function constDecl2(use) {
+  use = "eval('(function() { " + use + " })')";
+  return "(function() { const constvar = 1; " + use + "; })();";
+}
+
+
+function constDecl3(use) {
+  use = "eval('(function() { " + use + " })')";
+  return "(function() { " + use + "; const constvar = 1; })();";
+}
+
+
+// Block local const.
+function constDecl4(use) {
+  return "(function() { { const constvar = 1; " + use + "; } });";
+}
+
+
+function constDecl5(use) {
+  return "(function() { { " + use + "; const constvar = 1; } });";
+}
+
+
+// Block local const, assign from eval.
+function constDecl6(use) {
+  use = "eval('(function() {" + use + "})')";
+  return "(function() { { const constvar = 1; " + use + "; } })();";
+}
+
+
+function constDecl7(use) {
+  use = "eval('(function() {" + use + "})')";
+  return "(function() { { " + use + "; const constvar = 1; } })();";
+}
+
+
+// Function expression name.
+function constDecl8(use) {
+  return "(function constvar() { " + use + "; });";
+}
+
+
+// Function expression name, assign from eval.
+function constDecl9(use) {
+  use = "eval('(function(){" + use + "})')";
+  return "(function constvar() { " + use + "; })();";
+}
+
+let decls = [ constDecl0,
+              constDecl1,
+              constDecl2,
+              constDecl3,
+              constDecl4,
+              constDecl5,
+              constDecl6,
+              constDecl7,
+              constDecl8,
+              constDecl9
+              ];
+let uses = [ 'constvar = 1;',
+             'constvar += 1;',
+             '++constvar;',
+             'constvar++;'
+             ];
+
+function Test(d,u) {
+  'use strict';
+  try {
+    print(d(u));
+    eval(d(u));
+  } catch (e) {
+    assertInstanceof(e, SyntaxError);
+    assertTrue(e.toString().indexOf("Assignment to constant variable") >= 0);
+    return;
+  }
+  assertUnreachable();
+}
+
+for (var d = 0; d < decls.length; ++d) {
+  for (var u = 0; u < uses.length; ++u) {
+    Test(decls[d], uses[u]);
+  }
+}