[JSC] Filter DontEnum properties in ProxyObject::getOwnPropertyNames()
authorcaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Apr 2019 21:28:10 +0000 (21:28 +0000)
committercaitp@igalia.com <caitp@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Apr 2019 21:28:10 +0000 (21:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176810

Reviewed by Saam Barati.

JSTests:

Add tests for the DontEnum filtering, and variations of other tests
take the DontEnum-filtering path.

* stress/proxy-own-keys.js:
(i.catch):
(set assert):
(set add):
(let.set new):
(get let):

Source/JavaScriptCore:

This adds conditional logic following the invariant checks, to perform
filtering in common uses of getOwnPropertyNames.

While this would ideally only be done in JSPropertyNameEnumerator, adding
the filtering to ProxyObject::performGetOwnPropertyNames maintains the
invariant that the EnumerationMode is properly followed.

* runtime/PropertyNameArray.h:
(JSC::PropertyNameArray::reset):
* runtime/ProxyObject.cpp:
(JSC::ProxyObject::performGetOwnPropertyNames):

Source/WebCore:

Previously, there was a comment here indicating uncertainty of whether it
was necessary to filter DontEnum properties explicitly or not. It turns
out that it was necessary in the case of JSC ProxyObjects.

This patch adds DontEnum filtering for ProxyObjects, however we continue
to explicitly filter them in JSDOMConvertRecord, which needs to use the
property descriptor after filtering. This change prevents observably
fetching the property descriptor twice per property.

* bindings/js/JSDOMConvertRecord.h:

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

JSTests/ChangeLog
JSTests/stress/proxy-own-keys.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/PropertyNameArray.h
Source/JavaScriptCore/runtime/ProxyObject.cpp
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSDOMConvertRecord.h

index a1f2bed..2f91000 100644 (file)
@@ -1,5 +1,22 @@
 2019-04-05  Caitlin Potter  <caitp@igalia.com>
 
+        [JSC] Filter DontEnum properties in ProxyObject::getOwnPropertyNames()
+        https://bugs.webkit.org/show_bug.cgi?id=176810
+
+        Reviewed by Saam Barati.
+
+        Add tests for the DontEnum filtering, and variations of other tests
+        take the DontEnum-filtering path.
+
+        * stress/proxy-own-keys.js:
+        (i.catch):
+        (set assert):
+        (set add):
+        (let.set new):
+        (get let):
+
+2019-04-05  Caitlin Potter  <caitp@igalia.com>
+
         [JSC] throw if 'ownKeys' Proxy trap result contains duplicate keys
         https://bugs.webkit.org/show_bug.cgi?id=185211
 
index 01756fb..853d9c1 100644 (file)
@@ -135,6 +135,22 @@ function assert(b) {
         assert(called);
         called = false;
     }
+
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        let foundKey = false;
+        try {
+            for (let k in proxy)
+                foundKey = true;
+        } catch(e) {
+            threw = true;
+            assert(e.toString() === "TypeError: Proxy object's non-extensible 'target' has configurable property 'x' that was not in the result from the 'ownKeys' trap");
+            assert(!foundKey);
+        }
+        assert(threw);
+        assert(called);
+        called = false;
+    }
 }
 
 {
@@ -166,6 +182,22 @@ function assert(b) {
         assert(called);
         called = false;
     }
+
+    for (let i = 0; i < 500; i++) {
+        let threw = false;
+        let reached = false;
+        try {
+            for (let k in proxy)
+                reached = true;
+        } catch (e) {
+            threw = true;
+            assert(e.toString() === "TypeError: Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target");
+        }
+        assert(threw);
+        assert(called);
+        assert(!reached);
+        called = false;
+    }
 }
 
 {
@@ -667,3 +699,68 @@ function shallowEq(a, b) {
         error = null;
     }
 }
+
+{
+    let error = null;
+    let s1 = Symbol();
+    let s2 = Symbol();
+    let target = Object.defineProperties({}, {
+        x: {
+            value: "X",
+            enumerable: true,
+            configurable: true,
+        },
+        dontEnum1: {
+            value: "dont-enum",
+            enumerable: false,
+            configurable: true,
+        },
+        y: {
+            get() { return "Y"; },
+            enumerable: true,
+            configurable: true,
+        },
+        dontEnum2: {
+            get() { return "dont-enum-accessor" },
+            enumerable: false,
+            configurable: true
+        },
+        [s1]: {
+            value: "s1",
+            enumerable: true,
+            configurable: true,
+        },
+        [s2]: {
+            value: "dont-enum-symbol",
+            enumerable: false,
+            configurable: true,  
+        },
+    });
+    let checkedOwnKeys = false;
+    let checkedPropertyDescriptor = false;
+    let handler = {
+        ownKeys() {
+            checkedOwnKeys = true;
+            return ["x", "dontEnum1", "y", "dontEnum2", s1, s2];
+        },
+        getOwnPropertyDescriptor(t, k) {
+            checkedPropertyDescriptors = true;
+            return Reflect.getOwnPropertyDescriptor(t, k);
+        }
+    };
+    let proxy = new Proxy(target, handler);
+    for (let i = 0; i < 500; i++) {
+        checkedPropertyDescriptors = false;
+        assert(shallowEq(["x", "dontEnum1", "y", "dontEnum2", s1, s2], Reflect.ownKeys(proxy)));
+        assert(checkedOwnKeys);
+        assert(!checkedPropertyDescriptors);
+        checkedOwnKeys = false;
+
+        let enumerableStringKeys = [];
+        for (let k in proxy)
+            enumerableStringKeys.push(k);
+        assert(shallowEq(["x", "y"], enumerableStringKeys));
+        assert(checkedOwnKeys);
+        assert(checkedPropertyDescriptors);
+    }
+}
index eef98aa..d8f07c8 100644 (file)
@@ -1,3 +1,22 @@
+2019-04-05  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] Filter DontEnum properties in ProxyObject::getOwnPropertyNames()
+        https://bugs.webkit.org/show_bug.cgi?id=176810
+
+        Reviewed by Saam Barati.
+
+        This adds conditional logic following the invariant checks, to perform
+        filtering in common uses of getOwnPropertyNames.
+
+        While this would ideally only be done in JSPropertyNameEnumerator, adding
+        the filtering to ProxyObject::performGetOwnPropertyNames maintains the
+        invariant that the EnumerationMode is properly followed.
+
+        * runtime/PropertyNameArray.h:
+        (JSC::PropertyNameArray::reset):
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::performGetOwnPropertyNames):
+
 2019-04-05  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r243833.
index 5e93b79..8ec9ac1 100644 (file)
@@ -55,6 +55,12 @@ public:
     {
     }
 
+    void reset()
+    {
+        m_set.clear();
+        m_data = PropertyNameArrayData::create();
+    }
+
     VM* vm() { return m_vm; }
 
     void add(uint32_t index)
index 8cafad5..414a75e 100644 (file)
@@ -1006,19 +1006,33 @@ void ProxyObject::performGetOwnPropertyNames(ExecState* exec, PropertyNameArray&
         }
     }
 
-    if (targetIsExensible)
-        return;
+    if (!targetIsExensible) {
+        for (UniquedStringImpl* impl : targetConfigurableKeys) {
+            if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
+                throwVMTypeError(exec, scope, makeString("Proxy object's non-extensible 'target' has configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
+                return;
+            }
+        }
 
-    for (UniquedStringImpl* impl : targetConfigurableKeys) {
-        if (removeIfContainedInUncheckedResultKeys(impl) == IsNotContainedIn) {
-            throwVMTypeError(exec, scope, makeString("Proxy object's non-extensible 'target' has configurable property '", String(impl), "' that was not in the result from the 'ownKeys' trap"));
+        if (uncheckedResultKeys.size()) {
+            throwVMTypeError(exec, scope, "Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target"_s);
             return;
         }
     }
 
-    if (uncheckedResultKeys.size()) {
-        throwVMTypeError(exec, scope, "Proxy handler's 'ownKeys' method returned a key that was not present in its non-extensible target"_s);
-        return;
+    if (!enumerationMode.includeDontEnumProperties()) {
+        // Filtering DontEnum properties is observable in proxies and must occur following the invariant checks above.
+        auto data = trapResult.releaseData();
+        trapResult.reset();
+
+        for (auto propertyName : data->propertyNameVector()) {
+            PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
+            if (!getOwnPropertySlotCommon(exec, propertyName, slot))
+                continue;
+            if (slot.attributes() & PropertyAttribute::DontEnum)
+                continue;
+            trapResult.addUnchecked(propertyName.impl());
+        }
     }
 }
 
index 23d2a1b..88c59bd 100644 (file)
@@ -1,3 +1,21 @@
+2019-04-05  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] Filter DontEnum properties in ProxyObject::getOwnPropertyNames()
+        https://bugs.webkit.org/show_bug.cgi?id=176810
+
+        Reviewed by Saam Barati.
+
+        Previously, there was a comment here indicating uncertainty of whether it
+        was necessary to filter DontEnum properties explicitly or not. It turns
+        out that it was necessary in the case of JSC ProxyObjects.
+
+        This patch adds DontEnum filtering for ProxyObjects, however we continue
+        to explicitly filter them in JSDOMConvertRecord, which needs to use the
+        property descriptor after filtering. This change prevents observably
+        fetching the property descriptor twice per property.
+
+        * bindings/js/JSDOMConvertRecord.h:
+
 2019-04-05  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Unreviewed manual rollout of r243929
index 75b2eb6..df9c3a6 100644 (file)
@@ -86,7 +86,7 @@ template<typename K, typename V> struct Converter<IDLRecord<K, V>> : DefaultConv
     
         // 4. Let keys be ? O.[[OwnPropertyKeys]]().
         JSC::PropertyNameArray keys(&vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude);
-        object->methodTable(vm)->getOwnPropertyNames(object, &state, keys, JSC::EnumerationMode());
+        object->methodTable(vm)->getOwnPropertyNames(object, &state, keys, JSC::EnumerationMode(JSC::DontEnumPropertiesMode::Include));
 
         RETURN_IF_EXCEPTION(scope, { });
 
@@ -99,9 +99,8 @@ template<typename K, typename V> struct Converter<IDLRecord<K, V>> : DefaultConv
 
             // 2. If desc is not undefined and desc.[[Enumerable]] is true:
 
-            // FIXME: Do we need to check for enumerable / undefined, or is this handled by the default
-            // enumeration mode?
-
+            // It's necessary to filter enumerable here rather than using the default EnumerationMode,
+            // to prevent an observable extra [[GetOwnProperty]] operation in the case of ProxyObject records.
             if (didGetDescriptor && descriptor.enumerable()) {
                 // 1. Let typedKey be key converted to an IDL value of type K.
                 auto typedKey = Detail::IdentifierConverter<K>::convert(state, key);