<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>202139</bug_id>
          
          <creation_ts>2019-09-24 02:45:32 -0700</creation_ts>
          <short_desc>Object spread ({ ... } syntax): object key order is modified</short_desc>
          <delta_ts>2020-08-30 04:27:42 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>JavaScriptCore</component>
          <version>Other</version>
          <rep_platform>Mac</rep_platform>
          <op_sys>macOS 10.14</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>DUPLICATE</resolution>
          <dup_id>142933</dup_id>
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Normal</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter name="Damien Seguin">dmnsgn</reporter>
          <assigned_to name="Nobody">webkit-unassigned</assigned_to>
          <cc>ashvayka</cc>
    
    <cc>bakkot</cc>
    
    <cc>fpizlo</cc>
    
    <cc>ross.kirsling</cc>
    
    <cc>ticaiolima</cc>
    
    <cc>webkit-bug-importer</cc>
    
    <cc>ysuzuki</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>1573615</commentid>
    <comment_count>0</comment_count>
    <who name="Damien Seguin">dmnsgn</who>
    <bug_when>2019-09-24 02:45:32 -0700</bug_when>
    <thetext>Version: Safari 13

Using Object assign as follow:
Object.assign({ a: 1, b: 100, c: 3 }, { b: 2 })
the output will be: {a: 1, b: 2, c: 3} where the object key order is a,b,c.

However, using Object spread syntax as follow:
{ ...{ a: 1, b: 100, c: 3 }, ...{ b: 2 } }
the output will be: {a: 1, c: 3, b: 2} where the object key order is a,c,b.

This behaviour is different from Chrome where key order is preserved and I guess the underlying question here is:
is this following ECMA specification? I thought the spec was clear since ES2015.

See the following jsfiddle output to compare: https://jsfiddle.net/dmnsgn/r1n8q6o2/

Thanks</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1573925</commentid>
    <comment_count>1</comment_count>
    <who name="Ross Kirsling">ross.kirsling</who>
    <bug_when>2019-09-24 18:41:36 -0700</bug_when>
    <thetext>Spec-wise, it apparently all depends on *how* you get the keys out of the object.

Object.{keys, values, entries} and JSON.stringify apparently use EnumerableOwnPropertyNames which ultimately states &quot;[t]he mechanics and order of enumerating the properties is not specified&quot;.
(https://tc39.es/ecma262/#sec-enumerableownpropertynames)

That said, Reflect.ownKeys uses OrdinaryOwnPropertyKeys which states that string keys should be in &quot;ascending chronological order of property creation&quot;.
(https://tc39.es/ecma262/#sec-ordinaryownpropertykeys)

So we definitely have a bug with the latter:
  Reflect.ownKeys(Object.assign({}, { a: 1, b: 100, c: 3 }, { b: 3 }))
  &gt; a,b,c
  Reflect.ownKeys({ ...{ a: 1, b: 100, c: 3 }, ...{ b: 3 } })
  &gt; a,c,b

And fixing that may result in a &quot;fix&quot; for the behavior you mentioned too.

This would certainly be desirable, because there is in fact a proposal aiming to (partially) specify the order of EnumerableOwnPropertyNames which is about to reach stage 3 (https://github.com/tc39/proposal-for-in-order/).</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1573943</commentid>
    <comment_count>2</comment_count>
    <who name="Kevin Gibbons">bakkot</who>
    <bug_when>2019-09-24 21:36:41 -0700</bug_when>
    <thetext>Poking around some, it looks like the bug is actually in defineProperty, as reproduced by

Reflect.ownKeys(Object.defineProperty({ a: 0, b: 0 }, &apos;a&apos;, { value: 1, enumerable: true, configurable: true, writable: true }))

which outputs

[&quot;b&quot;, &quot;a&quot;]

which is backwards from what the spec requires. Contrast the essentially equivalent

x = { a: 0, b: 0 }; x.a = 1; Reflect.ownKeys(x)

which outputs

[&quot;a&quot;, b&quot;]

as it should.

---


Tracking this down, ObjectSpreadExpression is implemented in terms of copyDataPropertiesNoExclusionsPrivateName
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp#L4745-L4751

which is implemented in terms of defineEnumerableWritableConfigurableDataProperty
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/builtins/GlobalOperations.js#L114-L137

which is implemented in terms of emitCallDefineProperty
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp#L1399-L1411

which is implemented in terms of OpDefineDataProperty
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp#L3443

which I _think_ (it is hard to follow the macros) is implemented in terms of JSObject::defineOwnProperty
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/dfg/DFGOperations.cpp#L1627-L1647

which is implemented in terms of defineOwnNonIndexProperty, which is implemented in terms of validateAndApplyPropertyDescriptor
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/runtime/JSObject.cpp#L3718-L3749

which (as per the note in the previous code block) performs a delete when updating an existing property
https://github.com/WebKit/webkit/blob/2d7b35108aa7c8cd1c4ee0f4f6070206a55d5ad6/Source/JavaScriptCore/runtime/JSObject.cpp#L3643-L3658

which is the bug: it changes the order in which properties appear to have been added to the object, which is observable with Reflect.ownKeys. I don&apos;t know why it does that delete.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1573954</commentid>
    <comment_count>3</comment_count>
    <who name="Kevin Gibbons">bakkot</who>
    <bug_when>2019-09-24 23:03:39 -0700</bug_when>
    <thetext>Unfortunately, just deleting the relevant

object-&gt;methodTable(vm)-&gt;deleteProperty(object, exec, propertyName)

line in validateAndApplyPropertyDescriptor causes

Object.getOwnPropertyDescriptor(Object.defineProperty({ a: 0 }, &apos;a&apos;, { value: 1, enumerable: false }), &apos;a&apos;).enumerable

to start returning `true` instead of `false`, so some more complex solution will be necessary to allow updating the attributes of an existing property without first deleting it.

---

Also, note that any fix to this will require making all of

Reflect.ownKeys(Object.defineProperty({ a: 0, b: 0 }, &apos;a&apos;, { enumerable: false }))
Reflect.ownKeys(Object.defineProperty({ a: 0, b: 0 }, &apos;a&apos;, { get: () =&gt; 0 }))
Reflect.ownKeys(Object.defineProperty(Object.create(null, { a: { get: () =&gt; {}, configurable: true }, b: { get: () =&gt; {}, configurable: true } }), &apos;a&apos;, { value: 1 }))
Reflect.ownKeys(Object.defineProperty(Object.create(null, { a: { get: () =&gt; {}, configurable: true }, b: { get: () =&gt; {}, configurable: true } }), &apos;a&apos;, { get: () =&gt; {} }))

return `[&quot;a&quot;,&quot;b&quot;]` as well. (There are four distinct deleteProperty calls in validateAndApplyPropertyDescriptor, each of which needs to be avoided to preserve property enumeration order.)</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1574179</commentid>
    <comment_count>4</comment_count>
    <who name="Ross Kirsling">ross.kirsling</who>
    <bug_when>2019-09-25 13:08:41 -0700</bug_when>
    <thetext>Looks like the deletes were called out as a place for future improvement when Object.defineProperty was first implemented in r48542 (at which time this behavior wouldn&apos;t&apos;ve been a bug).</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1574256</commentid>
    <comment_count>5</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2019-09-25 15:42:18 -0700</bug_when>
    <thetext>&lt;rdar://problem/55721999&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>1684011</commentid>
    <comment_count>6</comment_count>
    <who name="Alexey Shvayka">ashvayka</who>
    <bug_when>2020-08-30 04:27:42 -0700</bug_when>
    <thetext>(In reply to Kevin Gibbons from comment #2)
&gt; which is the bug: it changes the order in which properties appear to have
&gt; been added to the object, which is observable with Reflect.ownKeys. I don&apos;t
&gt; know why it does that delete.

Thank you Kevin for super-detailed investigation!

r264574 replaces deleteProperty() calls in validateAndApplyPropertyDescriptor() with attribute-change transition, fixing the object spread order and the following test cases:

(In reply to Kevin Gibbons from comment #3)
&gt; Also, note that any fix to this will require making all of
&gt; 
&gt; Reflect.ownKeys(Object.defineProperty({ a: 0, b: 0 }, &apos;a&apos;, { enumerable:
&gt; false }))
&gt; Reflect.ownKeys(Object.defineProperty({ a: 0, b: 0 }, &apos;a&apos;, { get: () =&gt; 0 }))
&gt; Reflect.ownKeys(Object.defineProperty(Object.create(null, { a: { get: () =&gt;
&gt; {}, configurable: true }, b: { get: () =&gt; {}, configurable: true } }), &apos;a&apos;,
&gt; { value: 1 }))
&gt; Reflect.ownKeys(Object.defineProperty(Object.create(null, { a: { get: () =&gt;
&gt; {}, configurable: true }, b: { get: () =&gt; {}, configurable: true } }), &apos;a&apos;,
&gt; { get: () =&gt; {} }))
&gt; 
&gt; return `[&quot;a&quot;,&quot;b&quot;]` as well. (There are four distinct deleteProperty calls in validateAndApplyPropertyDescriptor, each of which needs to be avoided to preserve property enumeration order.)

Test262 coverage: https://github.com/tc39/test262/pull/2516.

*** This bug has been marked as a duplicate of bug 142933 ***</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>