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