Revert "[builtins] Implement Object.fromEntries"
This reverts commit a5336471f2
.
Reason for revert: Fails nosnap debug tests: https://ci.chromium.org/p/v8/builders/luci.v8.ci/V8%20Linux%20-%20nosnap%20-%20debug/21838
Original change's description:
> [builtins] Implement Object.fromEntries
>
> Adds the Object.fromEntries() method behind
> --harmony-object-from-entries.
>
>
> Includes an initial implementation of the new experimental builtin
> Object.fromEntries implemented by Daniel Clifford, and
> has been modified by Caitlin Potter to support a fast case to skip
> the iterator protocol when it can be done unobservably in common cases.
>
> There are some incidental changes: A number of CSA macros have been
> updated to use TNodes, and some Context arguments have been
> re-arranged to be implicit in Torque.
>
>
> There are also a number of mjsunit tests written mirroring and
> expanding on the test262 tests.
>
> BUG=v8:8021
>
> Change-Id: I1c12bee8a2f98c6297b77d5d723910a5e3b630cc
> Co-authored-by: Daniel Clifford <danno@chromium.org>
> Co-authored-by: Caitlin Potter <caitp@igalia.com>
> Reviewed-on: https://chromium-review.googlesource.com/c/1337585
> Commit-Queue: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#57667}
TBR=danno@chromium.org,caitp@igalia.com,tebbi@chromium.org
Change-Id: Id0cd8b16131f151a42dffbaca7e59ab17c68ab23
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:8021
Reviewed-on: https://chromium-review.googlesource.com/c/1346116
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57677}
This commit is contained in:
parent
aded5516ae
commit
6abd6f3dd4
7
BUILD.gn
7
BUILD.gn
@ -925,11 +925,9 @@ torque_files = [
|
||||
"src/builtins/array-splice.tq",
|
||||
"src/builtins/array-unshift.tq",
|
||||
"src/builtins/collections.tq",
|
||||
"src/builtins/data-view.tq",
|
||||
"src/builtins/object.tq",
|
||||
"src/builtins/object-fromentries.tq",
|
||||
"src/builtins/iterator.tq",
|
||||
"src/builtins/typed-array.tq",
|
||||
"src/builtins/data-view.tq",
|
||||
"src/builtins/iterator.tq",
|
||||
"test/torque/test-torque.tq",
|
||||
"third_party/v8/builtins/array-sort.tq",
|
||||
]
|
||||
@ -939,7 +937,6 @@ torque_namespaces = [
|
||||
"array",
|
||||
"collections",
|
||||
"iterator",
|
||||
"object",
|
||||
"typed-array",
|
||||
"data-view",
|
||||
"test",
|
||||
|
@ -4903,12 +4903,6 @@ void Genesis::InitializeGlobal_harmony_intl_segmenter() {
|
||||
|
||||
#endif // V8_INTL_SUPPORT
|
||||
|
||||
void Genesis::InitializeGlobal_harmony_object_from_entries() {
|
||||
if (!FLAG_harmony_object_from_entries) return;
|
||||
SimpleInstallFunction(isolate(), isolate()->object_function(), "fromEntries",
|
||||
Builtins::kObjectFromEntries, 1, false);
|
||||
}
|
||||
|
||||
Handle<JSFunction> Genesis::CreateArrayBuffer(
|
||||
Handle<String> name, ArrayBufferKind array_buffer_kind) {
|
||||
// Create the %ArrayBufferPrototype%
|
||||
|
@ -41,20 +41,10 @@ type JSArgumentsObjectWithLength extends JSObject
|
||||
generates 'TNode<JSArgumentsObjectWithLength>';
|
||||
type JSArray extends JSArgumentsObjectWithLength
|
||||
generates 'TNode<JSArray>';
|
||||
|
||||
// A HeapObject with a JSArray map, and either fast packed elements, or fast
|
||||
// holey elements when the global NoElementsProtector is not invalidated.
|
||||
transient type FastJSArray extends JSArray
|
||||
generates 'TNode<JSArray>';
|
||||
|
||||
// A FastJSArray when the global ArraySpeciesProtector is not invalidated.
|
||||
transient type FastJSArrayForCopy extends FastJSArray
|
||||
generates 'TNode<JSArray>';
|
||||
|
||||
// A FastJSArray when the global ArrayIteratorProtector is not invalidated.
|
||||
transient type FastJSArrayWithNoCustomIteration extends FastJSArray
|
||||
generates 'TNode<JSArray>';
|
||||
|
||||
type JSFunction extends JSObject generates 'TNode<JSFunction>';
|
||||
type JSBoundFunction extends JSObject generates 'TNode<JSBoundFunction>';
|
||||
type Callable = JSFunction | JSBoundFunction | JSProxy;
|
||||
@ -77,8 +67,6 @@ const ARRAY_JOIN_STACK_INDEX: constexpr NativeContextSlot
|
||||
generates 'Context::ARRAY_JOIN_STACK_INDEX';
|
||||
const OBJECT_FUNCTION_INDEX: constexpr NativeContextSlot
|
||||
generates 'Context::OBJECT_FUNCTION_INDEX';
|
||||
const ITERATOR_RESULT_MAP_INDEX: constexpr NativeContextSlot
|
||||
generates 'Context::ITERATOR_RESULT_MAP_INDEX';
|
||||
extern operator '[]' macro LoadContextElement(
|
||||
NativeContext, NativeContextSlot): Object;
|
||||
extern operator '[]=' macro StoreContextElement(
|
||||
@ -855,8 +843,6 @@ extern macro RawCastObjectToJSArgumentsObjectWithLength(Object):
|
||||
JSArgumentsObjectWithLength;
|
||||
extern macro RawCastObjectToFastJSArray(Object): FastJSArray;
|
||||
extern macro RawCastObjectToFastJSArrayForCopy(Object): FastJSArrayForCopy;
|
||||
extern macro RawCastObjectToFastJSArrayWithNoCustomIteration(Object):
|
||||
FastJSArrayWithNoCustomIteration;
|
||||
|
||||
macro BranchIfJSArgumentsObjectWithLength(implicit context: Context)(o: Object):
|
||||
never
|
||||
@ -925,21 +911,6 @@ Cast<FastJSArrayForCopy>(implicit context: Context)(o: Object):
|
||||
}
|
||||
}
|
||||
|
||||
UnsafeCast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(
|
||||
o: Object): FastJSArrayWithNoCustomIteration {
|
||||
assert(BranchIfFastJSArrayWithNoCustomIteration(o));
|
||||
return RawCastObjectToFastJSArrayWithNoCustomIteration(o);
|
||||
}
|
||||
|
||||
Cast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(o: Object):
|
||||
FastJSArrayWithNoCustomIteration labels CastError {
|
||||
if (BranchIfFastJSArrayWithNoCustomIteration(o)) {
|
||||
return UnsafeCast<FastJSArrayWithNoCustomIteration>(o);
|
||||
} else {
|
||||
goto CastError;
|
||||
}
|
||||
}
|
||||
|
||||
UnsafeCast<JSFunction>(implicit context: Context)(o: Object): JSFunction {
|
||||
assert(IsJSFunction(Cast<HeapObject>(o) otherwise unreachable));
|
||||
return RawCastObjectToJSFunction(o);
|
||||
@ -980,9 +951,6 @@ extern macro BranchIfFastJSArray(Object, Context): never
|
||||
labels Taken, NotTaken;
|
||||
extern macro BranchIfFastJSArrayForCopy(Object, Context): never
|
||||
labels Taken, NotTaken;
|
||||
extern macro BranchIfFastJSArrayWithNoCustomIteration(
|
||||
implicit context: Context)(Object): never labels Taken,
|
||||
NotTaken;
|
||||
extern macro BranchIfNotFastJSArray(Object, Context): never
|
||||
labels Taken, NotTaken;
|
||||
macro BranchIfNotFastJSArrayForCopy(implicit context: Context)(o: Object): never
|
||||
@ -1110,7 +1078,7 @@ extern macro CopyFixedArrayElements(
|
||||
extern macro AllocateJSArray(constexpr ElementsKind, Map, intptr, Smi): JSArray;
|
||||
extern macro AllocateJSArray(constexpr ElementsKind, Map, Smi, Smi): JSArray;
|
||||
|
||||
extern macro AllocateJSObjectFromMap(Map): JSObject;
|
||||
extern macro AllocateJSObjectFromMap(Map): HeapObject;
|
||||
|
||||
extern operator '[]=' macro StoreFixedDoubleArrayElementSmi(
|
||||
FixedDoubleArray, Smi, float64): void;
|
||||
@ -1227,22 +1195,9 @@ extern macro IsNumber(Object): bool;
|
||||
extern macro IsExtensibleMap(Map): bool;
|
||||
extern macro IsCustomElementsReceiverInstanceType(int32): bool;
|
||||
extern macro IsFastJSArray(Object, Context): bool;
|
||||
extern macro IsFastJSArrayWithNoCustomIteration(implicit context: Context)(
|
||||
Object): bool;
|
||||
extern macro Typeof(Object): Object;
|
||||
extern macro LoadTargetFromFrame(): JSFunction;
|
||||
|
||||
macro IsJSReceiver(o: Object): bool {
|
||||
typeswitch (o) {
|
||||
case (JSReceiver): {
|
||||
return true;
|
||||
}
|
||||
case (Object): {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return true iff number is NaN.
|
||||
macro NumberIsNaN(number: Number): bool {
|
||||
typeswitch (number) {
|
||||
|
@ -173,7 +173,7 @@ void BaseCollectionsAssembler::AddConstructorEntries(
|
||||
Variant variant, TNode<Context> context, TNode<Context> native_context,
|
||||
TNode<Object> collection, TNode<Object> initial_entries) {
|
||||
TVARIABLE(BoolT, use_fast_loop,
|
||||
IsFastJSArrayWithNoCustomIteration(context, initial_entries));
|
||||
IsFastJSArrayWithNoCustomIteration(initial_entries, context));
|
||||
TNode<IntPtrT> at_least_space_for =
|
||||
EstimatedInitialSize(initial_entries, use_fast_loop.value());
|
||||
Label allocate_table(this, &use_fast_loop), exit(this), fast_loop(this),
|
||||
@ -193,8 +193,8 @@ void BaseCollectionsAssembler::AddConstructorEntries(
|
||||
TNode<JSArray> initial_entries_jsarray =
|
||||
UncheckedCast<JSArray>(initial_entries);
|
||||
#if DEBUG
|
||||
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(
|
||||
context, initial_entries_jsarray));
|
||||
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(initial_entries_jsarray,
|
||||
context));
|
||||
TNode<Map> original_initial_entries_map = LoadMap(initial_entries_jsarray);
|
||||
#endif
|
||||
|
||||
@ -243,7 +243,7 @@ void BaseCollectionsAssembler::AddConstructorEntriesFromFastJSArray(
|
||||
CSA_ASSERT(
|
||||
this,
|
||||
WordEqual(GetAddFunction(variant, native_context, collection), add_func));
|
||||
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(context, fast_jsarray));
|
||||
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(fast_jsarray, context));
|
||||
TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(fast_jsarray));
|
||||
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(length, IntPtrConstant(0)));
|
||||
CSA_ASSERT(
|
||||
|
@ -256,7 +256,7 @@ TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) {
|
||||
|
||||
Label slow_path(this);
|
||||
|
||||
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable), &slow_path);
|
||||
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context), &slow_path);
|
||||
|
||||
// The fast path will copy holes to the new array.
|
||||
TailCallBuiltin(Builtins::kCloneFastJSArray, context, iterable);
|
||||
@ -270,7 +270,7 @@ void IteratorBuiltinsAssembler::FastIterableToList(
|
||||
TVariable<Object>* var_result, Label* slow) {
|
||||
Label done(this), check_string(this), check_map(this), check_set(this);
|
||||
|
||||
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable),
|
||||
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context),
|
||||
&check_string);
|
||||
|
||||
// Fast path for fast JSArray.
|
||||
|
@ -1,14 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
|
||||
#define V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
|
||||
|
||||
#include "src/code-stub-assembler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
namespace object {
|
||||
|
||||
transitioning macro ObjectFromEntriesFastCase(implicit context: Context)(
|
||||
iterable: Object): JSObject labels IfSlow {
|
||||
typeswitch (iterable) {
|
||||
case (array: FastJSArrayWithNoCustomIteration): {
|
||||
const elements: FixedArray =
|
||||
Cast<FixedArray>(array.elements) otherwise IfSlow;
|
||||
const length: Smi = array.length;
|
||||
const result: JSObject = AllocateEmptyJSObject();
|
||||
|
||||
for (let k: Smi = 0; k < length; ++k) {
|
||||
const value: Object = array::LoadElementOrUndefined(elements, k);
|
||||
const pair: KeyValuePair =
|
||||
collections::LoadKeyValuePairNoSideEffects(value)
|
||||
otherwise IfSlow;
|
||||
// Bail out if ToPropertyKey will attempt to load and call
|
||||
// Symbol.toPrimitive, toString, and valueOf, which could
|
||||
// invalidate assumptions about the iterable.
|
||||
if (IsJSReceiver(pair.key)) goto IfSlow;
|
||||
CreateDataProperty(result, pair.key, pair.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case (Object): {
|
||||
goto IfSlow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transitioning javascript builtin
|
||||
ObjectFromEntries(implicit context: Context)(receiver: Object): Object {
|
||||
const iterable: Object = receiver;
|
||||
try {
|
||||
if (IsNullOrUndefined(iterable)) goto Throw;
|
||||
return ObjectFromEntriesFastCase(iterable) otherwise IfSlow;
|
||||
}
|
||||
label IfSlow {
|
||||
const result: JSObject = AllocateEmptyJSObject();
|
||||
const fastIteratorResultMap: Map =
|
||||
Cast<Map>(LoadNativeContext(context)[ITERATOR_RESULT_MAP_INDEX])
|
||||
otherwise unreachable;
|
||||
let i: iterator::IteratorRecord = iterator::GetIterator(iterable);
|
||||
try {
|
||||
assert(!IsNullOrUndefined(i.object));
|
||||
while (true) {
|
||||
const step: Object = iterator::IteratorStep(i, fastIteratorResultMap)
|
||||
otherwise return result;
|
||||
const iteratorValue: Object =
|
||||
iterator::IteratorValue(step, fastIteratorResultMap);
|
||||
const pair: KeyValuePair =
|
||||
collections::LoadKeyValuePair(iteratorValue);
|
||||
CreateDataProperty(result, pair.key, pair.value);
|
||||
}
|
||||
return result;
|
||||
} catch (e) deferred {
|
||||
iterator::IteratorCloseOnException(i, e);
|
||||
}
|
||||
}
|
||||
label Throw deferred {
|
||||
ThrowTypeError(context, kNotIterable);
|
||||
}
|
||||
}
|
||||
} // namespace object
|
@ -1,12 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
namespace object {
|
||||
macro AllocateEmptyJSObject(implicit context: Context)(): JSObject {
|
||||
const objectFunction: JSFunction = GetObjectFunction();
|
||||
const map: Map = Cast<Map>(objectFunction.prototype_or_initial_map)
|
||||
otherwise unreachable;
|
||||
return AllocateJSObjectFromMap(map);
|
||||
}
|
||||
}
|
@ -1056,29 +1056,22 @@ TNode<BoolT> CodeStubAssembler::IsFastJSArray(SloppyTNode<Object> object,
|
||||
return var_result.value();
|
||||
}
|
||||
|
||||
void CodeStubAssembler::BranchIfFastJSArrayWithNoCustomIteration(
|
||||
TNode<Context> context, TNode<Object> object, Label* if_true,
|
||||
Label* if_false) {
|
||||
Label if_fast(this);
|
||||
BranchIfFastJSArray(object, context, &if_fast, if_false, true);
|
||||
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
|
||||
TNode<Object> object, TNode<Context> context) {
|
||||
Label if_false(this, Label::kDeferred), if_fast(this), exit(this);
|
||||
TVARIABLE(BoolT, var_result);
|
||||
BranchIfFastJSArray(object, context, &if_fast, &if_false, true);
|
||||
BIND(&if_fast);
|
||||
{
|
||||
// Check that the Array.prototype hasn't been modified in a way that would
|
||||
// affect iteration.
|
||||
Node* protector_cell = LoadRoot(RootIndex::kArrayIteratorProtector);
|
||||
DCHECK(isolate()->heap()->array_iterator_protector()->IsPropertyCell());
|
||||
Branch(
|
||||
var_result =
|
||||
WordEqual(LoadObjectField(protector_cell, PropertyCell::kValueOffset),
|
||||
SmiConstant(Isolate::kProtectorValid)),
|
||||
if_true, if_false);
|
||||
SmiConstant(Isolate::kProtectorValid));
|
||||
Goto(&exit);
|
||||
}
|
||||
}
|
||||
|
||||
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
|
||||
TNode<Context> context, TNode<Object> object) {
|
||||
Label if_false(this, Label::kDeferred), exit(this);
|
||||
TVARIABLE(BoolT, var_result, Int32TrueConstant());
|
||||
BranchIfFastJSArrayWithNoCustomIteration(context, object, &exit, &if_false);
|
||||
BIND(&if_false);
|
||||
{
|
||||
var_result = Int32FalseConstant();
|
||||
|
@ -416,11 +416,6 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
return TNode<JSArray>::UncheckedCast(p_o);
|
||||
}
|
||||
|
||||
TNode<JSArray> RawCastObjectToFastJSArrayWithNoCustomIteration(
|
||||
TNode<Object> p_o) {
|
||||
return TNode<JSArray>::UncheckedCast(p_o);
|
||||
}
|
||||
|
||||
TNode<JSFunction> RawCastObjectToJSFunction(TNode<Object> p_o) {
|
||||
return TNode<JSFunction>::UncheckedCast(p_o);
|
||||
}
|
||||
@ -812,10 +807,6 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
}
|
||||
void BranchIfFastJSArrayForCopy(Node* object, Node* context, Label* if_true,
|
||||
Label* if_false);
|
||||
void BranchIfFastJSArrayWithNoCustomIteration(TNode<Context> context,
|
||||
TNode<Object> object,
|
||||
Label* if_true,
|
||||
Label* if_false);
|
||||
|
||||
// Branches to {if_true} when --force-slow-path flag has been passed.
|
||||
// It's used for testing to ensure that slow path implementation behave
|
||||
@ -2031,8 +2022,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
TNode<BoolT> IsExternalStringInstanceType(SloppyTNode<Int32T> instance_type);
|
||||
TNode<BoolT> IsFastJSArray(SloppyTNode<Object> object,
|
||||
SloppyTNode<Context> context);
|
||||
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Context> context,
|
||||
TNode<Object> object);
|
||||
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Object> object,
|
||||
TNode<Context> context);
|
||||
TNode<BoolT> IsFeedbackCell(SloppyTNode<HeapObject> object);
|
||||
TNode<BoolT> IsFeedbackVector(SloppyTNode<HeapObject> object);
|
||||
TNode<BoolT> IsContext(SloppyTNode<HeapObject> object);
|
||||
|
@ -199,8 +199,7 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
|
||||
V(harmony_await_optimization, "harmony await taking 1 tick") \
|
||||
V(harmony_private_methods, "harmony private methods in class literals") \
|
||||
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
|
||||
V(harmony_weak_refs, "harmony weak references") \
|
||||
V(harmony_object_from_entries, "harmony Object.fromEntries()")
|
||||
V(harmony_weak_refs, "harmony weak references")
|
||||
|
||||
#ifdef V8_INTL_SUPPORT
|
||||
#define HARMONY_INPROGRESS(V) \
|
||||
|
@ -1,439 +0,0 @@
|
||||
// Copyright 2018 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flags: --harmony-object-from-entries
|
||||
|
||||
const fromEntries = Object.fromEntries;
|
||||
const ObjectPrototype = Object.prototype;
|
||||
const ObjectPrototypeHasOwnProperty = ObjectPrototype.hasOwnProperty;
|
||||
function hasOwnProperty(O, Name) {
|
||||
if (O === undefined || O === null) return false;
|
||||
return ObjectPrototypeHasOwnProperty.call(O, Name);
|
||||
}
|
||||
|
||||
let test = {
|
||||
methodExists() {
|
||||
assertTrue(hasOwnProperty(Object, "fromEntries"));
|
||||
assertEquals("function", typeof Object.fromEntries);
|
||||
},
|
||||
|
||||
methodLength() {
|
||||
assertEquals(1, Object.fromEntries.length);
|
||||
},
|
||||
|
||||
methodName() {
|
||||
assertEquals("fromEntries", Object.fromEntries.name);
|
||||
},
|
||||
|
||||
methodPropertyDescriptor() {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(Object, "fromEntries");
|
||||
assertFalse(descriptor.enumerable);
|
||||
assertTrue(descriptor.configurable);
|
||||
assertTrue(descriptor.writable);
|
||||
assertEquals(descriptor.value, Object.fromEntries);
|
||||
},
|
||||
|
||||
exceptionIfNotCoercible() {
|
||||
assertThrows(() => fromEntries(null), TypeError);
|
||||
assertThrows(() => fromEntries(undefined), TypeError);
|
||||
},
|
||||
|
||||
exceptionIfNotIterable() {
|
||||
let nonIterable = [1, 2, 3, 4, 5];
|
||||
Object.defineProperty(nonIterable, Symbol.iterator, { value: undefined });
|
||||
assertThrows(() => fromEntries(nonIterable), TypeError);
|
||||
},
|
||||
|
||||
exceptionIfGetIteratorThrows() {
|
||||
let iterable = [1, 2, 3, 4, 5];
|
||||
class ThrewDuringGet {};
|
||||
Object.defineProperty(iterable, Symbol.iterator, {
|
||||
get() { throw new ThrewDuringGet(); }
|
||||
});
|
||||
assertThrows(() => fromEntries(iterable), ThrewDuringGet);
|
||||
},
|
||||
|
||||
exceptionIfCallIteratorThrows() {
|
||||
let iterable = [1, 2, 3, 4, 5];
|
||||
class ThrewDuringCall {};
|
||||
iterable[Symbol.iterator] = function() {
|
||||
throw new ThrewDuringCall();
|
||||
}
|
||||
assertThrows(() => fromEntries(iterable), ThrewDuringCall);
|
||||
},
|
||||
|
||||
exceptionIfIteratorNextThrows() {
|
||||
let iterable = [1, 2, 3, 4, 5];
|
||||
class ThrewDuringIteratorNext {}
|
||||
iterable[Symbol.iterator] = function() {
|
||||
return {
|
||||
next() { throw new ThrewDuringIteratorNext; },
|
||||
return() {
|
||||
throw new Error(
|
||||
"IteratorClose must not be performed if IteratorStep throws");
|
||||
},
|
||||
}
|
||||
}
|
||||
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorNext);
|
||||
},
|
||||
|
||||
exceptionIfIteratorCompleteThrows() {
|
||||
let iterable = [1, 2, 3, 4, 5];
|
||||
class ThrewDuringIteratorComplete {}
|
||||
iterable[Symbol.iterator] = function() {
|
||||
return {
|
||||
next() {
|
||||
return {
|
||||
get value() { throw new Error(
|
||||
"IteratorValue must not be performed before IteratorComplete");
|
||||
},
|
||||
get done() {
|
||||
throw new ThrewDuringIteratorComplete();
|
||||
}
|
||||
}
|
||||
throw new ThrewDuringIteratorNext;
|
||||
},
|
||||
return() {
|
||||
throw new Error(
|
||||
"IteratorClose must not be performed if IteratorStep throws");
|
||||
},
|
||||
}
|
||||
}
|
||||
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorComplete);
|
||||
},
|
||||
|
||||
exceptionIfEntryIsNotObject() {
|
||||
{
|
||||
// Fast path (Objects/Smis)
|
||||
let iterables = [[null], [undefined], [1], [NaN], [false], [Symbol()],
|
||||
[""]];
|
||||
for (let iterable of iterables) {
|
||||
assertThrows(() => fromEntries(iterable), TypeError);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Fast path (Doubles)
|
||||
let iterable = [3.7, , , 3.6, 1.1, -0.4];
|
||||
assertThrows(() => fromEntries(iterable), TypeError);
|
||||
}
|
||||
{
|
||||
// Slow path
|
||||
let i = 0;
|
||||
let values = [null, undefined, 1, NaN, false, Symbol(), ""];
|
||||
let iterable = {
|
||||
[Symbol.iterator]() { return this; },
|
||||
next() {
|
||||
return {
|
||||
done: i >= values.length,
|
||||
value: values[i++],
|
||||
}
|
||||
},
|
||||
};
|
||||
for (let k = 0; k < values.length; ++k) {
|
||||
assertThrows(() => fromEntries(iterable), TypeError);
|
||||
}
|
||||
assertEquals({}, fromEntries(iterable));
|
||||
}
|
||||
},
|
||||
|
||||
returnIfEntryIsNotObject() {
|
||||
// Only observable/verifiable in the slow path :(
|
||||
let i = 0;
|
||||
let didCallReturn = false;
|
||||
let values = [null, undefined, 1, NaN, false, Symbol(), ""];
|
||||
let iterable = {
|
||||
[Symbol.iterator]() { return this; },
|
||||
next() {
|
||||
return {
|
||||
done: i >= values.length,
|
||||
value: values[i++],
|
||||
}
|
||||
},
|
||||
return() { didCallReturn = true; throw new Error("Unused!"); }
|
||||
};
|
||||
for (let k = 0; k < values.length; ++k) {
|
||||
didCallReturn = false;
|
||||
assertThrows(() => fromEntries(iterable), TypeError);
|
||||
assertTrue(didCallReturn);
|
||||
}
|
||||
assertEquals({}, fromEntries(iterable));
|
||||
},
|
||||
|
||||
returnIfEntryKeyAccessorThrows() {
|
||||
class ThrewDuringKeyAccessor {};
|
||||
let entries = [{ get 0() { throw new ThrewDuringKeyAccessor(); },
|
||||
get 1() { throw new Error("Unreachable!"); } }];
|
||||
let didCallReturn = false;
|
||||
let iterator = entries[Symbol.iterator]();
|
||||
iterator.return = function() {
|
||||
didCallReturn = true;
|
||||
throw new Error("Unused!");
|
||||
}
|
||||
assertThrows(() => fromEntries(iterator), ThrewDuringKeyAccessor);
|
||||
assertTrue(didCallReturn);
|
||||
},
|
||||
|
||||
returnIfEntryKeyAccessorThrows() {
|
||||
class ThrewDuringValueAccessor {};
|
||||
let entries = [{ get 1() { throw new ThrewDuringValueAccessor(); },
|
||||
0: "key",
|
||||
}];
|
||||
let didCallReturn = false;
|
||||
let iterator = entries[Symbol.iterator]();
|
||||
iterator.return = function() {
|
||||
didCallReturn = true;
|
||||
throw new Error("Unused!");
|
||||
};
|
||||
assertThrows(() => fromEntries(iterator), ThrewDuringValueAccessor);
|
||||
assertTrue(didCallReturn);
|
||||
},
|
||||
|
||||
returnIfKeyToStringThrows() {
|
||||
class ThrewDuringKeyToString {};
|
||||
let operations = [];
|
||||
let entries = [{
|
||||
get 0() {
|
||||
operations.push("[[Get]] key");
|
||||
return {
|
||||
toString() {
|
||||
operations.push("toString(key)");
|
||||
throw new ThrewDuringKeyToString();
|
||||
},
|
||||
valueOf() {
|
||||
operations.push("valueOf(key)");
|
||||
}
|
||||
};
|
||||
},
|
||||
get 1() {
|
||||
operations.push("[[Get]] value");
|
||||
return "value";
|
||||
},
|
||||
}];
|
||||
|
||||
let iterator = entries[Symbol.iterator]();
|
||||
iterator.return = function() {
|
||||
operations.push("IteratorClose");
|
||||
throw new Error("Unused!");
|
||||
};
|
||||
assertThrows(() => fromEntries(iterator), ThrewDuringKeyToString);
|
||||
assertEquals([
|
||||
"[[Get]] key",
|
||||
"[[Get]] value",
|
||||
"toString(key)",
|
||||
"IteratorClose",
|
||||
], operations);
|
||||
},
|
||||
|
||||
throwsIfIteratorValueThrows() {
|
||||
let iterable = [1, 2, 3, 4, 5];
|
||||
class ThrewDuringIteratorValue {}
|
||||
iterable[Symbol.iterator] = function() {
|
||||
return {
|
||||
next() {
|
||||
return {
|
||||
get value() { throw new ThrewDuringIteratorValue(); },
|
||||
get done() { return false; }
|
||||
}
|
||||
throw new ThrewDuringIteratorNext;
|
||||
},
|
||||
return() {
|
||||
throw new Error(
|
||||
"IteratorClose must not be performed if IteratorStep throws");
|
||||
},
|
||||
}
|
||||
}
|
||||
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorValue);
|
||||
},
|
||||
|
||||
emptyIterable() {
|
||||
let iterables = [[], new Set(), new Map()];
|
||||
for (let iterable of iterables) {
|
||||
let result = fromEntries(iterable);
|
||||
assertEquals({}, result);
|
||||
assertEquals(ObjectPrototype, result.__proto__);
|
||||
}
|
||||
},
|
||||
|
||||
keyOrderFastPath() {
|
||||
let entries = [
|
||||
["z", 1],
|
||||
["y", 2],
|
||||
["x", 3],
|
||||
["y", 4],
|
||||
[100, 0],
|
||||
];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
100: 0,
|
||||
z: 1,
|
||||
y: 4,
|
||||
x: 3,
|
||||
}, result);
|
||||
assertEquals(["100", "z", "y", "x"], Object.keys(result));
|
||||
},
|
||||
|
||||
keyOrderSlowPath() {
|
||||
let entries = [
|
||||
["z", 1],
|
||||
["y", 2],
|
||||
["x", 3],
|
||||
["y", 4],
|
||||
[100, 0],
|
||||
];
|
||||
let i = 0;
|
||||
let iterable = {
|
||||
[Symbol.iterator]() { return this; },
|
||||
next() {
|
||||
return {
|
||||
done: i >= entries.length,
|
||||
value: entries[i++]
|
||||
}
|
||||
},
|
||||
return() { throw new Error("Unreachable!"); }
|
||||
};
|
||||
let result = fromEntries(iterable);
|
||||
assertEquals({
|
||||
100: 0,
|
||||
z: 1,
|
||||
y: 4,
|
||||
x: 3,
|
||||
}, result);
|
||||
assertEquals(["100", "z", "y", "x"], Object.keys(result));
|
||||
},
|
||||
|
||||
doesNotUseIteratorForKeyValuePairFastCase() {
|
||||
class Entry {
|
||||
constructor(k, v) {
|
||||
this[0] = k;
|
||||
this[1] = v;
|
||||
}
|
||||
get [Symbol.iterator]() {
|
||||
throw new Error("Should not load Symbol.iterator from Entry!");
|
||||
}
|
||||
}
|
||||
function e(k, v) { return new Entry(k, v); }
|
||||
let entries = [e(100, 0), e('z', 1), e('y', 2), e('x', 3), e('y', 4)];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
100: 0,
|
||||
z: 1,
|
||||
y: 4,
|
||||
x: 3,
|
||||
}, result);
|
||||
},
|
||||
|
||||
doesNotUseIteratorForKeyValuePairSlowCase() {
|
||||
class Entry {
|
||||
constructor(k, v) {
|
||||
this[0] = k;
|
||||
this[1] = v;
|
||||
}
|
||||
get [Symbol.iterator]() {
|
||||
throw new Error("Should not load Symbol.iterator from Entry!");
|
||||
}
|
||||
}
|
||||
function e(k, v) { return new Entry(k, v); }
|
||||
let entries = new Set(
|
||||
[e(100, 0), e('z', 1), e('y', 2), e('x', 3), e('y', 4)]);
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
100: 0,
|
||||
z: 1,
|
||||
y: 4,
|
||||
x: 3,
|
||||
}, result);
|
||||
},
|
||||
|
||||
createDataPropertyFastCase() {
|
||||
Object.defineProperty(ObjectPrototype, "property", {
|
||||
configurable: true,
|
||||
get() { throw new Error("Should not invoke getter on prototype!"); },
|
||||
set() { throw new Error("Should not invoke setter on prototype!"); },
|
||||
});
|
||||
|
||||
let entries = [["property", "value"]];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals(result.property, "value");
|
||||
delete ObjectPrototype.property;
|
||||
},
|
||||
|
||||
createDataPropertySlowCase() {
|
||||
Object.defineProperty(ObjectPrototype, "property", {
|
||||
configurable: true,
|
||||
get() { throw new Error("Should not invoke getter on prototype!"); },
|
||||
set() { throw new Error("Should not invoke setter on prototype!"); },
|
||||
});
|
||||
|
||||
let entries = new Set([["property", "value"]]);
|
||||
let result = fromEntries(entries);
|
||||
assertEquals(result.property, "value");
|
||||
delete ObjectPrototype.property;
|
||||
},
|
||||
|
||||
keyToPrimitiveMutatesArrayInFastCase() {
|
||||
let mySymbol = Symbol();
|
||||
let entries = [[0, 1], ["a", 2], [{
|
||||
[Symbol.toPrimitive]() {
|
||||
// The fast path should bail out if a key is a JSReceiver, otherwise
|
||||
// assumptions about the structure of the iterable can change. If the
|
||||
// fast path doesn't bail out, the 4th key would be "undefined".
|
||||
delete entries[3][0];
|
||||
entries[3].__proto__ = { 0: "shfifty", };
|
||||
return mySymbol;
|
||||
},
|
||||
}, 3], [3, 4]];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
0: 1,
|
||||
"a": 2,
|
||||
[mySymbol]: 3,
|
||||
"shfifty": 4,
|
||||
}, result);
|
||||
assertEquals(["0", "a", "shfifty", mySymbol], Reflect.ownKeys(result));
|
||||
},
|
||||
|
||||
keyToStringMutatesArrayInFastCase() {
|
||||
let mySymbol = Symbol();
|
||||
let entries = [[mySymbol, 1], [0, 2], [{
|
||||
toString() {
|
||||
delete entries[3][0];
|
||||
entries[3].__proto__ = { 0: "shfifty", };
|
||||
return "z";
|
||||
},
|
||||
valueOf() { throw new Error("Unused!"); }
|
||||
}, 3], [3, 4]];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
[mySymbol]: 1,
|
||||
0: 2,
|
||||
"z": 3,
|
||||
"shfifty": 4,
|
||||
}, result);
|
||||
assertEquals(["0", "z", "shfifty", mySymbol], Reflect.ownKeys(result));
|
||||
},
|
||||
|
||||
keyValueOfMutatesArrayInFastCase() {
|
||||
let mySymbol = Symbol();
|
||||
let entries = [[mySymbol, 1], ["z", 2], [{
|
||||
toString: undefined,
|
||||
valueOf() {
|
||||
delete entries[3][0];
|
||||
entries[3].__proto__ = { 0: "shfifty", };
|
||||
return 0;
|
||||
},
|
||||
}, 3], [3, 4]];
|
||||
let result = fromEntries(entries);
|
||||
assertEquals({
|
||||
[mySymbol]: 1,
|
||||
"z": 2,
|
||||
0: 3,
|
||||
"shfifty": 4,
|
||||
}, result);
|
||||
assertEquals(["0", "z", "shfifty", mySymbol], Reflect.ownKeys(result));
|
||||
},
|
||||
}
|
||||
|
||||
for (let t of Reflect.ownKeys(test)) {
|
||||
test[t]();
|
||||
}
|
@ -57,10 +57,10 @@ FEATURE_FLAGS = {
|
||||
'globalThis': '--harmony-global',
|
||||
'well-formed-json-stringify': '--harmony-json-stringify',
|
||||
'export-star-as-namespace-from-module': '--harmony-namespace-exports',
|
||||
'Object.fromEntries': '--harmony-object-from-entries',
|
||||
}
|
||||
|
||||
SKIPPED_FEATURES = set(['class-fields-private',
|
||||
SKIPPED_FEATURES = set(['Object.fromEntries',
|
||||
'class-fields-private',
|
||||
'class-static-fields-private',
|
||||
'class-methods-private',
|
||||
'class-static-methods-private'])
|
||||
|
Loading…
Reference in New Issue
Block a user