Title: [245643] trunk
Revision
245643
Author
[email protected]
Date
2019-05-22 13:34:55 -0700 (Wed, 22 May 2019)

Log Message

Don't clear PropertyNameArray in Proxy code
https://bugs.webkit.org/show_bug.cgi?id=197691

Reviewed by Saam Barati.

JSTests:

* stress/proxy-get-own-property-names-should-not-clear-previous-results.js: Added.
(shouldBe):
(opt):

Source/_javascript_Core:

ProxyObject::performGetOwnPropertyNames clears the given PropertyNameArray to filter out non-enumerable keys.
But this does not assume that PropertyNameArray already contains the keys collected in the different objects.
We have an assumption that PropertyNameArray is always increasing, and JSPropertyNameEnumerator relies on this.
Since ProxyObject::performGetOwnPropertyNames clears the passed PropertyNameArray which contained the other
keys collected at some point of prototype hierarchy, this breaks JSPropertyNameEnumerator. Let's see the example.

var object = { __proto__: someProxy, someKey: 42 };
// Here, we first collect "someKey" in object. And using the same PropertyNameArray to add more keys from __proto__.
// But Proxy accidentally clears the passed PropertyNameArray, so "someKey" becomes missing.
for (var key in object);

This patch fixes ProxyObject::performGetOwnPropertyNames. Using separate PropertyNameArray to collect keys, and
filtering and adding them to the passed PropertyNameArray later. We also remove PropertyNameArray::reset method
since this breaks JSPropertyNameEnumerator's assumption.

We also fix the issue by changing seenKeys' HashSet<UniquedStringImpl*> to HashSet<RefPtr<UniquedStringImpl>>.
They can be deallocated if it is not added to trapResult later and it is toString-ed result from 'toPropertyKey()'.

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

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (245642 => 245643)


--- trunk/JSTests/ChangeLog	2019-05-22 20:22:44 UTC (rev 245642)
+++ trunk/JSTests/ChangeLog	2019-05-22 20:34:55 UTC (rev 245643)
@@ -1,3 +1,14 @@
+2019-05-22  Yusuke Suzuki  <[email protected]>
+
+        Don't clear PropertyNameArray in Proxy code
+        https://bugs.webkit.org/show_bug.cgi?id=197691
+
+        Reviewed by Saam Barati.
+
+        * stress/proxy-get-own-property-names-should-not-clear-previous-results.js: Added.
+        (shouldBe):
+        (opt):
+
 2019-05-22  Ross Kirsling  <[email protected]>
 
         [ESNext] Implement support for Numeric Separators

Added: trunk/JSTests/stress/proxy-get-own-property-names-should-not-clear-previous-results.js (0 => 245643)


--- trunk/JSTests/stress/proxy-get-own-property-names-should-not-clear-previous-results.js	                        (rev 0)
+++ trunk/JSTests/stress/proxy-get-own-property-names-should-not-clear-previous-results.js	2019-05-22 20:34:55 UTC (rev 245643)
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+a = {defineProperties:Object};
+function opt() {
+    a.__proto__ = new Proxy(Object,{ownKeys:opt});
+    return 1;
+}
+for(var i=0;i<400;i=i+1) {
+    var prop = null;
+    var count = 0;
+    for (t in a) {
+        opt();
+        prop = t;
+        ++count;
+    }
+    shouldBe(prop, "defineProperties");
+    shouldBe(count, 1);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (245642 => 245643)


--- trunk/Source/_javascript_Core/ChangeLog	2019-05-22 20:22:44 UTC (rev 245642)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-05-22 20:34:55 UTC (rev 245643)
@@ -1,3 +1,33 @@
+2019-05-22  Yusuke Suzuki  <[email protected]>
+
+        Don't clear PropertyNameArray in Proxy code
+        https://bugs.webkit.org/show_bug.cgi?id=197691
+
+        Reviewed by Saam Barati.
+
+        ProxyObject::performGetOwnPropertyNames clears the given PropertyNameArray to filter out non-enumerable keys.
+        But this does not assume that PropertyNameArray already contains the keys collected in the different objects.
+        We have an assumption that PropertyNameArray is always increasing, and JSPropertyNameEnumerator relies on this.
+        Since ProxyObject::performGetOwnPropertyNames clears the passed PropertyNameArray which contained the other
+        keys collected at some point of prototype hierarchy, this breaks JSPropertyNameEnumerator. Let's see the example.
+
+        var object = { __proto__: someProxy, someKey: 42 };
+        // Here, we first collect "someKey" in object. And using the same PropertyNameArray to add more keys from __proto__.
+        // But Proxy accidentally clears the passed PropertyNameArray, so "someKey" becomes missing.
+        for (var key in object);
+
+        This patch fixes ProxyObject::performGetOwnPropertyNames. Using separate PropertyNameArray to collect keys, and
+        filtering and adding them to the passed PropertyNameArray later. We also remove PropertyNameArray::reset method
+        since this breaks JSPropertyNameEnumerator's assumption.
+
+        We also fix the issue by changing seenKeys' HashSet<UniquedStringImpl*> to HashSet<RefPtr<UniquedStringImpl>>.
+        They can be deallocated if it is not added to trapResult later and it is toString-ed result from 'toPropertyKey()'.
+
+        * runtime/PropertyNameArray.h:
+        (JSC::PropertyNameArray::reset): Deleted.
+        * runtime/ProxyObject.cpp:
+        (JSC::ProxyObject::performGetOwnPropertyNames):
+
 2019-05-22  Ross Kirsling  <[email protected]>
 
         [ESNext] Implement support for Numeric Separators

Modified: trunk/Source/_javascript_Core/runtime/PropertyNameArray.h (245642 => 245643)


--- trunk/Source/_javascript_Core/runtime/PropertyNameArray.h	2019-05-22 20:22:44 UTC (rev 245642)
+++ trunk/Source/_javascript_Core/runtime/PropertyNameArray.h	2019-05-22 20:34:55 UTC (rev 245643)
@@ -55,12 +55,6 @@
     {
     }
 
-    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 (245642 => 245643)


--- trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2019-05-22 20:22:44 UTC (rev 245642)
+++ trunk/Source/_javascript_Core/runtime/ProxyObject.cpp	2019-05-22 20:34:55 UTC (rev 245643)
@@ -893,7 +893,7 @@
     return thisObject->performDefineOwnProperty(exec, propertyName, descriptor, shouldThrow);
 }
 
-void ProxyObject::performGetOwnPropertyNames(ExecState* exec, PropertyNameArray& trapResult, EnumerationMode enumerationMode)
+void ProxyObject::performGetOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode enumerationMode)
 {
     NO_TAIL_CALLS();
 
@@ -917,7 +917,7 @@
     JSObject* target = this->target();
     if (ownKeysMethod.isUndefined()) {
         scope.release();
-        target->methodTable(vm)->getOwnPropertyNames(target, exec, trapResult, enumerationMode);
+        target->methodTable(vm)->getOwnPropertyNames(target, exec, propertyNames, enumerationMode);
         return;
     }
 
@@ -927,58 +927,61 @@
     JSValue arrayLikeObject = call(exec, ownKeysMethod, callType, callData, handler, arguments);
     RETURN_IF_EXCEPTION(scope, void());
 
-    PropertyNameMode propertyNameMode = trapResult.propertyNameMode();
-    RuntimeTypeMask resultFilter = 0;
-    switch (propertyNameMode) {
-    case PropertyNameMode::Symbols:
-        resultFilter = TypeSymbol;
-        break;
-    case PropertyNameMode::Strings:
-        resultFilter = TypeString;
-        break;
-    case PropertyNameMode::StringsAndSymbols:
-        resultFilter = TypeSymbol | TypeString;
-        break;
-    }
-    ASSERT(resultFilter);
-    RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol;
+    PropertyNameArray trapResult(&vm, propertyNames.propertyNameMode(), propertyNames.privateSymbolMode());
     HashSet<UniquedStringImpl*> uncheckedResultKeys;
-    HashSet<UniquedStringImpl*> seenKeys;
+    {
+        HashSet<RefPtr<UniquedStringImpl>> seenKeys;
 
-    auto addPropName = [&] (JSValue value, RuntimeType type) -> bool {
-        static const bool doExitEarly = true;
-        static const bool dontExitEarly = false;
+        RuntimeTypeMask resultFilter = 0;
+        switch (propertyNames.propertyNameMode()) {
+        case PropertyNameMode::Symbols:
+            resultFilter = TypeSymbol;
+            break;
+        case PropertyNameMode::Strings:
+            resultFilter = TypeString;
+            break;
+        case PropertyNameMode::StringsAndSymbols:
+            resultFilter = TypeSymbol | TypeString;
+            break;
+        }
+        ASSERT(resultFilter);
 
-        Identifier ident = value.toPropertyKey(exec);
-        RETURN_IF_EXCEPTION(scope, doExitEarly);
+        auto addPropName = [&] (JSValue value, RuntimeType type) -> bool {
+            static const bool doExitEarly = true;
+            static const bool dontExitEarly = false;
 
-        // If trapResult contains any duplicate entries, throw a TypeError exception.
-        //    
-        // Per spec[1], filtering by type should occur _after_ [[OwnPropertyKeys]], so duplicates
-        // are tracked in a separate hashtable from uncheckedResultKeys (which only contain the
-        // keys filtered by type).
-        //
-        // [1] Per https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeysmust not contain any duplicate names"_s);
-        if (!seenKeys.add(ident.impl()).isNewEntry) {
-            throwTypeError(exec, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s);
-            return doExitEarly;
-        }
+            Identifier ident = value.toPropertyKey(exec);
+            RETURN_IF_EXCEPTION(scope, doExitEarly);
 
-        if (!(type & resultFilter))
+            // If trapResult contains any duplicate entries, throw a TypeError exception.
+            //    
+            // Per spec[1], filtering by type should occur _after_ [[OwnPropertyKeys]], so duplicates
+            // are tracked in a separate hashtable from uncheckedResultKeys (which only contain the
+            // keys filtered by type).
+            //
+            // [1] Per https://tc39.github.io/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeysmust not contain any duplicate names"_s);
+            if (!seenKeys.add(ident.impl()).isNewEntry) {
+                throwTypeError(exec, scope, "Proxy handler's 'ownKeys' trap result must not contain any duplicate names"_s);
+                return doExitEarly;
+            }
+
+            if (!(type & resultFilter))
+                return dontExitEarly;
+
+            uncheckedResultKeys.add(ident.impl());
+            trapResult.add(ident.impl());
             return dontExitEarly;
+        };
 
-        uncheckedResultKeys.add(ident.impl());
-        trapResult.addUnchecked(ident.impl());
-        return dontExitEarly;
-    };
+        RuntimeTypeMask dontThrowAnExceptionTypeFilter = TypeString | TypeSymbol;
+        createListFromArrayLike(exec, arrayLikeObject, dontThrowAnExceptionTypeFilter, "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s, addPropName);
+        RETURN_IF_EXCEPTION(scope, void());
+    }
 
-    createListFromArrayLike(exec, arrayLikeObject, dontThrowAnExceptionTypeFilter, "Proxy handler's 'ownKeys' method must return an array-like object containing only Strings and Symbols"_s, addPropName);
-    RETURN_IF_EXCEPTION(scope, void());
-
     bool targetIsExensible = target->isExtensible(exec);
     RETURN_IF_EXCEPTION(scope, void());
 
-    PropertyNameArray targetKeys(&vm, propertyNameMode, trapResult.privateSymbolMode());
+    PropertyNameArray targetKeys(&vm, propertyNames.propertyNameMode(), propertyNames.privateSymbolMode());
     target->methodTable(vm)->getOwnPropertyNames(target, exec, targetKeys, enumerationMode);
     RETURN_IF_EXCEPTION(scope, void());
     Vector<UniquedStringImpl*> targetConfigurableKeys;
@@ -1026,10 +1029,7 @@
 
     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()) {
+        for (auto propertyName : trapResult) {
             PropertySlot slot(this, PropertySlot::InternalMethodType::GetOwnProperty);
             auto result = getOwnPropertySlotCommon(exec, propertyName, slot);
             RETURN_IF_EXCEPTION(scope, void());
@@ -1037,8 +1037,11 @@
                 continue;
             if (slot.attributes() & PropertyAttribute::DontEnum)
                 continue;
-            trapResult.addUnchecked(propertyName.impl());
+            propertyNames.add(propertyName.impl());
         }
+    } else {
+        for (auto propertyName : trapResult)
+            propertyNames.add(propertyName.impl());
     }
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to