New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Object.fromEntries #5622
Conversation
lib/Runtime/Base/JnDirectFields.h
Outdated
@@ -140,8 +140,6 @@ ENTRY(every) | |||
ENTRY(exec) | |||
ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "false_" instead | |||
ENTRY(flags) | |||
ENTRY(flat) | |||
ENTRY(flatMap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated cleanup - these Entries were not needed (added when I implemented .flat and .flatMap but not actually needed for JsBuiltin methods)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what was causing issues in the legacy win7 and win8 builds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a windows7/8 setup to test on but this was my best guess - the assertion was saying a property name was invalid so I guessed that the mechanism it was using relied on this ENTRY macros.
let key; | ||
for (const entry of iterable) { | ||
if (typeof entry !== "object") { | ||
__chakraLibrary.raiseNeedObject("Object.prototype.fromEntries"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a great error message but not sure what I should use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this isn't required; if I understand the spec correctly, then this part of the implementation is an inlined version of CreateDataPropertyOnObject
, and the this
of that function is the o
here which is guaranteed to be an object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I think I'm mistaken; this is corresponding to the internals of AddEntriesFromIterable
, which does require that the iterable returns objects. Nevermind then!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Object.prototype.fromEntries/Object.fromEntries/
// #sec-object.fromentries | ||
"use strict"; | ||
if (this === null || this === undefined) { | ||
__chakraLibrary.raiseNeedObject("Object.prototype.fromEntries"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this required in the spec? I don't see language in the spec referencing the this
of the function
Apart from the failing tests which look like they just need baseline updates to account for this new method, this change looks good to me! |
Actually some of the tests are failing with crashes, an assertion to do with property IDs not being nullptr. EDIT: Initially I thought that it was to do with builtins not being present, but that's not the case. The lite build is running fine, but the win7 legacy build is failing with a crash. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @rhuanjl! I'll run this past our internal CI and see if anything falls out.
Looks like no significant issues on our internal CI. I need to update a few more baselines, but that's not a big problem. Could you please squash your fixup commit in, and once I've fixed the internal baselines I'll get this merged in. Thanks again! |
864729d
to
844c679
Compare
Thanks for testing this @MSLaguana I've squashed the fixes commit. |
@@ -352,7 +375,7 @@ namespace Js | |||
func->GetFunctionProxy()->SetIsPublicLibraryCode(); | |||
func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodName->GetString(), methodName->GetLength(), 0); | |||
|
|||
DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), scriptContext); | |||
DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), staticMethod, scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small nit: we have been trying to prefer bool
to BOOL
, and we turned off the warning about type mismatch for that conversion so it should always be implicit.
return library->stringConstructor; | ||
|
||
default: | ||
AssertMsg(false, "Unable to find a constructor that match with this className."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would AssertOrFailFast for any cost we never expect to run, since the extra if check should never actually affect runtime performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was copying code below that just did an Assert for this - relevant point though - should only come up when developing a new builtin and a silent fail isn't great.
@@ -344,6 +366,7 @@ namespace Js | |||
JavascriptString* methodName = JavascriptString::FromVar(methodNameProperty); | |||
int argumentsCount = TaggedInt::ToInt32(argumentsCountProperty); | |||
|
|||
BOOL staticMethod = JavascriptConversion::ToBoolean(staticMethodProperty, scriptContext); | |||
BOOL forceInline = JavascriptConversion::ToBoolean(forceInlineProperty, scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May want to add Assert(Is())
for staticMethod and forceInline above (forceInline isn't your change but the remaining properties all have asserts). aliasProperty also doesn't have an assert, but I can't see where its used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intended it as an optional property - if it's undefined ToBoolean will give false which is intended so an Assert here would be wrong. aliasProperty is used for the array iterator only (hence also optional) so again should not be asserted.
assert.doesNotThrow(()=>{Object.fromEntries(String());}, "Object.fromEntries does not throw when called with String Object parameter with length 0"); | ||
assert.throws(()=>{Object.fromEntries(String("anything"));}, TypeError, "Object.fromEntries throws when called with String Object parameter with length > 0"); | ||
assert.throws(()=>{Object.fromEntries({});}, TypeError, "Object.fromEntries throws when called with Object literal"); | ||
assert.throws(()=>{Object.fromEntries({a : "5", b : "10"});}, TypeError, "Object.fromEntries throws when called with Object literal"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proxy/array-like object/new Array()/getter cases would be nice.
}; | ||
|
||
let key; | ||
for (const entry of iterable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be tainted by overwriting Array.prototype[Symbol.iterator]
, I believe. Thats why I never used it in Intl, anyways. We should probably check, or use a regular for loop to be safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the proposed spec this method is specifically meant to use the iterator method of the provided iterable - i.e. if using an array and Array.prototype[Symbol.iterator] is overwritten it's meant to behave differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, weird. All of the Intl things that iterate through arrays call out for (var x in obj) { if (HasOwnProperty(obj, x) { … } }
or something similar. May want to add a test for tainting the iterator to make sure it is tainted, then, rather than that it isn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because the method is designed to be usable with other iterables, not just arrays e.g. generator functions etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, see DETAILS.md in the proposal.
This is to match the very similar behavior of the Map constructor (which is in fact defined using the same abstract operation).
May want to add a test for tainting the iterator to make sure it is tainted, then, rather than that it isn't.
There's one in test262! Not sure if you're using that or would consider it sufficient, just thought I'd mention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bakkot I ran this against test262 before submitting the PR but the Chakracore CI doesn't run test262 so I've added alternate tests for this - check the diff of this PR for tests.
@jackhorton I think I've addressed your points. (BOOL -> bool, the unreachable Assert -> AssertOrFailfast, extra test cases) Let me know if good to squash. |
9d533fc
to
4f03f21
Compare
#ifdef ENABLE_JS_BUILTINS | ||
if (scriptContext->IsJsBuiltInEnabled()) | ||
{ | ||
library->EnsureBuiltInEngineIsReady(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out this bit here is causing some problems in our internal tests that were initially obscured by the baseline changes.
@jackhorton how do we deal with the Intl object? We do lazy initialization there right? Perhaps we need to do the same here?
The specific issue is that when calling into script from here, there is a check that we can handle stack overflow, and that is failing in some cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, this is the lazy initialization of the Object
object, so this is already a similar pattern to what we do for intl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simply copied what was done for the array prototype - is there some difference between doing it for a prototype vs a constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that the difference is in the calling code, not here. I believe I'll need to add some additional error handling elsewhere since that code path also doesn't seem to handle calling into a JS builtin at all, despite sounding like it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick look shows that the EnsureBuiltInEngineIsReady doesn't have try/catch whereas EnsureIntlObjectReady does - what's confusing to me is that the EnsureBuiltInEngineIsReady method was previously used for ArrayPrototype without any problems.
Had to update the bytecode GUID since there were changes (essentially to invalidate any cached bytecode that may exist) but otherwise looks good. Once this CI pass completes I'll check it in. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
// #sec-object.fromentries | ||
"use strict"; | ||
if (iterable === null || iterable === undefined) { | ||
__chakraLibrary.raiseNeedObject("Object.prototype.fromEntries"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Object.prototype.fromEntries/Object.fromEntries/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/Object/fromEntries.js
Outdated
|
||
function verifyObject(expected, actual) | ||
{ | ||
for (let i in expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this include a has own property check on expected, too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Erm - yeah this was a mistake it was meant to do hasProperty checks on expected not on actual (as the verifyProperty bit checks the actuals; it's exactly backwards, will fix.
@rhuanjl would you mind squashing the error message fixup (and if you feel like it, the GUID update) into your original commit, and also regenerating the bytecode to account for that change again? |
2086a7e
to
c4a03d8
Compare
@MSLaguana I've rebased to master, fixed the merge conflicts (vs the recent addition of Array.prototype.forEach as a JsBuiltin) regenerated the bytecode and squashed. As there was a merge conflict does it need another new GUID? Per the comment in ByteCodeCacheReleaseFileVersion.h ? |
Was the merge conflict in the |
No - there was a merge conflict vs the generated bytecode files and JsBuiltins.js but that PR did not update the GUID so no conflict on ByteCodeCacheReleaseFileVersion.h |
In general, as long as a change to bytecode (such as this) gets merged with any new GUID, its fine. So if you had a GUID change and then rebased ontop of another GUID change, there would be a conflict, but you could either take your GUID change or generate another new one, since either are different from your parent. If that makes sense. This whole bytecode situation sucks. |
@dotnet-bot test Ubuntu shared_ubuntu_linux_debug please |
|
||
let key; | ||
for (const entry of iterable) { | ||
if (typeof entry !== "object") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. You may want to add || entry === null
to this check, just so you get a better error message. It's the right semantics either way - if entry is null, it will throw a TypeError at the key = entry[0]
line - but it may be surprising that the messages differ when entry is null vs any other primitive, especially since those are forbidden by the same part of the spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently the error message for failing on the key = entry[0] line is better than the error message for failing this check, so would rather not update unless devising a new specific error message.
Note CC will report that the error occurs in the line of user code containing the fromEntries call - the code in JsBuiltIn.js is hidden from the end user of CC.
The raiseNeedObject error simply gives "Object expected" as the error message whereas key = entry[0] line will give the slightly more helpful "Unable to get property '0' of undefined or null reference".
If combining them into one error it should probably be something specific like "Iterable provided to Object.fromEntries must not return non-object or null value"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what its worth, it is possible to add a new error :) In general our error messages havent been something we have worried about too much, but if it bothers you, feel free to hook a new error up. Plenty of specific cases have their own unique error text.
Stumped by this test failure (same test has failed twice now) - it's an ASM.js test that's meant to test for an out of memory error - it obviously does not use Object.fromEntries, so unsure how it's being affected, also it didn't error with earlier versions of this PR. |
@MikeHolman any ideas about the AsmJS failure? |
Weird, looks like the output is correct but exit code is bad. Any idea what is exit code -11? I think the only place your code interacts would be with the new |
@MikeHolman I did a bit of digging - this error is debug only at least... signal 11 = segmentation fault. The segfault occurs in the loop here: I'm afraid I know next to nothing about how CC's recycler works so I don't think I'll be able to fix this. |
Seems like an existing bug anyway, I guess we are writing to the card table for a non-GC object. IMO it's fine to disable the test and open an issue. |
c4a03d8
to
f983cd5
Compare
I've added some extra updates - see separate commit:
Hopefully this is good for merge though - let me know if good to squash (kept the new changes in a separate commit so easier to review) |
Bumping bytecode GUID again since some changes needed updates to private tests again. Feel free to squash it together when things are ready. I believe that @jackhorton is working on fixing some internal test baselines which were broken by the |
I've assumed I should hold on updating this until #5651 is merged to avoid extra merge conflicts - as far as I'm concerned this is done apart from resolving the merge conflict; please let me know if not. |
@rhuanjl I believe @jackhorton was saying he would yield to your PR since he can update and merge his own PR, and thus has less latency than your PR does. |
cea2faf
to
c279dfd
Compare
Looks like you have already regen'd bytecode, so I will try to merge yours first today and then get mine in after. |
@dotnet-bot test all please |
@dotnet-bot test this please |
Looks like the webhooks to talk with Jenkins is timing out. |
@dotnet-bot test this please |
@MSLaguana There's an issue open for this. I'm investigating https://github.com/dotnet/core-eng/issues/4149 |
@rhuanjl update on this, our internal tests are down until https://azure.microsoft.com/en-us/status/ is happy again :/ it tests clean locally, but we have already missed a bunch of builds due to this outage, and I don't want to compound any issues by merging this before things are green. |
Merge pull request #5622 from rhuanjl:ObjectfromEntries This PR implements the stage 3 proposal Object.fromEntries. Proposal spec text is here: https://tc39.github.io/proposal-object-from-entries/ Notes: 1. Implementation done as a JsBuiltin - note this requires initialising the JsBuiltins for accessing the Object constructor - could this have negative effects? 2. This implementation passes the test262 tests for this feature. fixes: #5590
This PR implements the stage 3 proposal Object.fromEntries.
Proposal spec text is here: https://tc39.github.io/proposal-object-from-entries/
Notes:
fixes: #5590