From c6ad284d994dc178d29e2e5cb40fb98ea40b0045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Mon, 26 May 2025 15:49:05 +0200 Subject: [PATCH] Add coverage for IteratorClose in Promise combinators SM doesn't pass these tests, whereas JSC and V8 passes them. --- ...-throws-iterator-return-is-not-callable.js | 70 ++++++++++++++++++ ...hrows-iterator-return-null-or-undefined.js | 71 +++++++++++++++++++ ...-throws-iterator-return-is-not-callable.js | 70 ++++++++++++++++++ ...hrows-iterator-return-null-or-undefined.js | 71 +++++++++++++++++++ ...-throws-iterator-return-is-not-callable.js | 70 ++++++++++++++++++ ...hrows-iterator-return-null-or-undefined.js | 71 +++++++++++++++++++ ...-throws-iterator-return-is-not-callable.js | 70 ++++++++++++++++++ ...hrows-iterator-return-null-or-undefined.js | 71 +++++++++++++++++++ 8 files changed, 564 insertions(+) create mode 100644 test/built-ins/Promise/all/resolve-throws-iterator-return-is-not-callable.js create mode 100644 test/built-ins/Promise/all/resolve-throws-iterator-return-null-or-undefined.js create mode 100644 test/built-ins/Promise/allSettled/resolve-throws-iterator-return-is-not-callable.js create mode 100644 test/built-ins/Promise/allSettled/resolve-throws-iterator-return-null-or-undefined.js create mode 100644 test/built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js create mode 100644 test/built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js create mode 100644 test/built-ins/Promise/race/resolve-throws-iterator-return-is-not-callable.js create mode 100644 test/built-ins/Promise/race/resolve-throws-iterator-return-null-or-undefined.js diff --git a/test/built-ins/Promise/all/resolve-throws-iterator-return-is-not-callable.js b/test/built-ins/Promise/all/resolve-throws-iterator-return-is-not-callable.js new file mode 100644 index 0000000000..bd1595a1ad --- /dev/null +++ b/test/built-ins/Promise/all/resolve-throws-iterator-return-is-not-callable.js @@ -0,0 +1,70 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.all +description: > + Input throw-completion forwarded when IteratorClose returns abruptly because GetMethod throws. +info: | + 27.2.4.1 Promise.all ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + ... + 5. If completion is a throw completion, return ? completion. + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [0, 0n, true, "string", {}, Symbol()]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.all.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/all/resolve-throws-iterator-return-null-or-undefined.js b/test/built-ins/Promise/all/resolve-throws-iterator-return-null-or-undefined.js new file mode 100644 index 0000000000..a1c1df3e46 --- /dev/null +++ b/test/built-ins/Promise/all/resolve-throws-iterator-return-null-or-undefined.js @@ -0,0 +1,71 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.all +description: > + Input throw-completion forwarded when IteratorClose returns normally because GetMethod returns undefined. +info: | + 27.2.4.1 Promise.all ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + ... + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [null, undefined]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.all.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-is-not-callable.js b/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-is-not-callable.js new file mode 100644 index 0000000000..c5fd489401 --- /dev/null +++ b/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-is-not-callable.js @@ -0,0 +1,70 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.allsettled +description: > + Input throw-completion forwarded when IteratorClose returns abruptly because GetMethod throws. +info: | + 27.2.4.2 Promise.allSettled ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + ... + 5. If completion is a throw completion, return ? completion. + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [0, 0n, true, "string", {}, Symbol()]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.allSettled.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-null-or-undefined.js b/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-null-or-undefined.js new file mode 100644 index 0000000000..04af05eebb --- /dev/null +++ b/test/built-ins/Promise/allSettled/resolve-throws-iterator-return-null-or-undefined.js @@ -0,0 +1,71 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.allsettled +description: > + Input throw-completion forwarded when IteratorClose returns normally because GetMethod returns undefined. +info: | + 27.2.4.2 Promise.allSettled ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + ... + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [null, undefined]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.allSettled.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js b/test/built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js new file mode 100644 index 0000000000..02ec406f3b --- /dev/null +++ b/test/built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js @@ -0,0 +1,70 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.any +description: > + Input throw-completion forwarded when IteratorClose returns abruptly because GetMethod throws. +info: | + 27.2.4.3 Promise.any ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + ... + 5. If completion is a throw completion, return ? completion. + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [0, 0n, true, "string", {}, Symbol()]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.any.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js b/test/built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js new file mode 100644 index 0000000000..c155b93f68 --- /dev/null +++ b/test/built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js @@ -0,0 +1,71 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.any +description: > + Input throw-completion forwarded when IteratorClose returns normally because GetMethod returns undefined. +info: | + 27.2.4.3 Promise.any ( iterable ) + ... + 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + ... + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [null, undefined]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.any.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/race/resolve-throws-iterator-return-is-not-callable.js b/test/built-ins/Promise/race/resolve-throws-iterator-return-is-not-callable.js new file mode 100644 index 0000000000..70a23cfc55 --- /dev/null +++ b/test/built-ins/Promise/race/resolve-throws-iterator-return-is-not-callable.js @@ -0,0 +1,70 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.race +description: > + Input throw-completion forwarded when IteratorClose returns abruptly because GetMethod throws. +info: | + 27.2.4.5 Promise.race ( iterable ) + ... + 7. Let result be Completion(PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + ... + 5. If completion is a throw completion, return ? completion. + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + 3. If IsCallable(func) is false, throw a TypeError exception. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [0, 0n, true, "string", {}, Symbol()]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.race.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +} diff --git a/test/built-ins/Promise/race/resolve-throws-iterator-return-null-or-undefined.js b/test/built-ins/Promise/race/resolve-throws-iterator-return-null-or-undefined.js new file mode 100644 index 0000000000..e440b27b1e --- /dev/null +++ b/test/built-ins/Promise/race/resolve-throws-iterator-return-null-or-undefined.js @@ -0,0 +1,71 @@ +// Copyright (C) 2025 André Bargull. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-promise.race +description: > + Input throw-completion forwarded when IteratorClose returns normally because GetMethod returns undefined. +info: | + 27.2.4.5 Promise.race ( iterable ) + ... + 7. Let result be Completion(PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve)). + 8. If result is an abrupt completion, then + a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + b. IfAbruptRejectPromise(result, promiseCapability). + ... + + 7.4.11 IteratorClose ( iteratorRecord, completion ) + ... + 3. Let innerResult be Completion(GetMethod(iterator, "return")). + 4. If innerResult is a normal completion, then + a. Let return be innerResult.[[Value]]. + b. If return is undefined, return ? completion. + ... + ... + + 7.3.10 GetMethod ( V, P ) + 1. Let func be ? GetV(V, P). + 2. If func is either undefined or null, return undefined. + ... +---*/ + +function resolve() { + throw new Test262Error("Unexpected call to resolve"); +} + +var rejectCallCount = 0; + +function reject(e) { + rejectCallCount += 1; + assert.sameValue(e, "bad promise resolve"); +} + +class BadPromise { + constructor(executor) { + executor(resolve, reject); + } + + static resolve() { + throw "bad promise resolve"; + } +} + +for (var returnMethod of [null, undefined]) { + var iterator = { + [Symbol.iterator]() { + return this; + }, + next() { + return {done: false}; + }, + return: returnMethod, + }; + + // Reset counter. + rejectCallCount = 0; + + Promise.race.call(BadPromise, iterator); + + // Ensure `reject` was called exactly once. + assert.sameValue(rejectCallCount, 1); +}