Bug 142933 - Redefining a property should not change its insertion index (Object.keys order)
Summary: Redefining a property should not change its insertion index (Object.keys order)
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P2 Minor
Assignee: Alexey Shvayka
URL:
Keywords: InRadar
: 202139 (view as bug list)
Depends on: 214089
Blocks: 163446
  Show dependency treegraph
 
Reported: 2015-03-21 00:03 PDT by Joseph Pecoraro
Modified: 2020-11-05 09:23 PST (History)
13 users (show)

See Also:


Attachments
[PATCH] Work In Progress - Needs Cleanup (11.07 KB, patch)
2015-03-23 12:10 PDT, Joseph Pecoraro
no flags Details | Formatted Diff | Diff
Patch (51.05 KB, patch)
2020-05-04 08:53 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (61.05 KB, patch)
2020-05-04 14:27 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (50.45 KB, patch)
2020-05-07 13:08 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (50.89 KB, patch)
2020-05-18 11:57 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (53.37 KB, patch)
2020-06-17 16:42 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (53.34 KB, patch)
2020-07-07 08:21 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (53.07 KB, patch)
2020-07-09 17:10 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff
Patch (51.91 KB, patch)
2020-07-14 23:35 PDT, Alexey Shvayka
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Joseph Pecoraro 2015-03-21 00:03:30 PDT
* SUMMARY
Redefining a property should not change its index in Object.keys.

* TEST:
var o = {};
o.x = 1;
o.y = 2;
o.__defineGetter__("x", function() {});
Object.keys(o);

* EXPECTED
["x", "y"]

* ACTUAL
["y", "x"]

* NOTES
- Firefox outputs expected
- Chrome outputs expected
Comment 1 Joseph Pecoraro 2015-03-21 00:04:37 PDT
My guess would be the deleteProperty called from JSObject::defineOwnNonIndexProperty, removes the "x" and then re-adds "x" to the end. We should have a replace here, that avoids the deletion.
Comment 2 Joseph Pecoraro 2015-03-22 00:01:58 PDT
Relevant parts of the spec are:

https://people.mozilla.org/~jorendorff/es6-draft.html#sec-validateandapplypropertydescriptor
- [[DefineOwnProperty]] -> ValidateAndApplyPropertyDescriptor -> "Convert the property ... from a data property to an accessor property"

https://people.mozilla.org/~jorendorff/es6-draft.html#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
- [[OwnPropertyKeys]] -> enumerates indexes, properties "in property creation order", and Symbols in proprty creation order

Though __defineGetter__ and __defineSetter__ are not specced, this should be no different then a Object.defineProperty changing a data descriptor to an accessor (and vice versa).

Now lets see if there is a JSC internal reason for the property deletion.
Comment 3 Joseph Pecoraro 2015-03-22 00:11:34 PDT
Doesn't appear to be any particular reason. The implementation has always had a delete since Object.defineProperty was first added for ES5 support years ago. ES5 has the update wording here.
Comment 4 Joseph Pecoraro 2015-03-22 00:13:16 PDT
Hah, the ChangeLog had:

> Currently defineOwnProperty uses a delete followed by a put to redefine
> attributes of a property, clearly this is less efficient than it could be
> but we can improve this if it needs to be possible in future.

I guess now is that time! But necessarily for efficiency =).
Comment 5 Joseph Pecoraro 2015-03-23 12:10:10 PDT
Created attachment 249247 [details]
[PATCH] Work In Progress - Needs Cleanup

Work in progress, not for review. Just saving my progress. I'll likely not pick this up again for a few weeks.

  - passes all tests as written
  - need to write many new tests to test this
  - needs some cleanup, as noted by FIXMEs
  - we may be able to remove DefineOwnPropertyScope and its uses throughout JSC
Comment 6 Alexey Shvayka 2020-05-04 08:53:50 PDT
Created attachment 398372 [details]
Patch
Comment 7 Alexey Shvayka 2020-05-04 09:08:01 PDT
Comment on attachment 398372 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=398372&action=review

> Source/WebCore/bindings/scripts/CodeGeneratorJS.pm:4325
> +            push(@implContent, "        JSCell::deleteProperty(this, globalObject(), propertyName);\n");

I will make sure to ASSERT delete success here and below, just making sure tests pass for now.

> JSTests/test262/expectations.yaml:1012
> +  default: 'Test262Error: Expected [a, name] and [name, a] to have the same contents. '

We are dealing with JSFunction's non-reified "name" property, hence the failure here and below (for "length").
Comment 8 Alexey Shvayka 2020-05-04 14:27:43 PDT
Created attachment 398417 [details]
Patch

Set hasReadOnlyOrGetterSetterPropertiesExcludingProto(), unset PropertyAttribute::ReadOnly for accessors, adjust tests.
Comment 9 Alexey Shvayka 2020-05-07 13:08:14 PDT
Created attachment 398787 [details]
Patch

Bring back DeletePropertyMode, always call attributeChangeTransition(), add tests and microbenchmarks.
Comment 10 Alexey Shvayka 2020-05-07 18:15:03 PDT
(In reply to Alexey Shvayka from comment #9)
> Bring back DeletePropertyMode, always call attributeChangeTransition(), add
> tests and microbenchmarks.

While this patch enables complete removal of DeletePropertyMode, I'd rather do it in a follow-up, along with implementing CanDeclareGlobalFunction for JSGlobalObject::addFunction().


Cold runs, --inner 4:

                                                r261263                    patch                                       

redefine-property-accessor-dictionary       64.1904+-0.5412     ^     39.7252+-2.3096        ^ definitely 1.6159x faster
redefine-property-accessor                  22.1282+-0.3532     ^     21.0878+-0.5099        ^ definitely 1.0493x faster
redefine-property-data-dictionary           60.6094+-0.4155     ^     32.5806+-1.2206        ^ definitely 1.8603x faster
redefine-property-data                      18.8928+-1.0337     ^     16.7405+-0.7271        ^ definitely 1.1286x faster

<geometric>                                 35.7080+-0.3323     ^     25.9930+-0.4242        ^ definitely 1.3738x faster
Comment 11 Yusuke Suzuki 2020-05-07 18:35:01 PDT
(In reply to Alexey Shvayka from comment #10)
> (In reply to Alexey Shvayka from comment #9)
> > Bring back DeletePropertyMode, always call attributeChangeTransition(), add
> > tests and microbenchmarks.
> 
> While this patch enables complete removal of DeletePropertyMode, I'd rather
> do it in a follow-up, along with implementing CanDeclareGlobalFunction for
> JSGlobalObject::addFunction().
> 
> 
> Cold runs, --inner 4:
> 
>                                                 r261263                   
> patch                                       
> 
> redefine-property-accessor-dictionary       64.1904+-0.5412     ^    
> 39.7252+-2.3096        ^ definitely 1.6159x faster
> redefine-property-accessor                  22.1282+-0.3532     ^    
> 21.0878+-0.5099        ^ definitely 1.0493x faster
> redefine-property-data-dictionary           60.6094+-0.4155     ^    
> 32.5806+-1.2206        ^ definitely 1.8603x faster
> redefine-property-data                      18.8928+-1.0337     ^    
> 16.7405+-0.7271        ^ definitely 1.1286x faster
> 
> <geometric>                                 35.7080+-0.3323     ^    
> 25.9930+-0.4242        ^ definitely 1.3738x faster

Awesome!
Comment 12 Yusuke Suzuki 2020-05-15 08:39:31 PDT
Comment on attachment 398787 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=398787&action=review

Looks nice. But I think some of Structures' flags are missing.

> Source/JavaScriptCore/runtime/Structure.cpp:676
> +    if (attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
> +        structure->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true);

Do we ensure that propertyName is not __proto__?

> Source/JavaScriptCore/runtime/Structure.cpp:677
> +

Is it OK not to set setHasGetterSetterProperties / setHasCustomGetterSetterProperties etc. Can you review all the flags of Structure?
Comment 13 Alexey Shvayka 2020-05-18 11:57:06 PDT
Created attachment 399662 [details]
Patch

Use setContainsReadOnlyProperties(), improve tests, and tweak ChangeLog.
Comment 14 Alexey Shvayka 2020-05-18 11:57:30 PDT
(In reply to Yusuke Suzuki from comment #12)
> Is it OK not to set setHasGetterSetterProperties /
> setHasCustomGetterSetterProperties etc. Can you review all the flags of
> Structure?

Thank you for review, Yusuke.

I've vetted all the flags and seems like we are safe here: all call sites of putDirectInternal<PutModeDefineOwnProperty>
perform setHasGetterSetterPropertiesWithProtoCheck(), including custom accessors. __proto__ property creation is also
handled earlier in Structure::add().

Tests are updated to cover __proto__ and indexed properties.
Comment 15 Alexey Shvayka 2020-06-17 16:42:59 PDT
Created attachment 402168 [details]
Patch

Rebase test262/expectations.yaml and simplify test added in r263070.
Comment 16 Saam Barati 2020-06-17 22:59:29 PDT
Comment on attachment 402168 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=402168&action=review

> Source/JavaScriptCore/runtime/ErrorInstance.cpp:108
> +    exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message), static_cast<unsigned>(PropertyAttribute::DontEnum));

should this be its own patch?

> Source/JavaScriptCore/runtime/JSObject.cpp:3583
> +                object->putDirect(vm, propertyName, value, descriptor.attributes() & ~PropertyAttribute::Accessor);

why are you masking out accessor? Why is that not an assert?

> Source/JavaScriptCore/runtime/JSObject.cpp:3654
> +        object->putDirect(vm, propertyName, value, attributes & ~PropertyAttribute::Accessor);

why is masking needed?

> Source/JavaScriptCore/runtime/JSObjectInlines.h:317
> +            if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))

so we always transition for defining a new accessor? Is this to maintain the invariant before that we did delete?

I think semantically we only care about doing this for customs and custom values
Comment 17 Joseph Pecoraro 2020-06-17 23:21:31 PDT
Awesome!
Comment 18 Alexey Shvayka 2020-06-18 09:51:13 PDT
(In reply to Saam Barati from comment #16)

I appreciate you taking a look, Saam.

> > Source/JavaScriptCore/runtime/ErrorInstance.cpp:108
> > +    exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message), static_cast<unsigned>(PropertyAttribute::DontEnum));
> 
> should this be its own patch?

We can land ErrorInstance.cpp change before the main patch, but we can't make it a follow-up since JSTests/stress/native-error-properties.js fails w/o it.
Because of the changes in JSObjectInlines.h, we need to explicitly set attributes to avoid Structure::attributeChangeTransition() branch.
With this patch, putDirectWithoutTransition() will fail loudly if data property already exists with different attributes.

> > Source/JavaScriptCore/runtime/JSObject.cpp:3583
> > +                object->putDirect(vm, propertyName, value, descriptor.attributes() & ~PropertyAttribute::Accessor);
> 
> why are you masking out accessor? Why is that not an assert?
> 
> > Source/JavaScriptCore/runtime/JSObject.cpp:3654
> > +        object->putDirect(vm, propertyName, value, attributes & ~PropertyAttribute::Accessor);
> 
> why is masking needed?

Masking doesn't feel right to me either; please note that I am not adding new instances, but merely preserving ones we had in putDescriptor().
Since there are similar masking outs in putIndexedDescriptor(), we can make a follow-up that moves them to PropertyDescriptor::attributesOverridingCurrent().

> > Source/JavaScriptCore/runtime/JSObjectInlines.h:317
> > +            if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))
> 
> so we always transition for defining a new accessor? Is this to maintain the
> invariant before that we did delete?

JSTests/stress/intrinsic-getter-with-poly-proto-getter-change.js fails if we don't preserve this invariant.
Please also note `current.equalTo(globalObject, descriptor)` check that avoids putDirect* route if accessor descriptor's [[Get]] and [[Set]] are the same.



(In reply to Joseph Pecoraro from comment #17)
> Awesome!

Thank you for sharing WIP patch!
Comment 19 Alexey Shvayka 2020-07-07 08:21:05 PDT
Created attachment 403690 [details]
Patch

Rebase test262 expectations.
Comment 20 Joseph Pecoraro 2020-07-07 12:00:49 PDT
Comment on attachment 403690 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=403690&action=review

> Source/JavaScriptCore/runtime/ErrorInstance.cpp:108
> -    exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message));
> +    exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message), static_cast<unsigned>(PropertyAttribute::DontEnum));

Should this be separate? At the least it should be called out in the ChangeLog.

> LayoutTests/js/script-tests/object-literal-duplicate-properties.js:-57
> -// FIXME: <https://webkit.org/b/142933> Redefining a property should not change its insertion index (Object.keys order)

Awesome! And since we are here can we drive-by update the comment on line 12 to say "... eliminated by basic properties.". This typo of mine has annoyed me for a while!
Comment 21 Alexey Shvayka 2020-07-08 16:56:40 PDT
(In reply to Joseph Pecoraro from comment #20)
> Should this be separate? At the least it should be called out in the
> ChangeLog.

Due to a reason mentioned in comment #16, we can't make it a follow-up, so let's land it first: https://webkit.org/b/214089.

> Awesome! And since we are here can we drive-by update the comment on line 12
> to say "... eliminated by basic properties.". This typo of mine has annoyed
> me for a while!

Thank you, I will fix it during next rebase.
Comment 22 Alexey Shvayka 2020-07-09 17:10:53 PDT
Created attachment 403934 [details]
Patch

Rebase patch to drop ErrorInstance.cpp change and fix typos.
Comment 23 Saam Barati 2020-07-13 11:12:34 PDT
(In reply to Alexey Shvayka from comment #18)
> (In reply to Saam Barati from comment #16)
> 
> I appreciate you taking a look, Saam.
> 
> > > Source/JavaScriptCore/runtime/ErrorInstance.cpp:108
> > > +    exception->putDirect(vm, vm.propertyNames->message, jsString(vm, message), static_cast<unsigned>(PropertyAttribute::DontEnum));
> > 
> > should this be its own patch?
> 
> We can land ErrorInstance.cpp change before the main patch, but we can't
> make it a follow-up since JSTests/stress/native-error-properties.js fails
> w/o it.
> Because of the changes in JSObjectInlines.h, we need to explicitly set
> attributes to avoid Structure::attributeChangeTransition() branch.
> With this patch, putDirectWithoutTransition() will fail loudly if data
> property already exists with different attributes.
> 
> > > Source/JavaScriptCore/runtime/JSObject.cpp:3583
> > > +                object->putDirect(vm, propertyName, value, descriptor.attributes() & ~PropertyAttribute::Accessor);
> > 
> > why are you masking out accessor? Why is that not an assert?
> > 
> > > Source/JavaScriptCore/runtime/JSObject.cpp:3654
> > > +        object->putDirect(vm, propertyName, value, attributes & ~PropertyAttribute::Accessor);
> > 
> > why is masking needed?
> 
> Masking doesn't feel right to me either; please note that I am not adding
> new instances, but merely preserving ones we had in putDescriptor().
> Since there are similar masking outs in putIndexedDescriptor(), we can make
> a follow-up that moves them to
> PropertyDescriptor::attributesOverridingCurrent().
> 
> > > Source/JavaScriptCore/runtime/JSObjectInlines.h:317
> > > +            if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))
> > 
> > so we always transition for defining a new accessor? Is this to maintain the
> > invariant before that we did delete?
> 
> JSTests/stress/intrinsic-getter-with-poly-proto-getter-change.js fails if we
> don't preserve this invariant.
> Please also note `current.equalTo(globalObject, descriptor)` check that
> avoids putDirect* route if accessor descriptor's [[Get]] and [[Set]] are the
> same.

I think we need to transition when changing custom value/custom accessor. But changing GetterSetter without transitioning should work, since all of our ICs are dynamic over this value.

> 
> 
> 
> (In reply to Joseph Pecoraro from comment #17)
> > Awesome!
> 
> Thank you for sharing WIP patch!
Comment 24 Saam Barati 2020-07-13 11:22:52 PDT
Comment on attachment 403934 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=403934&action=review

r=me with one comment/question

> Source/JavaScriptCore/ChangeLog:61
> -        beind redefined with PropertyAttribute::None.
> +        being redefined with PropertyAttribute::None.

no need to fix this now IMO. We usually don't go back and fix ChangeLog typos afaik.

> Source/JavaScriptCore/runtime/JSObjectInlines.h:317
> +            if (mode == PutModeDefineOwnProperty && (attributes != currentAttributes || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)))

same comment here. I think we should be ok with just CustomAccessorOrValue. Do tests failing just using CustomAccessorOrValue? If so, we should find out why.
Comment 25 Alexey Shvayka 2020-07-14 23:35:02 PDT
Created attachment 404321 [details]
Patch

Fix flaky microbenchmarks, add FIXMEs, undo ChangeLog typo fix.
Comment 26 Alexey Shvayka 2020-07-14 23:45:23 PDT
(In reply to Saam Barati from comment #24)
> r=me with one comment/question

Thank you for review!

> same comment here. I think we should be ok with just CustomAccessorOrValue.
> Do tests failing just using CustomAccessorOrValue? If so, we should find out
> why.

Only poly prototype __proto__ test (JSTests/stress/intrinsic-getter-with-poly-proto-getter-change.js) fails.
Changing non-intrinsic GetterSetter w/o transition works and is tested by JSTests/stress/redefine-property-{get,set}.js.
I've filed https://bugs.webkit.org/show_bug.cgi?id=214342 and added FIXMEs.
Comment 27 EWS 2020-07-18 16:10:00 PDT
Committed r264574: <https://trac.webkit.org/changeset/264574>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 404321 [details].
Comment 28 Radar WebKit Bug Importer 2020-07-18 16:10:18 PDT
<rdar://problem/65776081>
Comment 29 Yusuke Suzuki 2020-07-24 09:19:00 PDT
This change causes 10% regression in Speedometer2/Ember-Debug. Can you take a look?
Comment 30 Alexey Shvayka 2020-08-30 04:27:42 PDT
*** Bug 202139 has been marked as a duplicate of this bug. ***
Comment 31 Joseph Pecoraro 2020-11-05 09:18:52 PST
(In reply to Yusuke Suzuki from comment #29)
> This change causes 10% regression in Speedometer2/Ember-Debug. Can you take a look?

Did this ever get looked at?
Comment 32 Alexey Shvayka 2020-11-05 09:23:33 PST
(In reply to Joseph Pecoraro from comment #31)
> (In reply to Yusuke Suzuki from comment #29)
> > This change causes 10% regression in Speedometer2/Ember-Debug. Can you take a look?
> 
> Did this ever get looked at?

Yes, this regression was fixed in r265642.