Commit 0f2ed07f authored by machenbach's avatar machenbach Committed by Commit bot

Revert of Clean up promises and fix an edge case bug (patchset #4 id:60001 of...

Revert of Clean up promises and fix an edge case bug (patchset #4 id:60001 of https://codereview.chromium.org/1488783002/ )

Reason for revert:
[Sheriff] Breaks layout tests:
https://build.chromium.org/p/client.v8.fyi/builders/V8-Blink%20Linux%2064/builds/3266

Please request rebase upstream first.

Original issue's description:
> Clean up promises and fix an edge case bug
>
> This patch builds on previous Promise spec compliance work by
> cleaning out some old code which existed to support
> Promise.prototype.chain, rephrasing some code to correspond more
> closely to the specification, and removing some incorrect brand
> checking. A test is added for a bug in an edge case which was fixed.
>
> R=rossberg
> BUG=v8:3641
> LOG=Y
>
> Committed: https://crrev.com/1deb89c8fd3cb69714ae0a24e3b5a4e78f6b73b4
> Cr-Commit-Position: refs/heads/master@{#32627}

TBR=rossberg@chromium.org,caitpotter88@gmail.com,littledan@chromium.org
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=v8:3641

Review URL: https://codereview.chromium.org/1501763004

Cr-Commit-Position: refs/heads/master@{#32629}
parent 93955d16
...@@ -40,6 +40,9 @@ function CreateResolvingFunctions(promise) { ...@@ -40,6 +40,9 @@ function CreateResolvingFunctions(promise) {
var resolve = function(value) { var resolve = function(value) {
if (alreadyResolved === true) return; if (alreadyResolved === true) return;
alreadyResolved = true; alreadyResolved = true;
if (value === promise) {
return PromiseReject(promise, MakeTypeError(kPromiseCyclic, value));
}
PromiseResolve(promise, value); PromiseResolve(promise, value);
}; };
...@@ -113,11 +116,37 @@ function PromiseDone(promise, status, value, promiseQueue) { ...@@ -113,11 +116,37 @@ function PromiseDone(promise, status, value, promiseQueue) {
} }
} }
function PromiseCoerce(constructor, x) {
if (!IsPromise(x) && IS_SPEC_OBJECT(x)) {
var then;
try {
then = x.then;
} catch(r) {
return %_Call(PromiseRejected, constructor, r);
}
if (IS_CALLABLE(then)) {
var deferred = NewPromiseCapability(constructor);
try {
%_Call(then, x, deferred.resolve, deferred.reject);
} catch(r) {
deferred.reject(r);
}
return deferred.promise;
}
}
return x;
}
function PromiseHandle(value, handler, deferred) { function PromiseHandle(value, handler, deferred) {
try { try {
%DebugPushPromise(deferred.promise, PromiseHandle); %DebugPushPromise(deferred.promise, PromiseHandle);
var result = handler(value); var result = handler(value);
deferred.resolve(result); if (result === deferred.promise)
throw MakeTypeError(kPromiseCyclic, result);
else if (IsPromise(result))
%_Call(PromiseChain, result, deferred.resolve, deferred.reject);
else
deferred.resolve(result);
} catch (exception) { } catch (exception) {
try { deferred.reject(exception); } catch (e) { } try { deferred.reject(exception); } catch (e) { }
} finally { } finally {
...@@ -164,29 +193,28 @@ function PromiseCreate() { ...@@ -164,29 +193,28 @@ function PromiseCreate() {
} }
function PromiseResolve(promise, x) { function PromiseResolve(promise, x) {
if (x === promise) { if (GET_PRIVATE(promise, promiseStatusSymbol) === 0) {
return PromiseReject(promise, MakeTypeError(kPromiseCyclic, x)); if (IS_SPEC_OBJECT(x)) {
} // 25.4.1.3.2 steps 8-12
if (IS_SPEC_OBJECT(x)) { try {
// 25.4.1.3.2 steps 8-12 var then = x.then;
try { } catch (e) {
var then = x.then; return PromiseReject(promise, e);
} catch (e) { }
return PromiseReject(promise, e); if (IS_CALLABLE(then)) {
} // PromiseResolveThenableJob
if (IS_CALLABLE(then)) { return %EnqueueMicrotask(function() {
// PromiseResolveThenableJob try {
return %EnqueueMicrotask(function() { var callbacks = CreateResolvingFunctions(promise);
try { %_Call(then, x, callbacks.resolve, callbacks.reject);
var callbacks = CreateResolvingFunctions(promise); } catch (e) {
%_Call(then, x, callbacks.resolve, callbacks.reject); PromiseReject(promise, e);
} catch (e) { }
PromiseReject(promise, e); });
} }
});
} }
PromiseDone(promise, +1, x, promiseOnResolveSymbol);
} }
PromiseDone(promise, +1, x, promiseOnResolveSymbol);
} }
function PromiseReject(promise, r) { function PromiseReject(promise, r) {
...@@ -217,7 +245,6 @@ function NewPromiseCapability(C) { ...@@ -217,7 +245,6 @@ function NewPromiseCapability(C) {
} else { } else {
var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED }; var result = {promise: UNDEFINED, resolve: UNDEFINED, reject: UNDEFINED };
result.promise = new C(function(resolve, reject) { result.promise = new C(function(resolve, reject) {
// TODO(littledan): Check for resolve and reject being not undefined
result.resolve = resolve; result.resolve = resolve;
result.reject = reject; result.reject = reject;
}); });
...@@ -230,13 +257,15 @@ function PromiseDeferred() { ...@@ -230,13 +257,15 @@ function PromiseDeferred() {
} }
function PromiseResolved(x) { function PromiseResolved(x) {
return %_Call(PromiseCast, this, x); if (this === GlobalPromise) {
// Optimized case, avoid extra closure.
return PromiseCreateAndSet(+1, x);
} else {
return new this(function(resolve, reject) { resolve(x) });
}
} }
function PromiseRejected(r) { function PromiseRejected(r) {
if (!IS_SPEC_OBJECT(this)) {
throw MakeTypeError(kCalledOnNonObject, PromiseRejected);
}
var promise; var promise;
if (this === GlobalPromise) { if (this === GlobalPromise) {
// Optimized case, avoid extra closure. // Optimized case, avoid extra closure.
...@@ -250,18 +279,15 @@ function PromiseRejected(r) { ...@@ -250,18 +279,15 @@ function PromiseRejected(r) {
return promise; return promise;
} }
// Multi-unwrapped chaining with thenable coercion. // Simple chaining.
function PromiseThen(onResolve, onReject) { // PromiseChain a.k.a. flatMap
var constructor = this.constructor; function PromiseChainInternal(constructor, onResolve, onReject) {
onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler; onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler; onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
var deferred = NewPromiseCapability(constructor); var deferred = NewPromiseCapability(constructor);
switch (GET_PRIVATE(this, promiseStatusSymbol)) { switch (GET_PRIVATE(this, promiseStatusSymbol)) {
case UNDEFINED: case UNDEFINED:
// TODO(littledan): The type check should be called before
// constructing NewPromiseCapability; this is observable when
// erroneously copying this method to other classes.
throw MakeTypeError(kNotAPromise, this); throw MakeTypeError(kNotAPromise, this);
case 0: // Pending case 0: // Pending
GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred); GET_PRIVATE(this, promiseOnResolveSymbol).push(onResolve, deferred);
...@@ -291,25 +317,47 @@ function PromiseThen(onResolve, onReject) { ...@@ -291,25 +317,47 @@ function PromiseThen(onResolve, onReject) {
return deferred.promise; return deferred.promise;
} }
// Chain is left around for now as an alias for then
function PromiseChain(onResolve, onReject) { function PromiseChain(onResolve, onReject) {
return %_Call(PromiseThen, this, onResolve, onReject); return %_Call(PromiseChainInternal, this, this.constructor,
onResolve, onReject);
} }
function PromiseCatch(onReject) { function PromiseCatch(onReject) {
return this.then(UNDEFINED, onReject); return this.then(UNDEFINED, onReject);
} }
// Multi-unwrapped chaining with thenable coercion.
function PromiseThen(onResolve, onReject) {
onResolve = IS_CALLABLE(onResolve) ? onResolve : PromiseIdResolveHandler;
onReject = IS_CALLABLE(onReject) ? onReject : PromiseIdRejectHandler;
var that = this;
var constructor = this.constructor;
return %_Call(
PromiseChainInternal,
this,
constructor,
function(x) {
x = PromiseCoerce(constructor, x);
if (x === that) {
return onReject(MakeTypeError(kPromiseCyclic, x));
} else if (IsPromise(x)) {
return x.then(onResolve, onReject);
} else {
return onResolve(x);
}
},
onReject
);
}
// Combinators. // Combinators.
function PromiseCast(x) { function PromiseCast(x) {
if (!IS_SPEC_OBJECT(this)) {
throw MakeTypeError(kCalledOnNonObject, PromiseCast);
}
if (IsPromise(x) && x.constructor === this) { if (IsPromise(x) && x.constructor === this) {
return x; return x;
} else { } else {
return new this(function(resolve, reject) { resolve(x) }); return new this(function(resolve) { resolve(x) });
} }
} }
......
...@@ -193,9 +193,8 @@ function assertAsyncDone(iteration) { ...@@ -193,9 +193,8 @@ function assertAsyncDone(iteration) {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
// Note: Chain now has then-style semantics, here and in future tests.
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "resolved/chain") }, function(x) { assertAsync(x === p2, "resolved/chain") },
assertUnreachable assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
...@@ -217,8 +216,8 @@ function assertAsyncDone(iteration) { ...@@ -217,8 +216,8 @@ function assertAsyncDone(iteration) {
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
assertUnreachable, function(x) { assertAsync(x === p2, "rejected/chain") },
function(x) { assertAsync(x === 5, "rejected/chain") } assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
})(); })();
...@@ -234,16 +233,17 @@ function assertAsyncDone(iteration) { ...@@ -234,16 +233,17 @@ function assertAsyncDone(iteration) {
assertAsyncRan() assertAsyncRan()
})(); })();
/* TODO(caitp): remove tests once PromiseChain is removed, per bug 3237
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain(function(x) { return x }, assertUnreachable).chain( p3.chain(function(x) { return x }, assertUnreachable).chain(
function(x) { assertAsync(x === 5, "resolved/chain/chain") }, function(x) { assertAsync(x === p1, "resolved/chain/chain") },
assertUnreachable assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
})(); })();*/
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
...@@ -371,7 +371,7 @@ function assertAsyncDone(iteration) { ...@@ -371,7 +371,7 @@ function assertAsyncDone(iteration) {
var p2 = {then: function(onResolve, onReject) { onResolve(p1) }} var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "resolved/thenable/chain") }, function(x) { assertAsync(x === p2, "resolved/thenable/chain") },
assertUnreachable assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
...@@ -393,8 +393,8 @@ function assertAsyncDone(iteration) { ...@@ -393,8 +393,8 @@ function assertAsyncDone(iteration) {
var p2 = {then: function(onResolve, onReject) { onResolve(p1) }} var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
assertUnreachable, function(x) { assertAsync(x === p2, "rejected/thenable/chain") },
function(x) { assertAsync(x === 5, "rejected/thenable/chain") } assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
})(); })();
...@@ -416,7 +416,7 @@ function assertAsyncDone(iteration) { ...@@ -416,7 +416,7 @@ function assertAsyncDone(iteration) {
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "chain/resolve") }, function(x) { assertAsync(x === p2, "chain/resolve") },
assertUnreachable assertUnreachable
) )
deferred.resolve(5) deferred.resolve(5)
...@@ -426,8 +426,8 @@ function assertAsyncDone(iteration) { ...@@ -426,8 +426,8 @@ function assertAsyncDone(iteration) {
(function() { (function() {
var deferred = Promise.defer() var deferred = Promise.defer()
var p1 = deferred.promise var p1 = deferred.promise
var p2 = Promise.resolve(p1) var p2 = Promise.accept(p1)
var p3 = Promise.resolve(p2) var p3 = Promise.accept(p2)
p3.then( p3.then(
function(x) { assertAsync(x === 5, "then/resolve") }, function(x) { assertAsync(x === 5, "then/resolve") },
assertUnreachable assertUnreachable
...@@ -442,8 +442,8 @@ function assertAsyncDone(iteration) { ...@@ -442,8 +442,8 @@ function assertAsyncDone(iteration) {
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
assertUnreachable, function(x) { assertAsync(x === p2, "chain/reject") },
function(x) { assertAsync(x === 5, "chain/reject") } assertUnreachable
) )
deferred.reject(5) deferred.reject(5)
assertAsyncRan() assertAsyncRan()
...@@ -492,7 +492,7 @@ function assertAsyncDone(iteration) { ...@@ -492,7 +492,7 @@ function assertAsyncDone(iteration) {
var p2 = {then: function(onResolve, onReject) { onResolve(p1) }} var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "chain/resolve/thenable") }, function(x) { assertAsync(x === p2, "chain/resolve/thenable") },
assertUnreachable assertUnreachable
) )
deferred.resolve(5) deferred.resolve(5)
...@@ -518,8 +518,8 @@ function assertAsyncDone(iteration) { ...@@ -518,8 +518,8 @@ function assertAsyncDone(iteration) {
var p2 = {then: function(onResolve, onReject) { onResolve(p1) }} var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
var p3 = Promise.accept(p2) var p3 = Promise.accept(p2)
p3.chain( p3.chain(
assertUnreachable, function(x) { assertAsync(x === p2, "chain/reject/thenable") },
function(x) { assertAsync(x === 5, "chain/reject/thenable") } assertUnreachable
) )
deferred.reject(5) deferred.reject(5)
assertAsyncRan() assertAsyncRan()
...@@ -538,18 +538,19 @@ function assertAsyncDone(iteration) { ...@@ -538,18 +538,19 @@ function assertAsyncDone(iteration) {
assertAsyncRan() assertAsyncRan()
})(); })();
/* TODO(caitp): remove tests once PromiseChain is removed, per bug v8:3237
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
var p2 = Promise.accept(p1) var p2 = Promise.accept(p1)
var deferred = Promise.defer() var deferred = Promise.defer()
var p3 = deferred.promise var p3 = deferred.promise
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "chain/resolve2") }, function(x) { assertAsync(x === p2, "chain/resolve2") },
assertUnreachable assertUnreachable
) )
deferred.resolve(p2) deferred.resolve(p2)
assertAsyncRan() assertAsyncRan()
})(); })(); */
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
...@@ -590,18 +591,19 @@ function assertAsyncDone(iteration) { ...@@ -590,18 +591,19 @@ function assertAsyncDone(iteration) {
assertAsyncRan() assertAsyncRan()
})(); })();
/* TODO(caitp): remove tests once PromiseChain is removed, per bug v8:3237
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
var p2 = {then: function(onResolve, onReject) { onResolve(p1) }} var p2 = {then: function(onResolve, onReject) { onResolve(p1) }}
var deferred = Promise.defer() var deferred = Promise.defer()
var p3 = deferred.promise var p3 = deferred.promise
p3.chain( p3.chain(
function(x) { assertAsync(x === 5, "chain/resolve/thenable2") }, function(x) { assertAsync(x === p2, "chain/resolve/thenable2") },
assertUnreachable assertUnreachable
) )
deferred.resolve(p2) deferred.resolve(p2)
assertAsyncRan() assertAsyncRan()
})(); })(); */
(function() { (function() {
var p1 = Promise.accept(5) var p1 = Promise.accept(5)
...@@ -636,17 +638,19 @@ function assertAsyncDone(iteration) { ...@@ -636,17 +638,19 @@ function assertAsyncDone(iteration) {
assertAsyncRan() assertAsyncRan()
})(); })();
/* TODO(caitp): remove tests once PromiseChain is removed, per bug v8:3237
(function() { (function() {
var deferred = Promise.defer() var deferred = Promise.defer()
var p = deferred.promise var p = deferred.promise
deferred.resolve(p) deferred.resolve(p)
p.chain( p.chain(
assertUnreachable, function(x) { assertAsync(x === p, "cyclic/deferred/chain") },
function(r) { assertAsync(r instanceof TypeError, "cyclic/deferred/then") } assertUnreachable
) )
assertAsyncRan() assertAsyncRan()
})(); })();*/
/* TODO(caitp): remove tests once PromiseChain is removed, per bug v8:3237
(function() { (function() {
var deferred = Promise.defer() var deferred = Promise.defer()
var p = deferred.promise var p = deferred.promise
...@@ -656,7 +660,7 @@ function assertAsyncDone(iteration) { ...@@ -656,7 +660,7 @@ function assertAsyncDone(iteration) {
function(r) { assertAsync(r instanceof TypeError, "cyclic/deferred/then") } function(r) { assertAsync(r instanceof TypeError, "cyclic/deferred/then") }
) )
assertAsyncRan() assertAsyncRan()
})(); })();*/
(function() { (function() {
Promise.all([]).chain( Promise.all([]).chain(
...@@ -759,6 +763,7 @@ function assertAsyncDone(iteration) { ...@@ -759,6 +763,7 @@ function assertAsyncDone(iteration) {
assertAsyncRan() assertAsyncRan()
})(); })();
(function() { (function() {
'use strict'; 'use strict';
var getCalls = 0; var getCalls = 0;
......
...@@ -54,12 +54,6 @@ ...@@ -54,12 +54,6 @@
# Issue 3784: setters-on-elements is flaky # Issue 3784: setters-on-elements is flaky
'setters-on-elements': [PASS, FAIL], 'setters-on-elements': [PASS, FAIL],
# Issue 3641: The new 'then' semantics suppress some exceptions.
# These tests may be changed or removed when 'chain' is deprecated.
'es6/debug-promises/reject-with-throw-in-reject': [FAIL],
'es6/debug-promises/reject-with-undefined-reject': [FAIL],
'es6/debug-promises/reject-with-invalid-reject': [FAIL],
############################################################################## ##############################################################################
# TurboFan compiler failures. # TurboFan compiler failures.
......
// Copyright 2015 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: --allow-natives-syntax
// If a Promise's then method is overridden, that should be respected
// even if the promise is already resolved. x's resolution function is
// only called by Promise.resolve(); there shouldn't be a resolution
// check before when calling x.then.
// Async assert framework copied from mjsunit/es6/promises.js
var asyncAssertsExpected = 0;
function assertAsyncRan() { ++asyncAssertsExpected }
function assertLater(f, name) {
assertFalse(f()); // should not be true synchronously
++asyncAssertsExpected;
var iterations = 0;
function runAssertion() {
if (f()) {
print(name, "succeeded");
--asyncAssertsExpected;
} else if (iterations++ < 10) {
%EnqueueMicrotask(runAssertion);
} else {
%AbortJS(name + " FAILED!");
}
}
%EnqueueMicrotask(runAssertion);
}
function assertAsyncDone(iteration) {
var iteration = iteration || 0;
%EnqueueMicrotask(function() {
if (asyncAssertsExpected === 0)
assertAsync(true, "all")
else if (iteration > 10) // Shouldn't take more.
assertAsync(false, "all... " + asyncAssertsExpected)
else
assertAsyncDone(iteration + 1)
});
}
// End async assert framework
var y;
var x = Promise.resolve();
x.then = () => { y = true; }
Promise.resolve().then(() => x);
assertLater(() => y === true, "y === true");
assertAsyncDone();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment