Object.freeze() and seal() should throw if [[PreventExtensions]]() fails.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Oct 2016 03:20:53 +0000 (03:20 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 8 Oct 2016 03:20:53 +0000 (03:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=163160

Reviewed by Saam Barati.

JSTests:

* stress/object-freeze-with-proxy-preventExtensions.js: Added.
* stress/object-seal-with-proxy-preventExtensions.js: Added.

Source/JavaScriptCore:

See https://tc39.github.io/ecma262/#sec-object.freeze,
https://tc39.github.io/ecma262/#sec-object.seal, and
https://tc39.github.io/ecma262/#sec-setintegritylevel.  We need to call
preventExtensions first before proceeding to freeze / seal the object.  If
preventExtensions fails, we should throw a TypeError.

* runtime/ObjectConstructor.cpp:
(JSC::setIntegrityLevel):
(JSC::objectConstructorSeal):
(JSC::objectConstructorFreeze):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@206948 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/object-freeze-with-proxy-preventExtensions.js [new file with mode: 0644]
JSTests/stress/object-seal-with-proxy-preventExtensions.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/ObjectConstructor.cpp

index 3e6c51b..270d2a5 100644 (file)
@@ -1,3 +1,13 @@
+2016-10-07  Mark Lam  <mark.lam@apple.com>
+
+        Object.freeze() and seal() should throw if [[PreventExtensions]]() fails.
+        https://bugs.webkit.org/show_bug.cgi?id=163160
+
+        Reviewed by Saam Barati.
+
+        * stress/object-freeze-with-proxy-preventExtensions.js: Added.
+        * stress/object-seal-with-proxy-preventExtensions.js: Added.
+
 2016-10-05  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DOMJIT] Add initial CheckDOM and CallDOM implementations
diff --git a/JSTests/stress/object-freeze-with-proxy-preventExtensions.js b/JSTests/stress/object-freeze-with-proxy-preventExtensions.js
new file mode 100644 (file)
index 0000000..c7cbc05
--- /dev/null
@@ -0,0 +1,29 @@
+// See https://tc39.github.io/ecma262/#sec-object.freeze
+// See https://tc39.github.io/ecma262/#sec-setintegritylevel
+
+var x = [10];
+var visited = [];
+
+var proxy = new Proxy(x, {
+    preventExtensions() {
+        visited.push("proxy_preventExtensions");
+        return false;
+    }
+});
+
+var exception;
+try  {
+    visited.push("before_freeze");
+    Object.freeze(proxy);
+    visited.push("after_freeze");
+} catch (e) {
+    visited.push("catch");
+    exception = e;
+}
+
+var exceptionStr = "" + exception;
+if (!exceptionStr.startsWith("TypeError:"))
+    throw "Did not throw expected TypeError";
+
+if (visited != "before_freeze,proxy_preventExtensions,catch")
+    throw "ERROR: visited = " + visited;
diff --git a/JSTests/stress/object-seal-with-proxy-preventExtensions.js b/JSTests/stress/object-seal-with-proxy-preventExtensions.js
new file mode 100644 (file)
index 0000000..6da1db8
--- /dev/null
@@ -0,0 +1,29 @@
+// See https://tc39.github.io/ecma262/#sec-object.seal
+// See https://tc39.github.io/ecma262/#sec-setintegritylevel
+
+var x = [10];
+var visited = [];
+
+var proxy = new Proxy(x, {
+    preventExtensions() {
+        visited.push("proxy_preventExtensions");
+        return false;
+    }
+});
+
+var exception;
+try  {
+    visited.push("before_seal");
+    Object.seal(proxy);
+    visited.push("after_seal");
+} catch (e) {
+    visited.push("catch");
+    exception = e;
+}
+
+var exceptionStr = "" + exception;
+if (!exceptionStr.startsWith("TypeError:"))
+    throw "Did not throw expected TypeError";
+
+if (visited != "before_seal,proxy_preventExtensions,catch")
+    throw "ERROR: visited = " + visited;
index eecc79e..7fa3231 100644 (file)
@@ -1,3 +1,21 @@
+2016-10-07  Mark Lam  <mark.lam@apple.com>
+
+        Object.freeze() and seal() should throw if [[PreventExtensions]]() fails.
+        https://bugs.webkit.org/show_bug.cgi?id=163160
+
+        Reviewed by Saam Barati.
+
+        See https://tc39.github.io/ecma262/#sec-object.freeze,
+        https://tc39.github.io/ecma262/#sec-object.seal, and
+        https://tc39.github.io/ecma262/#sec-setintegritylevel.  We need to call
+        preventExtensions first before proceeding to freeze / seal the object.  If
+        preventExtensions fails, we should throw a TypeError.
+
+        * runtime/ObjectConstructor.cpp:
+        (JSC::setIntegrityLevel):
+        (JSC::objectConstructorSeal):
+        (JSC::objectConstructorFreeze):
+
 2016-10-06  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DOMJIT] Support slow path call
index 0fa0ce6..8bfa691 100644 (file)
@@ -471,6 +471,51 @@ EncodedJSValue JSC_HOST_CALL objectConstructorCreate(ExecState* exec)
     return JSValue::encode(defineProperties(exec, newObject, asObject(exec->argument(1))));
 }
 
+enum class IntegrityLevel {
+    Sealed,
+    Frozen
+};
+
+template<IntegrityLevel level>
+bool setIntegrityLevel(ExecState* exec, VM& vm, JSObject* object)
+{
+    // See https://tc39.github.io/ecma262/#sec-setintegritylevel.
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    bool success = object->methodTable(vm)->preventExtensions(object, exec);
+    RETURN_IF_EXCEPTION(scope, false);
+    if (UNLIKELY(!success))
+        return false;
+
+    PropertyNameArray properties(exec, PropertyNameMode::StringsAndSymbols);
+    object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(DontEnumPropertiesMode::Include));
+    RETURN_IF_EXCEPTION(scope, false);
+
+    PropertyNameArray::const_iterator end = properties.end();
+    for (PropertyNameArray::const_iterator iter = properties.begin(); iter != end; ++iter) {
+        Identifier propertyName = *iter;
+        if (vm.propertyNames->isPrivateName(propertyName))
+            continue;
+
+        PropertyDescriptor desc;
+        if (level == IntegrityLevel::Sealed)
+            desc.setConfigurable(false);
+        else {
+            if (!object->getOwnPropertyDescriptor(exec, propertyName, desc))
+                continue;
+
+            if (desc.isDataDescriptor())
+                desc.setWritable(false);
+
+            desc.setConfigurable(false);
+        }
+
+        object->methodTable(vm)->defineOwnProperty(object, exec, propertyName, desc, true);
+        RETURN_IF_EXCEPTION(scope, false);
+    }
+    return true;
+}
+    
 EncodedJSValue JSC_HOST_CALL objectConstructorSeal(ExecState* exec)
 {
     VM& vm = exec->vm();
@@ -487,31 +532,13 @@ EncodedJSValue JSC_HOST_CALL objectConstructorSeal(ExecState* exec)
         return JSValue::encode(obj);
     }
 
-    // 2. For each named own property name P of O,
-    PropertyNameArray properties(exec, PropertyNameMode::StringsAndSymbols);
-    object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(DontEnumPropertiesMode::Include));
+    bool success = setIntegrityLevel<IntegrityLevel::Sealed>(exec, vm, object);
     RETURN_IF_EXCEPTION(scope, encodedJSValue());
-    PropertyNameArray::const_iterator end = properties.end();
-    for (PropertyNameArray::const_iterator iter = properties.begin(); iter != end; ++iter) {
-        Identifier propertyName = *iter;
-        if (exec->propertyNames().isPrivateName(propertyName))
-            continue;
-        // a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P.
-        PropertyDescriptor desc;
-        if (!object->getOwnPropertyDescriptor(exec, propertyName, desc))
-            continue;
-        // b. If desc.[[Configurable]] is true, set desc.[[Configurable]] to false.
-        desc.setConfigurable(false);
-        // c. Call the [[DefineOwnProperty]] internal method of O with P, desc, and true as arguments.
-        object->methodTable(vm)->defineOwnProperty(object, exec, propertyName, desc, true);
-        RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    if (!success) {
+        throwTypeError(exec, scope, ASCIILiteral("Unable to prevent extension in Object.seal"));
+        return encodedJSValue();
     }
 
-    // 3. Set the [[Extensible]] internal property of O to false.
-    object->methodTable(vm)->preventExtensions(object, exec);
-    RETURN_IF_EXCEPTION(scope, encodedJSValue());
-
-    // 4. Return O.
     return JSValue::encode(obj);
 }
 
@@ -525,35 +552,10 @@ JSObject* objectConstructorFreeze(ExecState* exec, JSObject* object)
         return object;
     }
 
-    // 2. For each named own property name P of O,
-    PropertyNameArray properties(exec, PropertyNameMode::StringsAndSymbols);
-    object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(DontEnumPropertiesMode::Include));
+    bool success = setIntegrityLevel<IntegrityLevel::Frozen>(exec, vm, object);
     RETURN_IF_EXCEPTION(scope, nullptr);
-    PropertyNameArray::const_iterator end = properties.end();
-    for (PropertyNameArray::const_iterator iter = properties.begin(); iter != end; ++iter) {
-        Identifier propertyName = *iter;
-        if (exec->propertyNames().isPrivateName(propertyName))
-            continue;
-        // a. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with P.
-        PropertyDescriptor desc;
-        if (!object->getOwnPropertyDescriptor(exec, propertyName, desc))
-            continue;
-        // b. If IsDataDescriptor(desc) is true, then
-        // i. If desc.[[Writable]] is true, set desc.[[Writable]] to false.
-        if (desc.isDataDescriptor())
-            desc.setWritable(false);
-        // c. If desc.[[Configurable]] is true, set desc.[[Configurable]] to false.
-        desc.setConfigurable(false);
-        // d. Call the [[DefineOwnProperty]] internal method of O with P, desc, and true as arguments.
-        object->methodTable(vm)->defineOwnProperty(object, exec, propertyName, desc, true);
-        RETURN_IF_EXCEPTION(scope, nullptr);
-    }
-
-    // 3. Set the [[Extensible]] internal property of O to false.
-    object->methodTable(vm)->preventExtensions(object, exec);
-    RETURN_IF_EXCEPTION(scope, nullptr);
-
-    // 4. Return O.
+    if (!success)
+        return throwTypeError(exec, scope, ASCIILiteral("Unable to prevent extension in Object.freeze"));
     return object;
 }