Title: [244330] trunk
Revision
244330
Author
ca...@igalia.com
Date
2019-04-16 08:58:59 -0700 (Tue, 16 Apr 2019)

Log Message

[JSC] Filter DontEnum properties in ProxyObject::getOwnPropertyNames()
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/_javascript_Core:

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.

This was originally rolled out in r244020, as DontEnum filtering code
in ObjectConstructor.cpp's ownPropertyKeys() had not been removed. It's
now redundant due to being handled in ProxyObject::getOwnPropertyNames().

* 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:

Modified Paths

Diff

Modified: trunk/JSTests/ChangeLog (244329 => 244330)


--- trunk/JSTests/ChangeLog	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/JSTests/ChangeLog	2019-04-16 15:58:59 UTC (rev 244330)
@@ -1,3 +1,20 @@
+2019-04-16  Caitlin Potter  <ca...@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-15  Saam barati  <sbar...@apple.com>
 
         Modify how we do SetArgument when we inline varargs calls

Modified: trunk/JSTests/stress/proxy-own-keys.js (244329 => 244330)


--- trunk/JSTests/stress/proxy-own-keys.js	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/JSTests/stress/proxy-own-keys.js	2019-04-16 15:58:59 UTC (rev 244330)
@@ -135,6 +135,22 @@
         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 @@
         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 @@
         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);
+    }
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (244329 => 244330)


--- trunk/Source/_javascript_Core/ChangeLog	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-04-16 15:58:59 UTC (rev 244330)
@@ -1,3 +1,26 @@
+2019-04-16  Caitlin Potter  <ca...@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.
+
+        This was originally rolled out in r244020, as DontEnum filtering code
+        in ObjectConstructor.cpp's ownPropertyKeys() had not been removed. It's
+        now redundant due to being handled in ProxyObject::getOwnPropertyNames().
+
+        * runtime/PropertyNameArray.h:
+        (JSC::PropertyNameArray::reset):
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::performGetOwnPropertyNames):
+
 2019-04-15  Saam barati  <sbar...@apple.com>
 
         Modify how we do SetArgument when we inline varargs calls

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp (244329 => 244330)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2019-04-16 15:58:59 UTC (rev 244330)
@@ -920,23 +920,9 @@
     object->methodTable(vm)->getOwnPropertyNames(object, exec, properties, EnumerationMode(dontEnumPropertiesMode));
     RETURN_IF_EXCEPTION(scope, nullptr);
 
-    // https://tc39.github.io/ecma262/#sec-enumerableownproperties
-    // If {object} is a Proxy, an explicit and observable [[GetOwnProperty]] op is required to filter out non-enumerable properties.
-    // In other cases, filtering has already been performed.
-    const bool mustFilterProperty = dontEnumPropertiesMode == DontEnumPropertiesMode::Exclude && object->type() == ProxyObjectType;
-    auto filterPropertyIfNeeded = [exec, object, mustFilterProperty](const Identifier& identifier) {
-        if (!mustFilterProperty)
-            return true;
-        PropertyDescriptor descriptor;
-        PropertyName name(identifier);
-        return object->getOwnPropertyDescriptor(exec, name, descriptor) && descriptor.enumerable();
-    };
-
-    // If !mustFilterProperty and PropertyNameMode::Strings mode, we do not need to filter out any entries in PropertyNameArray.
-    // We can use fast allocation and initialization.
     if (propertyNameMode != PropertyNameMode::StringsAndSymbols) {
         ASSERT(propertyNameMode == PropertyNameMode::Strings || propertyNameMode == PropertyNameMode::Symbols);
-        if (!mustFilterProperty && properties.size() < MIN_SPARSE_ARRAY_INDEX) {
+        if (properties.size() < MIN_SPARSE_ARRAY_INDEX) {
             if (LIKELY(!globalObject->isHavingABadTime())) {
                 if (isObjectKeys) {
                     Structure* structure = object->structure(vm);
@@ -993,10 +979,7 @@
         for (size_t i = 0; i < numProperties; i++) {
             const auto& identifier = properties[i];
             ASSERT(!identifier.isSymbol());
-            bool hasProperty = filterPropertyIfNeeded(identifier);
-            EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
-            if (hasProperty)
-                pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
+            pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
         break;
@@ -1008,10 +991,7 @@
             const auto& identifier = properties[i];
             ASSERT(identifier.isSymbol());
             ASSERT(!identifier.isPrivateName());
-            bool hasProperty = filterPropertyIfNeeded(identifier);
-            EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
-            if (hasProperty)
-                pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
+            pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
         break;
@@ -1028,19 +1008,13 @@
                 continue;
             }
 
-            bool hasProperty = filterPropertyIfNeeded(identifier);
-            EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
-            if (hasProperty)
-                pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
+            pushDirect(exec, keys, jsOwnedString(exec, identifier.string()));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
 
         // To ensure the order defined in the spec (9.1.12), we append symbols at the last elements of keys.
         for (const auto& identifier : propertySymbols) {
-            bool hasProperty = filterPropertyIfNeeded(identifier);
-            EXCEPTION_ASSERT(!scope.exception() || !hasProperty);
-            if (hasProperty)
-                pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
+            pushDirect(exec, keys, Symbol::create(vm, static_cast<SymbolImpl&>(*identifier.impl())));
             RETURN_IF_EXCEPTION(scope, nullptr);
         }
 

Modified: trunk/Source/_javascript_Core/runtime/PropertyNameArray.h (244329 => 244330)


--- trunk/Source/_javascript_Core/runtime/PropertyNameArray.h	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/_javascript_Core/runtime/PropertyNameArray.h	2019-04-16 15:58:59 UTC (rev 244330)
@@ -55,6 +55,12 @@
     {
     }
 
+    void reset()
+    {
+        m_set.clear();
+        m_data = PropertyNameArrayData::create();
+    }
+
     VM* vm() { return m_vm; }
 
     void add(uint32_t index)

Modified: trunk/Source/_javascript_Core/runtime/ProxyObject.cpp (244329 => 244330)


--- trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2019-04-16 15:58:59 UTC (rev 244330)
@@ -1006,19 +1006,35 @@
         }
     }
 
-    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.reset();
+
+        for (auto propertyName : data->propertyNameVector()) {
+            PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
+            auto result = getOwnPropertySlotCommon(exec, propertyName, slot);
+            RETURN_IF_EXCEPTION(scope, void());
+            if (!result)
+                continue;
+            if (slot.attributes() & PropertyAttribute::DontEnum)
+                continue;
+            trapResult.addUnchecked(propertyName.impl());
+        }
     }
 }
 

Modified: trunk/Source/WebCore/ChangeLog (244329 => 244330)


--- trunk/Source/WebCore/ChangeLog	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/WebCore/ChangeLog	2019-04-16 15:58:59 UTC (rev 244330)
@@ -1,3 +1,21 @@
+2019-04-16  Caitlin Potter  <ca...@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-15  Antoine Quint  <grao...@apple.com>
 
         [iOS] Redundant pointer events causes material design buttons to flush twice

Modified: trunk/Source/WebCore/bindings/js/JSDOMConvertRecord.h (244329 => 244330)


--- trunk/Source/WebCore/bindings/js/JSDOMConvertRecord.h	2019-04-16 09:41:00 UTC (rev 244329)
+++ trunk/Source/WebCore/bindings/js/JSDOMConvertRecord.h	2019-04-16 15:58:59 UTC (rev 244330)
@@ -86,7 +86,7 @@
     
         // 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 @@
 
             // 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);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to