From 219bdc6f739abc3c2a254f88a1909ce89ae021f3 Mon Sep 17 00:00:00 2001
From: Mike Pennisi <mike@mikepennisi.com>
Date: Wed, 10 Feb 2016 13:30:54 -0500
Subject: [PATCH] Promise: Add tests to disallow faulty optimization

Add tests that assert behavior when a Promise is resolved with another
Promise whose `then` method has been overridden. Because all objects
with a `then` method are treated equivalently, the presence of a
[[PromiseState]] internal slot should have no effect on program
behavior.

These tests guard against a faulty optimization originally implemented
in V8:

https://bugs.chromium.org/p/v8/issues/detail?id=3641
---
 ...esolve-pending-fulfilled-prms-cstm-then.js | 55 +++++++++++++++++
 ...resolve-pending-rejected-prms-cstm-then.js | 56 ++++++++++++++++++
 ...esolve-settled-fulfilled-prms-cstm-then.js | 59 +++++++++++++++++++
 ...resolve-settled-rejected-prms-cstm-then.js | 59 +++++++++++++++++++
 .../Promise/race/resolve-prms-cstm-then.js    | 52 ++++++++++++++++
 .../resolve-prms-cstm-then-deferred.js        | 48 +++++++++++++++
 .../Promise/resolve-prms-cstm-then-immed.js   | 55 +++++++++++++++++
 .../Promise/resolve/resolve-prms-cstm-then.js | 40 +++++++++++++
 8 files changed, 424 insertions(+)
 create mode 100644 test/built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js
 create mode 100644 test/built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js
 create mode 100644 test/built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js
 create mode 100644 test/built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js
 create mode 100644 test/built-ins/Promise/race/resolve-prms-cstm-then.js
 create mode 100644 test/built-ins/Promise/resolve-prms-cstm-then-deferred.js
 create mode 100644 test/built-ins/Promise/resolve-prms-cstm-then-immed.js
 create mode 100644 test/built-ins/Promise/resolve/resolve-prms-cstm-then.js

diff --git a/test/built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js b/test/built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js
new file mode 100644
index 0000000000..24cc34407f
--- /dev/null
+++ b/test/built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden from a pending promise that is later fulfilled
+es6id: 25.4.5.3
+info: >
+    [...]
+    7. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+       resultCapability).
+
+    25.4.5.3.1 PerformPromiseThen
+    [...]
+    7. If the value of promise's [[PromiseState]] internal slot is "pending",
+       a. Append fulfillReaction as the last element of the List that is the
+          value of promise's [[PromiseFulfillReactions]] internal slot.
+       [...]
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var resolve;
+var thenable = new Promise(function(resolve) { resolve(); });
+var p1 = new Promise(function(_resolve) { resolve = _resolve; });
+var p2;
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+p2 = p1.then(function() {
+    return thenable;
+  });
+
+p2.then(function(x) {
+    if (x !== value) {
+      $DONE('The promise should be fulfilled with the resolution value of the provided promise.');
+      return;
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
+
+resolve();
diff --git a/test/built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js b/test/built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js
new file mode 100644
index 0000000000..b07f34fd17
--- /dev/null
+++ b/test/built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js
@@ -0,0 +1,56 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden from a pending promise that is later rejected
+es6id: 25.4.5.3
+info: >
+    [...]
+    7. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+       resultCapability).
+
+    25.4.5.3.1 PerformPromiseThen
+    [...]
+    7. If the value of promise's [[PromiseState]] internal slot is "pending",
+       [...]
+       b. Append rejectReaction as the last element of the List that is the
+          value of promise's [[PromiseRejectReactions]] internal slot.
+    [...]
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var reject;
+var thenable = new Promise(function(resolve) { resolve(); });
+var p1 = new Promise(function(_, _reject) { reject = _reject; });
+var p2;
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+p2 = p1.then(function() {}, function() {
+    return thenable;
+  });
+
+p2.then(function(x) {
+    if (x !== value) {
+      $DONE('The promise should be fulfilled with the resolution value of the provided promise.');
+      return;
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
+
+reject();
diff --git a/test/built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js b/test/built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js
new file mode 100644
index 0000000000..5527a4e970
--- /dev/null
+++ b/test/built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden from a fulfilled promise
+es6id: 25.4.5.3
+info: >
+    [...]
+    7. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+       resultCapability).
+
+    25.4.5.3.1 PerformPromiseThen
+    [...]
+    8. Else if the value of promise's [[PromiseState]] internal slot is
+       "fulfilled",
+       a. Let value be the value of promise's [[PromiseResult]] internal slot.
+       b. EnqueueJob("PromiseJobs", PromiseReactionJob, «fulfillReaction,
+          value»).
+
+    25.4.2.1 PromiseReactionJob
+    [...]
+    8. Let status be Call(promiseCapability.[[Resolve]], undefined,
+       «handlerResult.[[value]]»).
+    [...]
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var thenable = new Promise(function(resolve) { resolve(); });
+var p1 = new Promise(function(resolve) { resolve(); });
+var p2;
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+p2 = p1.then(function() {
+    return thenable;
+  });
+
+p2.then(function(x) {
+    if (x !== value) {
+      $DONE('The promise should be fulfilled with the resolution value of the provided promise.');
+      return;
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
diff --git a/test/built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js b/test/built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js
new file mode 100644
index 0000000000..54085255ac
--- /dev/null
+++ b/test/built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden from a rejected promise
+es6id: 25.4.5.3
+info: >
+    [...]
+    7. Return PerformPromiseThen(promise, onFulfilled, onRejected,
+       resultCapability).
+
+    25.4.5.3.1 PerformPromiseThen
+    [...]
+    9. Else if the value of promise's [[PromiseState]] internal slot is
+       "rejected",
+       a. Let reason be the value of promise's [[PromiseResult]] internal slot.
+       b. Perform EnqueueJob("PromiseJobs", PromiseReactionJob,
+          «rejectReaction, reason»).
+
+    25.4.2.1 PromiseReactionJob
+    [...]
+    8. Let status be Call(promiseCapability.[[Resolve]], undefined,
+       «handlerResult.[[value]]»).
+    [...]
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var thenable = new Promise(function(resolve) { resolve(); });
+var p1 = new Promise(function(_, reject) { reject(); });
+var p2;
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+p2 = p1.then(function() {}, function() {
+    return thenable;
+  });
+
+p2.then(function(x) {
+    if (x !== value) {
+      $DONE('The promise should be fulfilled with the resolution value of the provided promise.');
+      return;
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
diff --git a/test/built-ins/Promise/race/resolve-prms-cstm-then.js b/test/built-ins/Promise/race/resolve-prms-cstm-then.js
new file mode 100644
index 0000000000..d90834ddf8
--- /dev/null
+++ b/test/built-ins/Promise/race/resolve-prms-cstm-then.js
@@ -0,0 +1,52 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden
+es6id: 25.4.4.3
+info: >
+    [...]
+    6. Let promiseCapability be NewPromiseCapability(C).
+    [...]
+    11. Let result be PerformPromiseRace(iteratorRecord, promiseCapability, C).
+    [...]
+
+    25.4.4.3.1 Runtime Semantics: PerformPromiseRace
+    1. Repeat
+       [...]
+       j. Let result be Invoke(nextPromise, "then",
+          «promiseCapability.[[Resolve]], promiseCapability.[[Reject]]»).
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var thenableValue = {
+  then: function(resolve) {
+    resolve(value);
+  }
+};
+var thenable = new Promise(function(resolve) { resolve(); });
+
+thenable.then = function(resolve) {
+  resolve(thenableValue);
+};
+
+Promise.race([thenable])
+  .then(function(val) {
+    if (val !== value) {
+      $DONE('The promise should be resolved with the correct value.');
+      return;
+    }
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
diff --git a/test/built-ins/Promise/resolve-prms-cstm-then-deferred.js b/test/built-ins/Promise/resolve-prms-cstm-then-deferred.js
new file mode 100644
index 0000000000..6ebe0c8a71
--- /dev/null
+++ b/test/built-ins/Promise/resolve-prms-cstm-then-deferred.js
@@ -0,0 +1,48 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: >
+    Resolving with a resolved Promise instance whose `then` method has been
+    overridden after execution of the executor function
+es6id: 25.4.3.1
+info: >
+    [...]
+    8. Let resolvingFunctions be CreateResolvingFunctions(promise).
+    9. Let completion be Call(executor, undefined,
+       «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»).
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var resolve;
+var thenable = new Promise(function(resolve) { resolve(); });
+var promise = new Promise(function(_resolve) {
+  resolve = _resolve;
+});
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+promise.then(function(val) {
+    if (val !== value) {
+      $DONE('The promise should be fulfilled with the provided value.');
+      return;
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
+
+resolve(thenable);
diff --git a/test/built-ins/Promise/resolve-prms-cstm-then-immed.js b/test/built-ins/Promise/resolve-prms-cstm-then-immed.js
new file mode 100644
index 0000000000..c4312b50ec
--- /dev/null
+++ b/test/built-ins/Promise/resolve-prms-cstm-then-immed.js
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: >
+    Resolving with a resolved Promise instance whose `then` method has been
+    overridden from within the executor function
+es6id: 25.4.3.1
+info: >
+    [...]
+    8. Let resolvingFunctions be CreateResolvingFunctions(promise).
+    9. Let completion be Call(executor, undefined,
+       «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»).
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var lateCallCount = 0;
+var thenable = new Promise(function(resolve) { resolve(); });
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+var promise = new Promise(function(resolve) {
+  resolve(thenable);
+});
+
+thenable.then = function() {
+  lateCallCount += 1;
+};
+
+promise.then(function(val) {
+    if (val !== value) {
+      $DONE('The promise should be fulfilled with the provided value.');
+      return;
+    }
+
+    if (lateCallCount > 0) {
+      $DONE('The `then` method should be executed synchronously.');
+    }
+
+    $DONE();
+  }, function() {
+    $DONE('The promise should not be rejected.');
+  });
diff --git a/test/built-ins/Promise/resolve/resolve-prms-cstm-then.js b/test/built-ins/Promise/resolve/resolve-prms-cstm-then.js
new file mode 100644
index 0000000000..f53cd449d8
--- /dev/null
+++ b/test/built-ins/Promise/resolve/resolve-prms-cstm-then.js
@@ -0,0 +1,40 @@
+// Copyright (C) 2016 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: Resolving with a resolved Promise instance whose `then` method has been overridden
+es6id: 25.4.4.5
+info: >
+    [...]
+    6. Let resolveResult be Call(promiseCapability.[[Resolve]], undefined,
+       «x»).
+    [...]
+
+    25.4.1.3.2 Promise Resolve Functions
+    [...]
+    8. Let then be Get(resolution, "then").
+    9. If then is an abrupt completion, then
+       [...]
+    10. Let thenAction be then.[[value]].
+    11. If IsCallable(thenAction) is false, then
+        [...]
+    12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob,
+        «promise, resolution, thenAction»)
+---*/
+
+var value = {};
+var rejectCallCount = 0;
+var thenable = new Promise(function(resolve) { resolve(); });
+var resolvedValue;
+
+thenable.then = function(resolve) {
+  resolve(value);
+};
+
+Promise.resolve(thenable).then(function(val) {
+    resolvedValue = val;
+  }, function() {
+    rejectCallCount += 1;
+  });
+
+assert.sameValue(resolvedValue, value);
+assert.sameValue(rejectCallCount, 0);
-- 
GitLab