Title: [264574] trunk
Revision
264574
Author
[email protected]
Date
2020-07-18 16:09:56 -0700 (Sat, 18 Jul 2020)

Log Message

Redefining a property should not change its insertion index (Object.keys order)
https://bugs.webkit.org/show_bug.cgi?id=142933

Reviewed by Saam Barati.

JSTests:

* ChakraCore.yaml:
* ChakraCore/test/es5/EnumeratingWithES5.baseline-jsc: Removed.
* microbenchmarks/redefine-property-accessor-dictionary.js: Added.
* microbenchmarks/redefine-property-accessor.js: Added.
* microbenchmarks/redefine-property-data-dictionary.js: Added.
* microbenchmarks/redefine-property-data.js: Added.
* stress/define-own-indexed-property-fast-path.js:
* stress/redefine-property-enumerable.js: Added.
* stress/redefine-property-get.js: Added.
* stress/redefine-property-set.js: Added.
* stress/redefine-property-value.js: Added.
* stress/redefine-property-writable.js: Added.
* test262/expectations.yaml: Mark 12 test cases as passing.

Source/_javascript_Core:

Before this change, JSC used to delete & put back a non-indexed property just to
update attributes, which was less efficient and corrupted observable property order.

This patch:
1. Rewrites validateAndApplyPropertyDescriptor() to closely resemble the spec [1].
2. Drops property deletion, inlines putDescriptor(), and sets necessary Structure
   flags in attributeChangeTransition().
3. Simplifies validateAndApplyPropertyDescriptor() a bit by obtaining GetterSetter
   instance from current descriptor rather then calling getDirect().

This change aligns property order with V8 and SpiderMonkey, advancing provided
microbenchmarks by 5-85% (especially for objects in dictionary mode).
SixSpeed, SunSpider, and ARES-6 are all neutral.

[1]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor

* runtime/JSObject.cpp:
(JSC::validateAndApplyPropertyDescriptor):
(JSC::JSObject::defineOwnNonIndexProperty):
(JSC::putDescriptor): Deleted.
* runtime/JSObjectInlines.h:
(JSC::JSObject::putDirectInternal):
* runtime/PropertyDescriptor.cpp:
(JSC::PropertyDescriptor::slowGetterSetter const):
(JSC::PropertyDescriptor::slowGetterSetter): Deleted.
* runtime/PropertyDescriptor.h:
* runtime/Structure.cpp:
(JSC::Structure::attributeChangeTransition):

LayoutTests:

* js/object-literal-duplicate-properties-expected.txt:
* js/script-tests/object-literal-duplicate-properties.js:

Modified Paths

Added Paths

Removed Paths

Diff

Deleted: trunk/JSTests/ChakraCore/test/es5/EnumeratingWithES5.baseline-jsc (264573 => 264574)


--- trunk/JSTests/ChakraCore/test/es5/EnumeratingWithES5.baseline-jsc	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/JSTests/ChakraCore/test/es5/EnumeratingWithES5.baseline-jsc	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1,382 +0,0 @@
-Test 1: Non-writable, simple type handler
-Testing for-in enumeration
-d: dvalue
-Setting value to 1
-d: 1
-Testing getOwnPropertyNames enumeration
-d: 1
-Setting value to 2
-d: 2
-Setting writability of d to false
-Testing for-in enumeration
-d: 2
-Setting value to 3
-d: 2
-Testing getOwnPropertyNames enumeration
-d: 2
-Setting value to 4
-d: 2
-Setting writability of d to true
-Testing for-in enumeration
-d: 2
-Setting value to 5
-d: 5
-Testing getOwnPropertyNames enumeration
-d: 5
-Setting value to 6
-d: 6
-Changing writability during enumeration...
-Testing for-in enumeration
-Setting writability of d to false
-d: 6
-Setting value to 7
-d: 6
-Testing getOwnPropertyNames enumeration
-Setting writability of d to false
-d: 6
-Setting value to 8
-d: 6
-Testing for-in enumeration
-Setting writability of d to true
-d: 6
-Setting value to 9
-d: 9
-Testing getOwnPropertyNames enumeration
-Setting writability of d to true
-d: 9
-Setting value to 10
-d: 10
-Freezing object
-Testing for-in enumeration
-d: 10
-Setting value to 11
-d: 10
-Testing getOwnPropertyNames enumeration
-d: 10
-Setting value to 12
-d: 10
-
-Test 2: Non-writable, simple dictionary type handler
-Testing for-in enumeration
-d: dvalue
-Setting value to 1
-d: 1
-Testing getOwnPropertyNames enumeration
-d: 1
-Setting value to 2
-d: 2
-Setting writability of d to false
-Testing for-in enumeration
-d: 2
-Setting value to 3
-d: 2
-Testing getOwnPropertyNames enumeration
-d: 2
-Setting value to 4
-d: 2
-Setting writability of d to true
-Testing for-in enumeration
-d: 2
-Setting value to 5
-d: 5
-Testing getOwnPropertyNames enumeration
-d: 5
-Setting value to 6
-d: 6
-Changing writability during enumeration...
-Testing for-in enumeration
-Setting writability of d to false
-d: 6
-Setting value to 7
-d: 6
-Testing getOwnPropertyNames enumeration
-Setting writability of d to false
-d: 6
-Setting value to 8
-d: 6
-Testing for-in enumeration
-Setting writability of d to true
-d: 6
-Setting value to 9
-d: 9
-Testing getOwnPropertyNames enumeration
-Setting writability of d to true
-d: 9
-Setting value to 10
-d: 10
-Freezing object
-Testing for-in enumeration
-d: 10
-Setting value to 11
-d: 10
-Testing getOwnPropertyNames enumeration
-d: 10
-Setting value to 12
-d: 10
-
-Test 3: Non-writable, dictionary type handler
-Testing for-in enumeration
-d: dvalue
-Setting value to 1
-d: 1
-Testing getOwnPropertyNames enumeration
-d: 1
-Setting value to 2
-d: 2
-Setting writability of d to false
-Testing for-in enumeration
-d: 2
-Setting value to 3
-d: 2
-Testing getOwnPropertyNames enumeration
-d: 2
-Setting value to 4
-d: 2
-Setting writability of d to true
-Testing for-in enumeration
-d: 2
-Setting value to 5
-d: 5
-Testing getOwnPropertyNames enumeration
-d: 5
-Setting value to 6
-d: 6
-Changing writability during enumeration...
-Testing for-in enumeration
-Setting writability of d to false
-d: 6
-Setting value to 7
-d: 6
-Testing getOwnPropertyNames enumeration
-Setting writability of d to false
-d: 6
-Setting value to 8
-d: 6
-Testing for-in enumeration
-Setting writability of d to true
-d: 6
-Setting value to 9
-d: 9
-Testing getOwnPropertyNames enumeration
-Setting writability of d to true
-d: 9
-Setting value to 10
-d: 10
-Freezing object
-Testing for-in enumeration
-d: 10
-Setting value to 11
-d: 10
-Testing getOwnPropertyNames enumeration
-d: 10
-Setting value to 12
-d: 10
-
-Test 4: Accessors
-Defining accessors for b
-Defining accessors for f
-Testing for-in enumeration
-a: aValue
-Setting value to 1
-a: 1
-b: GETTER FOR b
-Setting value to 2
-SETTER FOR b
-b: GETTER FOR b
-d: dValue
-Setting value to 3
-d: 3
-f: GETTER FOR f
-Setting value to 4
-SETTER FOR f
-f: GETTER FOR f
-g: gValue
-Setting value to 5
-g: 5
-Testing getOwnPropertyNames enumeration
-a: 1
-Setting value to 6
-a: 6
-b: GETTER FOR b
-Setting value to 7
-SETTER FOR b
-b: GETTER FOR b
-d: 3
-Setting value to 8
-d: 8
-e: eValue
-Setting value to 9
-e: 9
-f: GETTER FOR f
-Setting value to 10
-SETTER FOR f
-f: GETTER FOR f
-g: 5
-Setting value to 11
-g: 11
-Defining accessors for d
-Testing for-in enumeration
-a: 6
-Setting value to 12
-a: 12
-b: GETTER FOR b
-Setting value to 13
-SETTER FOR b
-b: GETTER FOR b
-f: GETTER FOR f
-Setting value to 14
-SETTER FOR f
-f: GETTER FOR f
-g: 11
-Setting value to 15
-g: 15
-d: GETTER
-Setting value to 16
-SETTER
-d: GETTER
-Testing getOwnPropertyNames enumeration
-a: 12
-Setting value to 17
-a: 17
-b: GETTER FOR b
-Setting value to 18
-SETTER FOR b
-b: GETTER FOR b
-e: 9
-Setting value to 19
-e: 19
-f: GETTER FOR f
-Setting value to 20
-SETTER FOR f
-f: GETTER FOR f
-g: 15
-Setting value to 21
-g: 21
-d: GETTER
-Setting value to 22
-SETTER
-d: GETTER
-Defining data property d with value 23
-Testing for-in enumeration
-a: 17
-Setting value to 24
-a: 24
-b: GETTER FOR b
-Setting value to 25
-SETTER FOR b
-b: GETTER FOR b
-f: GETTER FOR f
-Setting value to 26
-SETTER FOR f
-f: GETTER FOR f
-g: 21
-Setting value to 27
-g: 27
-d: 23
-Setting value to 28
-d: 28
-Testing getOwnPropertyNames enumeration
-a: 24
-Setting value to 29
-a: 29
-b: GETTER FOR b
-Setting value to 30
-SETTER FOR b
-b: GETTER FOR b
-e: 19
-Setting value to 31
-e: 31
-f: GETTER FOR f
-Setting value to 32
-SETTER FOR f
-f: GETTER FOR f
-g: 27
-Setting value to 33
-g: 33
-d: 28
-Setting value to 34
-d: 34
-Defining accessor property during enumeration...
-Testing for-in enumeration
-a: 29
-Setting value to 35
-a: 35
-b: GETTER FOR b
-Setting value to 36
-SETTER FOR b
-b: GETTER FOR b
-f: GETTER FOR f
-Setting value to 37
-SETTER FOR f
-f: GETTER FOR f
-g: 33
-Setting value to 38
-g: 38
-Defining accessors for d
-d: GETTER
-Setting value to 39
-SETTER
-d: GETTER
-Testing getOwnPropertyNames enumeration
-a: 35
-Setting value to 40
-a: 40
-b: GETTER FOR b
-Setting value to 41
-SETTER FOR b
-b: GETTER FOR b
-e: 31
-Setting value to 42
-e: 42
-f: GETTER FOR f
-Setting value to 43
-SETTER FOR f
-f: GETTER FOR f
-g: 38
-Setting value to 44
-g: 44
-d: GETTER
-Setting value to 45
-SETTER
-d: GETTER
-Defining data property during enumeration...
-Testing for-in enumeration
-a: 40
-Setting value to 47
-a: 47
-b: GETTER FOR b
-Setting value to 49
-SETTER FOR b
-b: GETTER FOR b
-f: GETTER FOR f
-Setting value to 51
-SETTER FOR f
-f: GETTER FOR f
-g: 44
-Setting value to 53
-g: 53
-Defining data property d with value 54
-d: 54
-Setting value to 55
-d: 55
-Testing getOwnPropertyNames enumeration
-a: 47
-Setting value to 57
-a: 57
-b: GETTER FOR b
-Setting value to 59
-SETTER FOR b
-b: GETTER FOR b
-e: 42
-Setting value to 61
-e: 61
-f: GETTER FOR f
-Setting value to 63
-SETTER FOR f
-f: GETTER FOR f
-g: 53
-Setting value to 65
-g: 65
-d: 55
-Setting value to 67
-d: 67

Modified: trunk/JSTests/ChakraCore.yaml (264573 => 264574)


--- trunk/JSTests/ChakraCore.yaml	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/JSTests/ChakraCore.yaml	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1474,7 +1474,7 @@
 - path: ChakraCore/test/es5/regexSpace.js
   cmd: runChakra :baseline, "NoException", "regexSpace.baseline", []
 - path: ChakraCore/test/es5/EnumeratingWithES5.js
-  cmd: runChakra :baseline, "NoException", "EnumeratingWithES5.baseline-jsc", []
+  cmd: runChakra :baseline, "NoException", "EnumeratingWithES5.baseline", []
 - path: ChakraCore/test/es5/InsufficientArguments.js
   cmd: runChakra :baseline, "NoException", "InsufficientArguments.baseline", []
 - path: ChakraCore/test/es5/recursivesetter.js

Modified: trunk/JSTests/ChangeLog (264573 => 264574)


--- trunk/JSTests/ChangeLog	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/JSTests/ChangeLog	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1,3 +1,24 @@
+2020-07-18  Alexey Shvayka  <[email protected]>
+
+        Redefining a property should not change its insertion index (Object.keys order)
+        https://bugs.webkit.org/show_bug.cgi?id=142933
+
+        Reviewed by Saam Barati.
+
+        * ChakraCore.yaml:
+        * ChakraCore/test/es5/EnumeratingWithES5.baseline-jsc: Removed.
+        * microbenchmarks/redefine-property-accessor-dictionary.js: Added.
+        * microbenchmarks/redefine-property-accessor.js: Added.
+        * microbenchmarks/redefine-property-data-dictionary.js: Added.
+        * microbenchmarks/redefine-property-data.js: Added.
+        * stress/define-own-indexed-property-fast-path.js:
+        * stress/redefine-property-enumerable.js: Added.
+        * stress/redefine-property-get.js: Added.
+        * stress/redefine-property-set.js: Added.
+        * stress/redefine-property-value.js: Added.
+        * stress/redefine-property-writable.js: Added.
+        * test262/expectations.yaml: Mark 12 test cases as passing.
+
 2020-07-17  Xan Lopez  <[email protected]>
 
         Math.max() can yield the wrong result for max(0, -0).

Added: trunk/JSTests/microbenchmarks/redefine-property-accessor-dictionary.js (0 => 264574)


--- trunk/JSTests/microbenchmarks/redefine-property-accessor-dictionary.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/redefine-property-accessor-dictionary.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,24 @@
+//@ skip if $memoryLimited and ["arm", "mips"].include?($architecture)
+const obj = {};
+for (let i = 0; i < 100; ++i)
+    obj["k" + i] = i;
+
+function getter1() {}
+function getter2() {}
+function setter1(v) {}
+function setter2(v) {}
+
+const descGet1 = {get: getter1, configurable: true};
+const descGet1Set1 = {get: getter1, set: setter1};
+const descGet2Set1 = {get: getter2, set: setter1};
+const descGet1Set2 = {get: getter1, set: setter2};
+const descGet2Set2 = {get: getter2, set: setter2};
+
+for (let i = 0; i < 1e4; ++i) {
+    const key = "k" + (i % 100);
+    Object.defineProperty(obj, key, descGet1);
+    Object.defineProperty(obj, key, descGet1Set1);
+    Object.defineProperty(obj, key, descGet2Set1);
+    Object.defineProperty(obj, key, descGet1Set2);
+    Object.defineProperty(obj, key, descGet2Set2);
+}

Added: trunk/JSTests/microbenchmarks/redefine-property-accessor.js (0 => 264574)


--- trunk/JSTests/microbenchmarks/redefine-property-accessor.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/redefine-property-accessor.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,19 @@
+function getter1() {}
+function getter2() {}
+function setter1(v) {}
+function setter2(v) {}
+
+const obj = {a: 0, b: 1, get foo() {}, c: 3, d: 4};
+const descGet1 = {get: getter1, configurable: true};
+const descGet1Set1 = {get: getter1, set: setter1};
+const descGet2Set1 = {get: getter2, set: setter1};
+const descGet1Set2 = {get: getter1, set: setter2};
+const descGet2Set2 = {get: getter2, set: setter2};
+
+for (let i = 0; i < 1e4; ++i) {
+    Object.defineProperty(obj, "foo", descGet1);
+    Object.defineProperty(obj, "foo", descGet1Set1);
+    Object.defineProperty(obj, "foo", descGet2Set1);
+    Object.defineProperty(obj, "foo", descGet1Set2);
+    Object.defineProperty(obj, "foo", descGet2Set2);
+}

Added: trunk/JSTests/microbenchmarks/redefine-property-data-dictionary.js (0 => 264574)


--- trunk/JSTests/microbenchmarks/redefine-property-data-dictionary.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/redefine-property-data-dictionary.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,18 @@
+//@ skip if $memoryLimited and ["arm", "mips"].include?($architecture)
+const obj = {};
+for (let i = 0; i < 100; ++i)
+    obj["k" + i] = i;
+
+const descNonWritable = {writable: false};
+const descNonEnumerable = {enumerable: false};
+const descWritable = {writable: true};
+const descEnumerable = {enumerable: true};
+
+for (let i = 0; i < 1e4; ++i) {
+    const key = "k" + (i % 100);
+    Object.defineProperty(obj, key, descNonWritable);
+    Object.defineProperty(obj, key, descNonEnumerable);
+    Object.defineProperty(obj, key, descWritable);
+    Object.defineProperty(obj, key, descEnumerable);
+    Object.defineProperty(obj, key, {value: i});
+}

Added: trunk/JSTests/microbenchmarks/redefine-property-data.js (0 => 264574)


--- trunk/JSTests/microbenchmarks/redefine-property-data.js	                        (rev 0)
+++ trunk/JSTests/microbenchmarks/redefine-property-data.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,13 @@
+const obj = {a: 0, b: 1, foo: 2, c: 3, d: 4};
+const descNonWritable = {writable: false};
+const descNonEnumerable = {enumerable: false};
+const descWritable = {writable: true};
+const descEnumerable = {enumerable: true};
+
+for (let i = 0; i < 1e4; ++i) {
+    Object.defineProperty(obj, "foo", descNonWritable);
+    Object.defineProperty(obj, "foo", descNonEnumerable);
+    Object.defineProperty(obj, "foo", descWritable);
+    Object.defineProperty(obj, "foo", descEnumerable);
+    Object.defineProperty(obj, "foo", {value: i});
+}

Modified: trunk/JSTests/stress/define-own-indexed-property-fast-path.js (264573 => 264574)


--- trunk/JSTests/stress/define-own-indexed-property-fast-path.js	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/JSTests/stress/define-own-indexed-property-fast-path.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1,19 +1,11 @@
 function assertSameDescriptor(a, b) {
-  const aString = stringifyDescriptor(a);
-  const bString = stringifyDescriptor(b);
+  const aString = JSON.stringify(a, null, 2);
+  const bString = JSON.stringify(b, null, 2);
 
   if (aString !== bString)
     throw new Error(`Bad descriptor!\nActual: ${aString}\nExpected: ${bString}`);
 }
 
-function stringifyDescriptor(descriptor) {
-  const sortedDescriptor = {};
-  for (const key of Object.keys(descriptor).sort())
-    sortedDescriptor[key] = descriptor[key];
-
-  return JSON.stringify(sortedDescriptor, null, 2);
-}
-
 function getCombinations(options) {
   const optionKeys = Object.keys(options);
   const results = [];

Added: trunk/JSTests/stress/redefine-property-enumerable.js (0 => 264574)


--- trunk/JSTests/stress/redefine-property-enumerable.js	                        (rev 0)
+++ trunk/JSTests/stress/redefine-property-enumerable.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,73 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error("bad value: " + actual);
+}
+
+function makeDictionary(dict) {
+    for (let i = 0; i < 1000; ++i)
+        dict["k" + i] = i;
+    return dict;
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        }
+        return new C();
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function test(proto, key1, key2) {
+    proto[key1] = 1;
+    proto[key2] = 2;
+    Object.defineProperty(proto, key2, {enumerable: false});
+
+    const child = Object.create(proto);
+
+    function isKey1Enumerable(obj) {
+        for (let key in obj) {
+            if (key == key1) return true;
+        }
+        return false;
+    }
+
+    function isKey2Enumerable(obj) {
+        for (let key in obj) {
+            if (key == key2) return true;
+        }
+        return false;
+    }
+
+    noInline(isKey1Enumerable);
+    noInline(isKey2Enumerable);
+
+    for (let i = 0; i < 20; ++i) {
+        shouldBe(isKey1Enumerable(child), true);
+        shouldBe(isKey1Enumerable(proto), true);
+        shouldBe(isKey2Enumerable(child), false);
+        shouldBe(isKey2Enumerable(proto), false);
+    }
+
+    Object.defineProperty(proto, key1, {enumerable: false});
+    shouldBe(isKey1Enumerable(child), false);
+    shouldBe(isKey1Enumerable(proto), false);
+
+    Object.defineProperty(proto, key2, {enumerable: true});
+    shouldBe(isKey2Enumerable(child), true);
+    shouldBe(isKey2Enumerable(proto), true);
+}
+
+test({}, 0, "bar");
+test(Object.create(null), "__proto__", 1);
+
+test(makeDictionary({}), "foo", 2);
+test(makeDictionary(Object.create(null)), 3, "__proto__");
+
+test(makePolyProtoObject(), "foo", "bar");
+test(makePolyProtoObject(), 4, 5);

Added: trunk/JSTests/stress/redefine-property-get.js (0 => 264574)


--- trunk/JSTests/stress/redefine-property-get.js	                        (rev 0)
+++ trunk/JSTests/stress/redefine-property-get.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,73 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error("bad value: " + actual);
+}
+
+function makeDictionary(dict) {
+    for (let i = 0; i < 1000; ++i)
+        dict["k" + i] = i;
+    return dict;
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        }
+        return new C();
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function test(proto, key1, key2) {
+    Object.defineProperty(proto, key1, {
+        get: () => 1,
+        configurable: true,
+    });
+
+    Object.defineProperty(proto, key2, {
+        get: () => 2,
+        configurable: true,
+    });
+
+    const child = Object.create(proto);
+
+    function getKey1(obj) {
+        return obj[key1];
+    }
+
+    function getKey2(obj) {
+        return obj[key2];
+    }
+
+    noInline(getKey1);
+    noInline(getKey2);
+
+    for (let i = 0; i < 1e4; ++i) {
+        shouldBe(getKey1(child), 1);
+        shouldBe(getKey1(proto), 1);
+        shouldBe(getKey2(child), 2);
+        shouldBe(getKey2(proto), 2);
+    }
+
+    Object.defineProperty(proto, key1, {get() {}});
+    shouldBe(getKey1(child), undefined);
+    shouldBe(getKey1(proto), undefined);
+
+    Object.defineProperty(proto, key2, {get: undefined});
+    shouldBe(getKey2(child), undefined);
+    shouldBe(getKey2(proto), undefined);
+}
+
+test({}, 0, "bar");
+test(Object.create(null), "__proto__", 1);
+
+test(makeDictionary({}), "foo", 2);
+test(makeDictionary(Object.create(null)), 3, "__proto__");
+
+test(makePolyProtoObject(), "foo", "bar");
+test(makePolyProtoObject(), 4, 5);

Added: trunk/JSTests/stress/redefine-property-set.js (0 => 264574)


--- trunk/JSTests/stress/redefine-property-set.js	                        (rev 0)
+++ trunk/JSTests/stress/redefine-property-set.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,79 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error("bad value: " + actual);
+}
+
+function makeDictionary(dict) {
+    for (let i = 0; i < 1000; ++i)
+        dict["k" + i] = i;
+    return dict;
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        }
+        return new C();
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function test(proto, key1, key2) {
+    let nextValue = 10;
+
+    let value1;
+    Object.defineProperty(proto, key1, {
+        set(val) { value1 = val; },
+        configurable: true,
+    });
+
+    let value2;
+    Object.defineProperty(proto, key2, {
+        set(val) { value2 = val; },
+        configurable: true,
+    });
+
+    const child = Object.create(proto);
+
+    function setKey1(obj) {
+        obj[key1] = ++nextValue;
+        return value1 === nextValue;
+    }
+
+    function setKey2(obj) {
+        obj[key2] = ++nextValue;
+        return value2 === nextValue;
+    }
+
+    noInline(setKey1);
+    noInline(setKey2);
+
+    for (let i = 0; i < 1e4; ++i) {
+        shouldBe(setKey1(child), true);
+        shouldBe(setKey1(proto), true);
+        shouldBe(setKey2(child), true);
+        shouldBe(setKey2(proto), true);
+    }
+
+    Object.defineProperty(proto, key1, {set: undefined});
+    shouldBe(setKey1(child), false);
+    shouldBe(setKey1(proto), false);
+
+    Object.defineProperty(proto, key2, {set() {}});
+    shouldBe(setKey2(child), false);
+    shouldBe(setKey2(proto), false);
+}
+
+test({}, 0, "bar");
+test(Object.create(null), "__proto__", 1);
+
+test(makeDictionary({}), "foo", 2);
+test(makeDictionary(Object.create(null)), 3, "__proto__");
+
+test(makePolyProtoObject(), "foo", "bar");
+test(makePolyProtoObject(), 4, 5);

Added: trunk/JSTests/stress/redefine-property-value.js (0 => 264574)


--- trunk/JSTests/stress/redefine-property-value.js	                        (rev 0)
+++ trunk/JSTests/stress/redefine-property-value.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,66 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error("bad value: " + actual);
+}
+
+function makeDictionary(dict) {
+    for (let i = 0; i < 1000; ++i)
+        dict["k" + i] = i;
+    return dict;
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        }
+        return new C();
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function test(proto, key1, key2) {
+    proto[key1] = 1;
+    proto[key2] = 2;
+
+    const child = Object.create(proto);
+
+    function getKey1(obj) {
+        return obj[key1];
+    }
+
+    function getKey2(obj) {
+        return obj[key2];
+    }
+
+    noInline(getKey1);
+    noInline(getKey2);
+
+    for (let i = 0; i < 1e4; ++i) {
+        shouldBe(getKey1(child), 1);
+        shouldBe(getKey1(proto), 1);
+        shouldBe(getKey2(child), 2);
+        shouldBe(getKey2(proto), 2);
+    }
+
+    Object.defineProperty(proto, key1, {value: 3});
+    shouldBe(getKey1(child), 3);
+    shouldBe(getKey1(proto), 3);
+
+    Object.defineProperty(proto, key2, {set() {}});
+    shouldBe(getKey2(child), undefined);
+    shouldBe(getKey2(proto), undefined);
+}
+
+test({}, 0, "bar");
+test(Object.create(null), "__proto__", 1);
+
+test(makeDictionary({}), "foo", 2);
+test(makeDictionary(Object.create(null)), 3, "__proto__");
+
+test(makePolyProtoObject(), "foo", "bar");
+test(makePolyProtoObject(), 4, 5);

Added: trunk/JSTests/stress/redefine-property-writable.js (0 => 264574)


--- trunk/JSTests/stress/redefine-property-writable.js	                        (rev 0)
+++ trunk/JSTests/stress/redefine-property-writable.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -0,0 +1,95 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error("bad value: " + actual);
+}
+
+function makeDictionary(dict) {
+    for (let i = 0; i < 1000; ++i)
+        dict["k" + i] = i;
+    return dict;
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        }
+        return new C();
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function test(proto, key1, key2) {
+    proto[key1] = 1;
+    proto[key2] = 2;
+    Object.defineProperty(proto, key2, {writable: false});
+
+    const child = Object.create(proto);
+    let nextValue = 10;
+
+    function isKey1WritableStrict(obj) {
+        "use strict";
+        try {
+            obj[key1] = ++nextValue;
+        } catch {
+            return false;
+        }
+        return true;
+    }
+
+    function isKey1Writable(obj) {
+        obj[key1] = ++nextValue;
+        return obj[key1] === nextValue;
+    }
+
+    function isKey2WritableStrict(obj) {
+        "use strict";
+        try {
+            obj[key2] = ++nextValue;
+        } catch {
+            return false;
+        }
+        return true;
+    }
+
+    function isKey2Writable(obj) {
+        obj[key2] = ++nextValue;
+        return obj[key2] === nextValue;
+    }
+
+    noInline(isKey1WritableStrict);
+    noInline(isKey1Writable);
+    noInline(isKey2WritableStrict);
+    noInline(isKey2Writable);
+
+    for (let i = 0; i < 1e4; ++i) {
+        shouldBe(isKey1WritableStrict(child), true);
+        shouldBe(isKey1Writable(proto), true);
+        shouldBe(isKey2WritableStrict(child), false);
+        shouldBe(isKey2Writable(proto), false);
+
+        delete child[key1];
+        delete child[key2];
+    }
+
+    Object.defineProperty(proto, key1, {writable: false});
+    shouldBe(isKey1WritableStrict(child), false);
+    shouldBe(isKey1Writable(proto), false);
+
+    Object.defineProperty(proto, key2, {writable: true});
+    shouldBe(isKey2WritableStrict(child), true);
+    shouldBe(isKey2Writable(proto), true);
+}
+
+test({}, 0, "bar");
+test(Object.create(null), "__proto__", 1);
+
+test(makeDictionary({}), "foo", 2);
+test(makeDictionary(Object.create(null)), 3, "__proto__");
+
+test(makePolyProtoObject(), "foo", "bar");
+test(makePolyProtoObject(), 4, 5);

Modified: trunk/JSTests/test262/expectations.yaml (264573 => 264574)


--- trunk/JSTests/test262/expectations.yaml	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/JSTests/test262/expectations.yaml	2020-07-18 23:09:56 UTC (rev 264574)
@@ -910,23 +910,14 @@
   default: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
   strict mode: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
 test/built-ins/Object/entries/order-after-define-property.js:
-  default: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-  strict mode: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
+  default: 'Test262Error: Expected [a, name] and [name, a] to have the same contents. '
+  strict mode: 'Test262Error: Expected [a, name] and [name, a] to have the same contents. '
 test/built-ins/Object/freeze/proxy-no-ownkeys-returned-keys-order.js:
   default: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
   strict mode: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
-test/built-ins/Object/getOwnPropertyDescriptors/order-after-define-property.js:
-  default: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
-  strict mode: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
 test/built-ins/Object/getOwnPropertyDescriptors/proxy-no-ownkeys-returned-keys-order.js:
   default: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
   strict mode: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
-test/built-ins/Object/getOwnPropertyNames/order-after-define-property.js:
-  default: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-  strict mode: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-test/built-ins/Object/getOwnPropertySymbols/order-after-define-property.js:
-  default: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
-  strict mode: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
 test/built-ins/Object/internals/DefineOwnProperty/consistent-value-function-arguments.js:
   default: 'Test262Error: Expected SameValue(«null», «[object Arguments]») to be true'
 test/built-ins/Object/internals/DefineOwnProperty/consistent-value-function-caller.js:
@@ -941,14 +932,11 @@
   default: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
   strict mode: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
 test/built-ins/Object/keys/order-after-define-property.js:
-  default: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-  strict mode: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
+  default: 'Test262Error: Expected [a, length] and [length, a] to have the same contents. '
+  strict mode: 'Test262Error: Expected [a, length] and [length, a] to have the same contents. '
 test/built-ins/Object/seal/proxy-no-ownkeys-returned-keys-order.js:
   default: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
   strict mode: 'Test262Error: Expected [0, Symbol(), foo] and [0, foo, Symbol()] to have the same contents. '
-test/built-ins/Object/values/order-after-define-property.js:
-  default: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-  strict mode: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
 test/built-ins/Proxy/apply/arguments-realm.js:
   default: 'Test262Error: Expected SameValue(«function Array() {'
   strict mode: 'Test262Error: Expected SameValue(«function Array() {'
@@ -988,9 +976,6 @@
 test/built-ins/Proxy/ownKeys/trap-is-undefined-target-is-proxy.js:
   default: 'Test262Error: Expected [length, foo, 0, Symbol()] and [Symbol(), length, foo, 0] to have the same contents. '
   strict mode: 'Test262Error: Expected [length, foo, 0, Symbol()] and [Symbol(), length, foo, 0] to have the same contents. '
-test/built-ins/Reflect/ownKeys/order-after-define-property.js:
-  default: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
-  strict mode: 'Test262Error: Expected [Symbol(b), Symbol(a)] and [Symbol(a), Symbol(b)] to have the same contents. '
 test/built-ins/RegExp/property-escapes/generated/Alphabetic.js:
   default: 'Test262Error: `\p{Alphabetic}` should match U+001CFA (`ᳺ`)'
   strict mode: 'Test262Error: `\p{Alphabetic}` should match U+001CFA (`ᳺ`)'
@@ -3204,9 +3189,6 @@
   default: "SyntaxError: Cannot use the keyword 'in' as a lexical variable name."
 test/language/statements/for-in/let-array-with-newline.js:
   default: 'Test262: This statement should not be evaluated.'
-test/language/statements/for-in/order-after-define-property.js:
-  default: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
-  strict mode: 'Test262Error: Expected [b, a] and [a, b] to have the same contents. '
 test/language/statements/for-in/order-enumerable-shadowed.js:
   default: 'Test262Error: Expected [p1, p2] and [p1] to have the same contents. '
   strict mode: 'Test262Error: Expected [p1, p2] and [p1] to have the same contents. '

Modified: trunk/LayoutTests/ChangeLog (264573 => 264574)


--- trunk/LayoutTests/ChangeLog	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/LayoutTests/ChangeLog	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1,3 +1,13 @@
+2020-07-18  Alexey Shvayka  <[email protected]>
+
+        Redefining a property should not change its insertion index (Object.keys order)
+        https://bugs.webkit.org/show_bug.cgi?id=142933
+
+        Reviewed by Saam Barati.
+
+        * js/object-literal-duplicate-properties-expected.txt:
+        * js/script-tests/object-literal-duplicate-properties.js:
+
 2020-07-18  Chris Dumez  <[email protected]>
 
         Unreviewed, skip a few css WPT tests that are crashing in Debug since the recent WPT resync.

Modified: trunk/LayoutTests/js/object-literal-duplicate-properties-expected.txt (264573 => 264574)


--- trunk/LayoutTests/js/object-literal-duplicate-properties-expected.txt	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/LayoutTests/js/object-literal-duplicate-properties-expected.txt	2020-07-18 23:09:56 UTC (rev 264574)
@@ -14,7 +14,7 @@
 PASS x is 4
 PASS o.foo is 3
 PASS o.bar is undefined
-FAIL Object.keys(o).join() should be foo,test1,bar,test2,test3,nest. Was test1,test2,test3,foo,bar,nest.
+PASS Object.keys(o).join() is 'foo,test1,bar,test2,test3,nest'
 
 Basic
 PASS o = {foo:1}; descriptionString(o, 'foo'); is 'value:1 keys:1 [ECW][Extensible]'

Modified: trunk/LayoutTests/js/script-tests/object-literal-duplicate-properties.js (264573 => 264574)


--- trunk/LayoutTests/js/script-tests/object-literal-duplicate-properties.js	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/LayoutTests/js/script-tests/object-literal-duplicate-properties.js	2020-07-18 23:09:56 UTC (rev 264574)
@@ -9,7 +9,7 @@
 shouldBe("x", "1");
 shouldBe("o.foo", "'getter'");
 
-// Ensure getters/setters get eliminated by computed properties.
+// Ensure getters/setters get eliminated by basic properties.
 x = 0;
 o = {
     get foo() {return "getter"},
@@ -54,7 +54,6 @@
 shouldBe("x", "4");
 shouldBe("o.foo", "3");
 shouldBe("o.bar", "undefined");
-// FIXME: <https://webkit.org/b/142933> Redefining a property should not change its insertion index (Object.keys order)
 shouldBe("Object.keys(o).join()", "'foo,test1,bar,test2,test3,nest'");
 
 

Modified: trunk/Source/_javascript_Core/ChangeLog (264573 => 264574)


--- trunk/Source/_javascript_Core/ChangeLog	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-07-18 23:09:56 UTC (rev 264574)
@@ -1,3 +1,39 @@
+2020-07-18  Alexey Shvayka  <[email protected]>
+
+        Redefining a property should not change its insertion index (Object.keys order)
+        https://bugs.webkit.org/show_bug.cgi?id=142933
+
+        Reviewed by Saam Barati.
+
+        Before this change, JSC used to delete & put back a non-indexed property just to
+        update attributes, which was less efficient and corrupted observable property order.
+
+        This patch:
+        1. Rewrites validateAndApplyPropertyDescriptor() to closely resemble the spec [1].
+        2. Drops property deletion, inlines putDescriptor(), and sets necessary Structure
+           flags in attributeChangeTransition().
+        3. Simplifies validateAndApplyPropertyDescriptor() a bit by obtaining GetterSetter
+           instance from current descriptor rather then calling getDirect().
+
+        This change aligns property order with V8 and SpiderMonkey, advancing provided
+        microbenchmarks by 5-85% (especially for objects in dictionary mode).
+        SixSpeed, SunSpider, and ARES-6 are all neutral.
+
+        [1]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
+
+        * runtime/JSObject.cpp:
+        (JSC::validateAndApplyPropertyDescriptor):
+        (JSC::JSObject::defineOwnNonIndexProperty):
+        (JSC::putDescriptor): Deleted.
+        * runtime/JSObjectInlines.h:
+        (JSC::JSObject::putDirectInternal):
+        * runtime/PropertyDescriptor.cpp:
+        (JSC::PropertyDescriptor::slowGetterSetter const):
+        (JSC::PropertyDescriptor::slowGetterSetter): Deleted.
+        * runtime/PropertyDescriptor.h:
+        * runtime/Structure.cpp:
+        (JSC::Structure::attributeChangeTransition):
+
 2020-07-17  Yusuke Suzuki  <[email protected]>
 
         [JSC] Clean up resolveLocale

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (264573 => 264574)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2020-07-18 23:09:56 UTC (rev 264574)
@@ -3551,41 +3551,6 @@
     return true;
 }
 
-static bool putDescriptor(JSGlobalObject* globalObject, JSObject* target, PropertyName propertyName, const PropertyDescriptor& descriptor, unsigned attributes, const PropertyDescriptor& oldDescriptor)
-{
-    VM& vm = globalObject->vm();
-    if (descriptor.isGenericDescriptor() || descriptor.isDataDescriptor()) {
-        if (descriptor.isGenericDescriptor() && oldDescriptor.isAccessorDescriptor()) {
-            JSObject* getter = oldDescriptor.getterPresent() ? oldDescriptor.getterObject() : nullptr;
-            JSObject* setter = oldDescriptor.setterPresent() ? oldDescriptor.setterObject() : nullptr;
-            GetterSetter* accessor = GetterSetter::create(vm, globalObject, getter, setter);
-            target->putDirectAccessor(globalObject, propertyName, accessor, attributes | PropertyAttribute::Accessor);
-            return true;
-        }
-        JSValue newValue = jsUndefined();
-        if (descriptor.value())
-            newValue = descriptor.value();
-        else if (oldDescriptor.value())
-            newValue = oldDescriptor.value();
-        target->putDirect(vm, propertyName, newValue, attributes & ~PropertyAttribute::Accessor);
-        if (attributes & PropertyAttribute::ReadOnly)
-            target->structure(vm)->setContainsReadOnlyProperties();
-        return true;
-    }
-    attributes &= ~PropertyAttribute::ReadOnly;
-
-    JSObject* getter = descriptor.getterPresent()
-        ? descriptor.getterObject() : oldDescriptor.getterPresent()
-        ? oldDescriptor.getterObject() : nullptr;
-    JSObject* setter = descriptor.setterPresent()
-        ? descriptor.setterObject() : oldDescriptor.setterPresent()
-        ? oldDescriptor.setterObject() : nullptr;
-    GetterSetter* accessor = GetterSetter::create(vm, globalObject, getter, setter);
-
-    target->putDirectAccessor(globalObject, propertyName, accessor, attributes | PropertyAttribute::Accessor);
-    return true;
-}
-
 bool JSObject::putDirectMayBeIndex(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value)
 {
     if (Optional<uint32_t> index = parseIndex(propertyName))
@@ -3593,8 +3558,7 @@
     return putDirect(globalObject->vm(), propertyName, value);
 }
 
-// 9.1.6.3 of the spec
-// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-validateandapplypropertydescriptor
+// https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
 bool validateAndApplyPropertyDescriptor(JSGlobalObject* globalObject, JSObject* object, PropertyName propertyName, bool isExtensible,
     const PropertyDescriptor& descriptor, bool isCurrentDefined, const PropertyDescriptor& current, bool throwException)
 {
@@ -3608,25 +3572,30 @@
         // Step 2.a
         if (!isExtensible)
             return typeError(globalObject, scope, throwException, NonExtensibleObjectPropertyDefineError);
-        if (!object)
-            return true;
-        // Step 2.c/d
-        PropertyDescriptor oldDescriptor;
-        oldDescriptor.setValue(jsUndefined());
-        // FIXME: spec says to always return true here.
-        return putDescriptor(globalObject, object, propertyName, descriptor, descriptor.attributes(), oldDescriptor);
+
+        if (object) {
+            if (descriptor.isAccessorDescriptor()) {
+                unsigned attributes = (descriptor.attributes() | PropertyAttribute::Accessor) & ~PropertyAttribute::ReadOnly;
+                object->putDirectAccessor(globalObject, propertyName, descriptor.slowGetterSetter(globalObject), attributes);
+            } else {
+                ASSERT(descriptor.isGenericDescriptor() || descriptor.isDataDescriptor());
+                JSValue value = descriptor.value() ? descriptor.value() : jsUndefined();
+                object->putDirect(vm, propertyName, value, descriptor.attributes() & ~PropertyAttribute::Accessor);
+            }
+        }
+
+        return true;
     }
     // Step 3.
     if (descriptor.isEmpty())
         return true;
-    // Step 4.
+
     bool isEqual = current.equalTo(globalObject, descriptor);
     RETURN_IF_EXCEPTION(scope, false);
     if (isEqual)
         return true;
 
-    // Step 5.
-    // Filter out invalid changes
+    // Step 4.
     if (!current.configurable()) {
         if (descriptor.configurable())
             return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeConfigurabilityError);
@@ -3633,108 +3602,58 @@
         if (descriptor.enumerablePresent() && descriptor.enumerable() != current.enumerable())
             return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeEnumerabilityError);
     }
-    
-    // Step 6.
-    // A generic descriptor is simply changing the attributes of an existing property
+
     if (descriptor.isGenericDescriptor()) {
-        if (!current.attributesEqual(descriptor) && object) {
-            JSCell::deleteProperty(object, globalObject, propertyName);
-            RETURN_IF_EXCEPTION(scope, false);
-            return putDescriptor(globalObject, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
-        }
-        return true;
-    }
-    
-    // Step 7.
-    // Changing between a normal property or an accessor property
-    if (descriptor.isDataDescriptor() != current.isDataDescriptor()) {
+        // Step 5.
+        // Changing [[Enumerable]] and [[Configurable]] attributes of an existing property
+    } else if (current.isDataDescriptor() != descriptor.isDataDescriptor()) {
+        // Step 6.
+        // Changing between a data property and accessor property
         if (!current.configurable())
             return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
+    } else if (current.isDataDescriptor() && descriptor.isDataDescriptor()) {
+        // Step 7.
+        // Changing the value and attributes of an existing data property
+        if (!current.configurable() && !current.writable()) {
+            if (descriptor.writable())
+                return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeWritabilityError);
+            if (descriptor.value() && !sameValue(globalObject, current.value(), descriptor.value()))
+                return typeError(globalObject, scope, throwException, ReadonlyPropertyChangeError);
 
-        if (!object)
             return true;
+        }
+    } else {
+        // Step 8.
+        // Changing the accessor functions and attributes of an existing accessor property
+        ASSERT(descriptor.isAccessorDescriptor());
+        if (!current.configurable()) {
+            if (descriptor.setterPresent() && descriptor.setter() != current.setter())
+                return typeError(globalObject, scope, throwException, "Attempting to change the setter of an unconfigurable property."_s);
+            if (descriptor.getterPresent() && descriptor.getter() != current.getter())
+                return typeError(globalObject, scope, throwException, "Attempting to change the getter of an unconfigurable property."_s);
+            if (current.attributes() & PropertyAttribute::CustomAccessor)
+                return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
 
-        JSCell::deleteProperty(object, globalObject, propertyName);
-        RETURN_IF_EXCEPTION(scope, false);
-        return putDescriptor(globalObject, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
-    }
-
-    // Step 8.
-    // Changing the value and attributes of an existing property
-    if (descriptor.isDataDescriptor()) {
-        if (!current.configurable()) {
-            if (!current.writable() && descriptor.writable())
-                return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeWritabilityError);
-            if (!current.writable()) {
-                if (descriptor.value()) {
-                    bool isSame = sameValue(globalObject, current.value(), descriptor.value());
-                    RETURN_IF_EXCEPTION(scope, false);
-                    if (!isSame)
-                        return typeError(globalObject, scope, throwException, ReadonlyPropertyChangeError);
-                }
-            }
+            return true;
         }
-        if (current.attributesEqual(descriptor) && !descriptor.value())
-            return true;
-        if (!object)
-            return true;
-        JSCell::deleteProperty(object, globalObject, propertyName);
-        RETURN_IF_EXCEPTION(scope, false);
-        return putDescriptor(globalObject, object, propertyName, descriptor, descriptor.attributesOverridingCurrent(current), current);
     }
 
-    // Step 9.
-    // Changing the accessor functions of an existing accessor property
-    ASSERT(descriptor.isAccessorDescriptor());
-    if (!current.configurable()) {
-        if (descriptor.setterPresent() && !(current.setterPresent() && JSValue::strictEqual(globalObject, current.setter(), descriptor.setter())))
-            return typeError(globalObject, scope, throwException, "Attempting to change the setter of an unconfigurable property."_s);
-        if (descriptor.getterPresent() && !(current.getterPresent() && JSValue::strictEqual(globalObject, current.getter(), descriptor.getter())))
-            return typeError(globalObject, scope, throwException, "Attempting to change the getter of an unconfigurable property."_s);
-        if (current.attributes() & PropertyAttribute::CustomAccessor)
-            return typeError(globalObject, scope, throwException, UnconfigurablePropertyChangeAccessMechanismError);
-    }
-
-    // Step 10/11.
     if (!object)
         return true;
-    JSValue accessor = object->getDirect(vm, propertyName);
-    if (!accessor)
-        return false;
-    JSObject* getter = nullptr;
-    JSObject* setter = nullptr;
-    bool getterSetterChanged = false;
-
-    if (accessor.isCustomGetterSetter()) {
-        auto* customGetterSetter = jsCast<CustomGetterSetter*>(accessor);
-        if (customGetterSetter->setter())
-            setter = getCustomGetterSetterFunctionForGetterSetter(globalObject, propertyName, customGetterSetter, JSCustomGetterSetterFunction::Type::Setter);
-        if (customGetterSetter->getter())
-            getter = getCustomGetterSetterFunctionForGetterSetter(globalObject, propertyName, customGetterSetter, JSCustomGetterSetterFunction::Type::Getter);
+    // Step 9.
+    unsigned attributes = descriptor.attributesOverridingCurrent(current);
+    if (descriptor.isAccessorDescriptor() || (current.isAccessorDescriptor() && !descriptor.isDataDescriptor())) {
+        ASSERT(attributes & PropertyAttribute::Accessor);
+        JSObject* getter = descriptor.getterPresent() ? descriptor.getterObject() : (current.getterPresent() ? current.getterObject() : nullptr);
+        JSObject* setter = descriptor.setterPresent() ? descriptor.setterObject() : (current.setterPresent() ? current.setterObject() : nullptr);
+        GetterSetter* getterSetter = GetterSetter::create(vm, globalObject, getter, setter);
+        object->putDirectAccessor(globalObject, propertyName, getterSetter, attributes & ~PropertyAttribute::ReadOnly);
     } else {
-        ASSERT(accessor.isGetterSetter());
-        auto* getterSetter = jsCast<GetterSetter*>(accessor);
-        getter = getterSetter->getter();
-        setter = getterSetter->setter();
+        ASSERT(descriptor.isGenericDescriptor() || descriptor.isDataDescriptor());
+        JSValue value = descriptor.value() ? descriptor.value() : (current.value() ? current.value() : jsUndefined());
+        object->putDirect(vm, propertyName, value, attributes & ~PropertyAttribute::Accessor);
     }
-    if (descriptor.setterPresent()) {
-        setter = descriptor.setterObject();
-        getterSetterChanged = true;
-    }
-    if (descriptor.getterPresent()) {
-        getter = descriptor.getterObject();
-        getterSetterChanged = true;
-    }
 
-    if (current.attributesEqual(descriptor) && !getterSetterChanged)
-        return true;
-
-    GetterSetter* getterSetter = GetterSetter::create(vm, globalObject, getter, setter);
-
-    JSCell::deleteProperty(object, globalObject, propertyName);
-    RETURN_IF_EXCEPTION(scope, false);
-    unsigned attrs = descriptor.attributesOverridingCurrent(current);
-    object->putDirectAccessor(globalObject, propertyName, getterSetter, attrs | PropertyAttribute::Accessor);
     return true;
 }
 
@@ -3743,11 +3662,6 @@
     VM& vm  = globalObject->vm();
     auto throwScope = DECLARE_THROW_SCOPE(vm);
 
-    // Track on the globaldata that we're in define property.
-    // Currently DefineOwnProperty uses delete to remove properties when they are being replaced
-    // (particularly when changing attributes), however delete won't allow non-configurable (i.e.
-    // DontDelete) properties to be deleted. For now, we can use this flag to make this work.
-    VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
     PropertyDescriptor current;
     bool isCurrentDefined = getOwnPropertyDescriptor(globalObject, propertyName, current);
     RETURN_IF_EXCEPTION(throwScope, false);

Modified: trunk/Source/_javascript_Core/runtime/JSObjectInlines.h (264573 => 264574)


--- trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/runtime/JSObjectInlines.h	2020-07-18 23:09:56 UTC (rev 264574)
@@ -314,10 +314,11 @@
             putDirect(vm, offset, value);
             structure->didReplaceProperty(offset);
 
-            if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessorOrValue) != (currentAttributes & PropertyAttribute::CustomAccessorOrValue)) {
-                ASSERT(!(attributes & PropertyAttribute::ReadOnly));
+            // FIXME: Check attributes against PropertyAttribute::CustomAccessorOrValue. Changing GetterSetter should work w/o transition.
+            // https://bugs.webkit.org/show_bug.cgi?id=214342
+            if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))
                 setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
-            } else
+            else
                 slot.setExistingProperty(this, offset);
 
             return true;
@@ -368,10 +369,11 @@
         structure->didReplaceProperty(offset);
         putDirect(vm, offset, value);
 
-        if ((attributes & PropertyAttribute::Accessor) != (currentAttributes & PropertyAttribute::Accessor) || (attributes & PropertyAttribute::CustomAccessorOrValue) != (currentAttributes & PropertyAttribute::CustomAccessorOrValue)) {
-            ASSERT(!(attributes & PropertyAttribute::ReadOnly));
+        // FIXME: Check attributes against PropertyAttribute::CustomAccessorOrValue. Changing GetterSetter should work w/o transition.
+        // https://bugs.webkit.org/show_bug.cgi?id=214342
+        if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))
             setStructure(vm, Structure::attributeChangeTransition(vm, structure, propertyName, attributes));
-        } else
+        else
             slot.setExistingProperty(this, offset);
 
         return true;

Modified: trunk/Source/_javascript_Core/runtime/PropertyDescriptor.cpp (264573 => 264574)


--- trunk/Source/_javascript_Core/runtime/PropertyDescriptor.cpp	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/runtime/PropertyDescriptor.cpp	2020-07-18 23:09:56 UTC (rev 264574)
@@ -69,7 +69,7 @@
     m_attributes = PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete | PropertyAttribute::DontEnum;
 }
 
-GetterSetter* PropertyDescriptor::slowGetterSetter(JSGlobalObject* globalObject)
+GetterSetter* PropertyDescriptor::slowGetterSetter(JSGlobalObject* globalObject) const
 {
     VM& vm = globalObject->vm();
     JSValue getter = m_getter && !m_getter.isUndefined() ? jsCast<JSObject*>(m_getter) : jsUndefined();

Modified: trunk/Source/_javascript_Core/runtime/PropertyDescriptor.h (264573 => 264574)


--- trunk/Source/_javascript_Core/runtime/PropertyDescriptor.h	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/runtime/PropertyDescriptor.h	2020-07-18 23:09:56 UTC (rev 264574)
@@ -57,7 +57,7 @@
     JS_EXPORT_PRIVATE bool isAccessorDescriptor() const;
     unsigned attributes() const { return m_attributes; }
     JSValue value() const { return m_value; }
-    GetterSetter* slowGetterSetter(JSGlobalObject*); // Be aware that this will lazily allocate a GetterSetter object. It's much better to use getter() and setter() individually if possible.
+    GetterSetter* slowGetterSetter(JSGlobalObject*) const; // Be aware that this will lazily allocate a GetterSetter object. It's much better to use getter() and setter() individually if possible.
     JS_EXPORT_PRIVATE JSValue getter() const;
     JS_EXPORT_PRIVATE JSValue setter() const;
     JSObject* getterObject() const;

Modified: trunk/Source/_javascript_Core/runtime/Structure.cpp (264573 => 264574)


--- trunk/Source/_javascript_Core/runtime/Structure.cpp	2020-07-18 22:53:42 UTC (rev 264573)
+++ trunk/Source/_javascript_Core/runtime/Structure.cpp	2020-07-18 23:09:56 UTC (rev 264574)
@@ -723,6 +723,12 @@
     ASSERT(entry);
     entry->attributes = attributes;
 
+    if (attributes & PropertyAttribute::ReadOnly)
+        structure->setContainsReadOnlyProperties();
+
+    if (attributes & PropertyAttribute::DontEnum)
+        structure->setIsQuickPropertyAccessAllowedForEnumeration(false);
+
     structure->checkOffsetConsistency();
     return structure;
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to