// 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. 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](); }