Implement ES5 erratum: global declarations shadow inherited properties.
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 16 Apr 2012 13:20:50 +0000 (13:20 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Mon, 16 Apr 2012 13:20:50 +0000 (13:20 +0000)
I also discovered that our treatment of const declarations is inconsistent
when inside a global eval under 'with' (i.e., when created by
DeclareContextSlots). That is,

  var x;
  eval("const x = 9")

and

  var x;
  eval("with({}) const x = 9")

differ (the former assigns 9, the latter throws). This appears to be an
oversight from earlier changes to our const semantics (the latter shouldn't
throw either). Fixing this is a separate issue, though (and one that doesn't
seem quite worthwhile).

R=mstarzinger@chromium.org
BUG=v8:1991,80591
TEST=

Review URL: https://chromiumcodereview.appspot.com/10067010

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

src/contexts.h
src/objects.cc
src/parser.cc
src/runtime.cc
test/cctest/test-api.cc
test/cctest/test-decls.cc
test/mjsunit/declare-locally.js
test/mjsunit/mjsunit.js
test/mjsunit/regress/regress-1119.js
test/mjsunit/regress/regress-115452.js
test/mjsunit/regress/regress-1170.js

index af5cb03..647c15c 100644 (file)
@@ -397,7 +397,7 @@ class Context: public FixedArray {
   GLOBAL_CONTEXT_FIELDS(GLOBAL_CONTEXT_FIELD_ACCESSORS)
 #undef GLOBAL_CONTEXT_FIELD_ACCESSORS
 
-  // Lookup the the slot called name, starting with the current context.
+  // Lookup the slot called name, starting with the current context.
   // There are three possibilities:
   //
   // 1) result->IsContext():
index 961876b..f96b69b 100644 (file)
@@ -3025,7 +3025,6 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
     String* name,
     Object* value,
     PropertyAttributes attributes) {
-
   // Make sure that the top context does not change when doing callbacks or
   // interceptor calls.
   AssertNoContextChange ncc;
@@ -3094,7 +3093,6 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
       return ConvertDescriptorToFieldAndMapTransition(name, value, attributes);
     case HANDLER:
       UNREACHABLE();
-      return value;
   }
   UNREACHABLE();  // keep the compiler happy
   return value;
index 516e651..5bce764 100644 (file)
@@ -2254,7 +2254,7 @@ Block* Parser::ParseVariableDeclarations(
     // Global variable declarations must be compiled in a specific
     // way. When the script containing the global variable declaration
     // is entered, the global variable must be declared, so that if it
-    // doesn't exist (not even in a prototype of the global object) it
+    // doesn't exist (on the global object itself, see ES5 errata) it
     // gets created with an initial undefined value. This is handled
     // by the declarations part of the function representing the
     // top-level global code; see Runtime::DeclareGlobalVariable. If
index 868c4bc..ed89c6e 100644 (file)
@@ -1298,8 +1298,9 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeclareGlobals) {
     if (is_var || is_const) {
       // Lookup the property in the global object, and don't set the
       // value of the variable if the property is already there.
+      // Do the lookup locally only, see ES5 errata.
       LookupResult lookup(isolate);
-      global->Lookup(*name, &lookup);
+      global->LocalLookup(*name, &lookup);
       if (lookup.IsProperty()) {
         // We found an existing property. Unless it was an interceptor
         // that claims the property is absent, skip this declaration.
@@ -1336,40 +1337,28 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeclareGlobals) {
 
     LanguageMode language_mode = DeclareGlobalsLanguageMode::decode(flags);
 
-    // Safari does not allow the invocation of callback setters for
-    // function declarations. To mimic this behavior, we do not allow
-    // the invocation of setters for function values. This makes a
-    // difference for global functions with the same names as event
-    // handlers such as "function onload() {}". Firefox does call the
-    // onload setter in those case and Safari does not. We follow
-    // Safari for compatibility.
-    if (is_function || is_module) {
-      if (lookup.IsProperty() && (lookup.type() != INTERCEPTOR)) {
-        // Do not overwrite READ_ONLY properties.
-        if (lookup.GetAttributes() & READ_ONLY) {
-          if (language_mode != CLASSIC_MODE) {
-            Handle<Object> args[] = { name };
-            return isolate->Throw(*isolate->factory()->NewTypeError(
-                "strict_cannot_assign", HandleVector(args, ARRAY_SIZE(args))));
-          }
-          continue;
+    if (!lookup.IsProperty() || is_function || is_module) {
+      // If the local property exists, check that we can reconfigure it
+      // as required for function declarations.
+      if (lookup.IsProperty() && lookup.IsDontDelete()) {
+        if (lookup.IsReadOnly() || lookup.IsDontEnum() ||
+            lookup.type() == CALLBACKS) {
+          return ThrowRedeclarationError(
+              isolate, is_function ? "function" : "module", name);
         }
-        // Do not change DONT_DELETE to false from true.
-        attr |= lookup.GetAttributes() & DONT_DELETE;
+        // If the existing property is not configurable, keep its attributes.
+        attr = lookup.GetAttributes();
       }
-      PropertyAttributes attributes = static_cast<PropertyAttributes>(attr);
-
-      RETURN_IF_EMPTY_HANDLE(
-          isolate,
-          JSObject::SetLocalPropertyIgnoreAttributes(global, name, value,
-                                                     attributes));
+      // Define or redefine own property.
+      RETURN_IF_EMPTY_HANDLE(isolate,
+          JSObject::SetLocalPropertyIgnoreAttributes(
+              global, name, value, static_cast<PropertyAttributes>(attr)));
     } else {
-      RETURN_IF_EMPTY_HANDLE(
-          isolate,
-          JSReceiver::SetProperty(global, name, value,
-                                  static_cast<PropertyAttributes>(attr),
-                                  language_mode == CLASSIC_MODE
-                                      ? kNonStrictMode : kStrictMode));
+      // Do a [[Put]] on the existing (own) property.
+      RETURN_IF_EMPTY_HANDLE(isolate,
+          JSObject::SetProperty(
+              global, name, value, static_cast<PropertyAttributes>(attr),
+              language_mode == CLASSIC_MODE ? kNonStrictMode : kStrictMode));
     }
   }
 
@@ -1402,6 +1391,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeclareContextSlot) {
 
   if (attributes != ABSENT) {
     // The name was declared before; check for conflicting re-declarations.
+    // Note: this is actually inconsistent with what happens for globals (where
+    // we silently ignore such declarations).
     if (((attributes & READ_ONLY) != 0) || (mode == READ_ONLY)) {
       // Functions are not read-only.
       ASSERT(mode != READ_ONLY || initial_value->IsTheHole());
@@ -1464,9 +1455,14 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DeclareContextSlot) {
         return ThrowRedeclarationError(isolate, "const", name);
       }
     }
-    RETURN_IF_EMPTY_HANDLE(
-        isolate,
-        JSReceiver::SetProperty(object, name, value, mode, kNonStrictMode));
+    if (object->IsJSGlobalObject()) {
+      // Define own property on the global object.
+      RETURN_IF_EMPTY_HANDLE(isolate,
+         JSObject::SetLocalPropertyIgnoreAttributes(object, name, value, mode));
+    } else {
+      RETURN_IF_EMPTY_HANDLE(isolate,
+         JSReceiver::SetProperty(object, name, value, mode, kNonStrictMode));
+    }
   }
 
   return isolate->heap()->undefined_value();
index 0232363..ed5de37 100644 (file)
@@ -12441,19 +12441,15 @@ THREADED_TEST(GetCallingContext) {
 
 
 // Check that a variable declaration with no explicit initialization
-// value does not shadow an existing property in the prototype chain.
-//
-// This is consistent with Firefox and Safari.
-//
-// See http://crbug.com/12548.
+// value does shadow an existing property in the prototype chain.
 THREADED_TEST(InitGlobalVarInProtoChain) {
   v8::HandleScope scope;
   LocalContext context;
   // Introduce a variable in the prototype chain.
   CompileRun("__proto__.x = 42");
-  v8::Handle<v8::Value> result = CompileRun("var x; x");
+  v8::Handle<v8::Value> result = CompileRun("var x = 43; x");
   CHECK(!result->IsUndefined());
-  CHECK_EQ(42, result->Int32Value());
+  CHECK_EQ(43, result->Int32Value());
 }
 
 
index aa733c7..e3d6158 100644 (file)
@@ -535,17 +535,17 @@ TEST(ExistsInPrototype) {
 
   { ExistsInPrototypeContext context;
     context.Check("var x; x",
-                  1,  // get
+                  0,  // get
                   0,
-                  1,  // declaration
-                  EXPECT_EXCEPTION);
+                  0,  // declaration
+                  EXPECT_RESULT, Undefined());
   }
 
   { ExistsInPrototypeContext context;
     context.Check("var x = 0; x",
                   0,
                   0,
-                  1,  // declaration
+                  0,  // declaration
                   EXPECT_RESULT, Number::New(0));
   }
 
@@ -553,7 +553,7 @@ TEST(ExistsInPrototype) {
     context.Check("const x; x",
                   0,
                   0,
-                  1,  // declaration
+                  0,  // declaration
                   EXPECT_RESULT, Undefined());
   }
 
@@ -561,7 +561,7 @@ TEST(ExistsInPrototype) {
     context.Check("const x = 0; x",
                   0,
                   0,
-                  1,  // declaration
+                  0,  // declaration
                   EXPECT_RESULT, Number::New(0));
   }
 }
@@ -589,7 +589,7 @@ TEST(AbsentInPrototype) {
     context.Check("if (false) { var x = 0; }; x",
                   0,
                   0,
-                  1,  // declaration
+                  0,  // declaration
                   EXPECT_RESULT, Undefined());
   }
 }
index 93fcb85..458ac7e 100644 (file)
@@ -36,8 +36,8 @@
 this.__proto__.foo = 42;
 this.__proto__.bar = 87;
 
-eval("assertEquals(42, foo); var foo = 87;");
+eval("assertEquals(undefined, foo); var foo = 87;");
 assertEquals(87, foo);
 
-eval("assertEquals(87, bar); const bar = 42;");
+eval("assertEquals(undefined, bar); const bar = 42;");
 assertEquals(42, bar);
index 033c78f..65fb301 100644 (file)
@@ -75,7 +75,7 @@ var assertTrue;
 // Checks that the found value is false.
 var assertFalse;
 
-// Checks that the found value is null. Kept for historical compatability,
+// Checks that the found value is null. Kept for historical compatibility,
 // please just use assertEquals(null, expected).
 var assertNull;
 
index 16b2e4f..1163ca0 100644 (file)
 // Test runtime declaration of properties with var which are intercepted
 // by JS accessors.
 
-__proto__.__defineSetter__("x", function() { hasBeenInvoked = true; });
-__proto__.__defineSetter__("y", function() { throw 'exception'; });
+this.__defineSetter__("x", function() { hasBeenInvoked = true; });
+this.__defineSetter__("y", function() { throw 'exception'; });
 
 var hasBeenInvoked = false;
 eval("try { } catch (e) { var x = false; }");
 assertTrue(hasBeenInvoked);
 
-var exception;
+// This has to run in global scope, so cannot use assertThrows...
 try {
   eval("try { } catch (e) { var y = false; }");
+  assertUnreachable();
 } catch (e) {
-  exception = e;
+  assertEquals('exception', e);
 }
-assertEquals('exception', exception);
index 7e424ed..f745e1b 100644 (file)
 
 // Test that a function declaration cannot overwrite a read-only property.
 
-print(0)
 function foobl() {}
 assertTrue(typeof this.foobl == "function");
 assertTrue(Object.getOwnPropertyDescriptor(this, "foobl").writable);
 
-print(1)
 Object.defineProperty(this, "foobl", {value: 1, writable: false});
 assertSame(1, this.foobl);
 assertFalse(Object.getOwnPropertyDescriptor(this, "foobl").writable);
 
-print(2)
-eval("function foobl() {}");
+// This has to run in global scope, so cannot use assertThrows...
+try {
+  eval("function foobl() {}");  // Should throw.
+  assertUnreachable();
+} catch (e) {
+  assertInstanceof(e, TypeError);
+}
 assertSame(1, this.foobl);
-assertFalse(Object.getOwnPropertyDescriptor(this, "foobl").writable);
-
-print(3)
-eval("function foobl() {}");
-assertSame(1, this.foobl);
-assertFalse(Object.getOwnPropertyDescriptor(this, "foobl").writable);
index 66ed9f2..eb3f3c7 100644 (file)
 
 var setter_value = 0;
 
-__proto__.__defineSetter__("a", function(v) { setter_value = v; });
+this.__defineSetter__("a", function(v) { setter_value = v; });
 eval("var a = 1");
 assertEquals(1, setter_value);
-assertFalse(this.hasOwnProperty("a"));
+assertFalse("value" in Object.getOwnPropertyDescriptor(this, "a"));
 
 eval("with({}) { eval('var a = 2') }");
 assertEquals(2, setter_value);
-assertFalse(this.hasOwnProperty("a"));
+assertFalse("value" in Object.getOwnPropertyDescriptor(this, "a"));
 
 // Function declarations are treated specially to match Safari. We do
 // not call setters for them.
+this.__defineSetter__("a", function(v) { assertUnreachable(); });
 eval("function a() {}");
-assertTrue(this.hasOwnProperty("a"));
+assertTrue("value" in Object.getOwnPropertyDescriptor(this, "a"));
 
-__proto__.__defineSetter__("b", function(v) { assertUnreachable(); });
-var exception = false;
+this.__defineSetter__("b", function(v) { setter_value = v; });
 try {
-  eval("const b = 23");
+  eval("const b = 3");
 } catch(e) {
-  exception = true;
-  assertTrue(/TypeError/.test(e));
+  assertUnreachable();
 }
-assertFalse(exception);
+assertEquals(3, setter_value);
 
-exception = false;
 try {
   eval("with({}) { eval('const b = 23') }");
 } catch(e) {
-  exception = true;
-  assertTrue(/TypeError/.test(e));
+  assertInstanceof(e, TypeError);
 }
-assertTrue(exception);
 
-__proto__.__defineSetter__("c", function(v) { throw 42; });
-exception = false;
+this.__defineSetter__("c", function(v) { throw 42; });
 try {
   eval("var c = 1");
+  assertUnreachable();
 } catch(e) {
-  exception = true;
   assertEquals(42, e);
-  assertFalse(this.hasOwnProperty("c"));
+  assertFalse("value" in Object.getOwnPropertyDescriptor(this, "c"));
+}
+
+
+
+
+__proto__.__defineSetter__("aa", function(v) { assertUnreachable(); });
+eval("var aa = 1");
+assertTrue(this.hasOwnProperty("aa"));
+
+__proto__.__defineSetter__("bb", function(v) { assertUnreachable(); });
+eval("with({}) { eval('var bb = 2') }");
+assertTrue(this.hasOwnProperty("bb"));
+
+// Function declarations are treated specially to match Safari. We do
+// not call setters for them.
+__proto__.__defineSetter__("cc", function(v) { assertUnreachable(); });
+eval("function cc() {}");
+assertTrue(this.hasOwnProperty("cc"));
+
+__proto__.__defineSetter__("dd", function(v) { assertUnreachable(); });
+try {
+  eval("const dd = 23");
+} catch(e) {
+  assertUnreachable();
+}
+
+try {
+  eval("with({}) { eval('const dd = 23') }");
+} catch(e) {
+  assertInstanceof(e, TypeError);
 }
-assertTrue(exception);