[ES6] Implement Proxy.[[PreventExtensions]]
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Mar 2016 23:42:33 +0000 (23:42 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 1 Mar 2016 23:42:33 +0000 (23:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=154873

Reviewed by Oliver Hunt.

This patch is a direct implementation of Proxy.[[PreventExtensions]] with respect to section 9.5.4
of the ECMAScript 6 spec.
https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions

* runtime/ProxyObject.cpp:
(JSC::ProxyObject::deletePropertyByIndex):
(JSC::ProxyObject::performPreventExtensions):
(JSC::ProxyObject::preventExtensions):
(JSC::ProxyObject::visitChildren):
* runtime/ProxyObject.h:
* tests/es6.yaml:
* tests/stress/proxy-prevent-extensions.js: Added.
(assert):
(throw.new.Error.let.handler.get preventExtensions):
(throw.new.Error):
(assert.let.handler.preventExtensions):
(assert.):
(let.handler.preventExtensions):
(assert.Object.isSealed.let.handler.preventExtensions):
(assert.Object.isSealed):

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

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/ProxyObject.cpp
Source/JavaScriptCore/runtime/ProxyObject.h
Source/JavaScriptCore/tests/es6.yaml
Source/JavaScriptCore/tests/stress/proxy-prevent-extensions.js [new file with mode: 0644]

index 911b1f5..4f04fed 100644 (file)
@@ -1,3 +1,31 @@
+2016-03-01  Saam barati  <sbarati@apple.com>
+
+        [ES6] Implement Proxy.[[PreventExtensions]]
+        https://bugs.webkit.org/show_bug.cgi?id=154873
+
+        Reviewed by Oliver Hunt.
+
+        This patch is a direct implementation of Proxy.[[PreventExtensions]] with respect to section 9.5.4
+        of the ECMAScript 6 spec.
+        https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions
+
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::deletePropertyByIndex):
+        (JSC::ProxyObject::performPreventExtensions):
+        (JSC::ProxyObject::preventExtensions):
+        (JSC::ProxyObject::visitChildren):
+        * runtime/ProxyObject.h:
+        * tests/es6.yaml:
+        * tests/stress/proxy-prevent-extensions.js: Added.
+        (assert):
+        (throw.new.Error.let.handler.get preventExtensions):
+        (throw.new.Error):
+        (assert.let.handler.preventExtensions):
+        (assert.):
+        (let.handler.preventExtensions):
+        (assert.Object.isSealed.let.handler.preventExtensions):
+        (assert.Object.isSealed):
+
 2016-03-01  Filip Pizlo  <fpizlo@apple.com>
 
         FTL should simplify StringReplace with an empty replacement string
index 40cfc27..805f879 100644 (file)
@@ -597,6 +597,54 @@ bool ProxyObject::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned
     return thisObject->performDelete(exec, ident.impl(), performDefaultDelete);
 }
 
+bool ProxyObject::performPreventExtensions(ExecState* exec)
+{
+    VM& vm = exec->vm();
+
+    JSValue handlerValue = this->handler();
+    if (handlerValue.isNull()) {
+        throwVMTypeError(exec, ASCIILiteral("Proxy 'handler' is null. It should be an Object."));
+        return false;
+    }
+
+    JSObject* handler = jsCast<JSObject*>(handlerValue);
+    CallData callData;
+    CallType callType;
+    JSValue preventExtensionsMethod = handler->getMethod(exec, callData, callType, makeIdentifier(vm, "preventExtensions"), ASCIILiteral("'preventExtensions' property of a Proxy's handler should be callable."));
+    if (exec->hadException())
+        return false;
+    JSObject* target = this->target();
+    if (preventExtensionsMethod.isUndefined())
+        return target->methodTable(vm)->preventExtensions(target, exec);
+
+    MarkedArgumentBuffer arguments;
+    arguments.append(target);
+    JSValue trapResult = call(exec, preventExtensionsMethod, callType, callData, handler, arguments);
+    if (exec->hadException())
+        return false;
+
+    bool trapResultAsBool = trapResult.toBoolean(exec);
+    if (exec->hadException())
+        return false;
+
+    if (trapResultAsBool) {
+        bool targetIsExtensible = target->isExtensibleInline(exec);
+        if (exec->hadException())
+            return false;
+        if (targetIsExtensible) {
+            throwVMTypeError(exec, ASCIILiteral("Proxy's 'preventExtensions' trap returned true even though its target is extensible. It should have returned false."));
+            return false;
+        }
+    }
+
+    return trapResultAsBool;
+}
+
+bool ProxyObject::preventExtensions(JSObject* object, ExecState* exec)
+{
+    return jsCast<ProxyObject*>(object)->performPreventExtensions(exec);
+}
+
 void ProxyObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
 {
     ProxyObject* thisObject = jsCast<ProxyObject*>(cell);
index 1210b28..28dedaf 100644 (file)
@@ -69,6 +69,7 @@ private:
     static ConstructType getConstructData(JSCell*, ConstructData&);
     static bool deleteProperty(JSCell*, ExecState*, PropertyName);
     static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned propertyName);
+    static bool preventExtensions(JSObject*, ExecState*);
     static void visitChildren(JSCell*, SlotVisitor&);
 
     bool getOwnPropertySlotCommon(ExecState*, PropertyName, PropertySlot&);
@@ -78,6 +79,7 @@ private:
     bool performDelete(ExecState*, PropertyName, DefaultDeleteFunction);
     template <typename PerformDefaultPutFunction>
     void performPut(ExecState*, JSValue putValue, JSValue thisValue, PropertyName, PerformDefaultPutFunction);
+    bool performPreventExtensions(ExecState*);
 
     WriteBarrier<JSObject> m_target;
     WriteBarrier<Unknown> m_handler;
index c11d106..61b6fbe 100644 (file)
 - path: es6/Proxy_ownKeys_handler.js
   cmd: runES6 :fail
 - path: es6/Proxy_preventExtensions_handler.js
-  cmd: runES6 :fail
+  cmd: runES6 :normal
 - path: es6/Proxy_Proxy.revocable.js
   cmd: runES6 :fail
 - path: es6/Proxy_set_handler.js
diff --git a/Source/JavaScriptCore/tests/stress/proxy-prevent-extensions.js b/Source/JavaScriptCore/tests/stress/proxy-prevent-extensions.js
new file mode 100644 (file)
index 0000000..9bed85b
--- /dev/null
@@ -0,0 +1,280 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad assertion.");
+}
+
+{
+    let target = {};
+    let error = null;
+    let handler = {
+        get preventExtensions() {
+            error = new Error;
+            throw error;
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        try {
+            Reflect.preventExtensions(proxy);
+        } catch(e) {
+            assert(e === error);
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = {};
+    let error = null;
+    let handler = {
+        preventExtensions: function() {
+            error = new Error;
+            throw error;
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        try {
+            Reflect.preventExtensions(proxy);
+        } catch(e) {
+            assert(e === error);
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let error = null;
+    let target = new Proxy({}, {
+        preventExtensions: function() {
+            error = new Error;
+            throw error;
+        }
+    });
+    let handler = {
+        preventExtensions: function(theTarget) {
+            return Reflect.preventExtensions(theTarget);
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        try {
+            Reflect.preventExtensions(proxy);
+        } catch(e) {
+            assert(e === error);
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = {};
+    let handler = {
+        preventExtensions: 45
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        try {
+            Reflect.preventExtensions(proxy);
+        } catch(e) {
+            assert(e.toString() === "TypeError: 'preventExtensions' property of a Proxy's handler should be callable.");
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = {};
+    let handler = {
+        preventExtensions: null
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(!Reflect.isExtensible(target));
+    }
+}
+
+{
+    let target = {};
+    let handler = {
+        preventExtensions: undefined
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(!Reflect.isExtensible(target));
+    }
+}
+
+{
+    let target = {};
+    let handler = {
+        preventExtensions: function(theTarget) {
+            return true;
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        try {
+            Reflect.preventExtensions(proxy);
+        } catch(e) {
+            assert(e.toString() === "TypeError: Proxy's 'preventExtensions' trap returned true even though its target is extensible. It should have returned false.");
+            threw = true;
+        }
+        assert(threw);
+    }
+}
+
+{
+    let target = {};
+    let called = false;
+    let handler = {
+        preventExtensions: function(theTarget) {
+            assert(theTarget === target);
+            called = true;
+            return Reflect.preventExtensions(theTarget);
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = {};
+    let called = false;
+    let handler = {
+        preventExtensions: function(theTarget) {
+            assert(theTarget === target);
+            called = true;
+            return false;
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let result = Reflect.preventExtensions(proxy);
+        assert(!result);
+        assert(called);
+        called = false;
+    }
+}
+
+{
+    let target = {};
+    let called = false;
+    let handler = {
+        preventExtensions: function(theTarget) {
+            assert(theTarget === target);
+            called = true;
+            return Reflect.preventExtensions(theTarget);
+        }
+    };
+    
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(called);
+        called = false;
+
+        // FIXME: This is true once we implement Proxy.[[IsExtensible]]
+        // https://bugs.webkit.org/show_bug.cgi?id=154872
+        /*
+        assert(!Reflect.isExtensible(proxy));
+        */
+
+        assert(!Reflect.isExtensible(target));
+        assert(!Object.isExtensible(target));
+        assert(Object.isFrozen(target));
+        assert(Object.isSealed(target));
+    }
+}
+
+{
+    for (let i = 0; i < 500; i++) {
+        let target = {};
+        let called = false;
+        let handler = {
+            preventExtensions: function(theTarget) {
+                assert(theTarget === target);
+                called = true;
+                return Reflect.preventExtensions(theTarget);
+            }
+        };
+        
+        let proxy = new Proxy(target, handler);
+
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(called);
+        called = false;
+
+        // FIXME: This is true once we implement Proxy.[[IsExtensible]]
+        // https://bugs.webkit.org/show_bug.cgi?id=154872
+        /*
+        assert(!Reflect.isExtensible(proxy));
+        */
+
+        assert(!Reflect.isExtensible(target));
+        assert(!Object.isExtensible(target));
+        assert(Object.isFrozen(target));
+        assert(Object.isSealed(target));
+    }
+}
+
+{
+    for (let i = 0; i < 500; i++) {
+        let target = {};
+        let called = false;
+        let handler = {
+            preventExtensions: function(theTarget) {
+                assert(theTarget === target);
+                called = true;
+                return Object.preventExtensions(theTarget);
+            }
+        };
+        
+        let proxy = new Proxy(target, handler);
+
+        let result = Reflect.preventExtensions(proxy);
+        assert(result);
+        assert(called);
+        called = false;
+
+        // FIXME: This is true once we implement Proxy.[[IsExtensible]]
+        // https://bugs.webkit.org/show_bug.cgi?id=154872
+        /*
+        assert(!Reflect.isExtensible(proxy));
+        */
+
+        assert(!Reflect.isExtensible(target));
+        assert(!Object.isExtensible(target));
+        assert(Object.isFrozen(target));
+        assert(Object.isSealed(target));
+    }
+}