X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/4be4e30906bcb8ee30b4d189205cb70bad6707ce..81345200c95645a1b0d2635520f96ad55dfde63f:/runtime/JSPromiseConstructor.cpp diff --git a/runtime/JSPromiseConstructor.cpp b/runtime/JSPromiseConstructor.cpp new file mode 100644 index 0000000..4026196 --- /dev/null +++ b/runtime/JSPromiseConstructor.cpp @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSPromiseConstructor.h" + +#if ENABLE(PROMISES) + +#include "Error.h" +#include "JSCJSValueInlines.h" +#include "JSCellInlines.h" +#include "JSPromise.h" +#include "JSPromiseDeferred.h" +#include "JSPromiseFunctions.h" +#include "JSPromisePrototype.h" +#include "Lookup.h" +#include "NumberObject.h" +#include "StructureInlines.h" + +namespace JSC { + +STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSPromiseConstructor); + +static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncCast(ExecState*); +static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncResolve(ExecState*); +static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState*); +static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState*); +static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncAll(ExecState*); +} + +#include "JSPromiseConstructor.lut.h" + +namespace JSC { + +const ClassInfo JSPromiseConstructor::s_info = { "Function", &InternalFunction::s_info, 0, ExecState::promiseConstructorTable, CREATE_METHOD_TABLE(JSPromiseConstructor) }; + +/* Source for JSPromiseConstructor.lut.h +@begin promiseConstructorTable + cast JSPromiseConstructorFuncCast DontEnum|Function 1 + resolve JSPromiseConstructorFuncResolve DontEnum|Function 1 + reject JSPromiseConstructorFuncReject DontEnum|Function 1 + race JSPromiseConstructorFuncRace DontEnum|Function 1 + all JSPromiseConstructorFuncAll DontEnum|Function 1 +@end +*/ + +JSPromiseConstructor* JSPromiseConstructor::create(VM& vm, Structure* structure, JSPromisePrototype* promisePrototype) +{ + JSPromiseConstructor* constructor = new (NotNull, allocateCell(vm.heap)) JSPromiseConstructor(vm, structure); + constructor->finishCreation(vm, promisePrototype); + return constructor; +} + +Structure* JSPromiseConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +JSPromiseConstructor::JSPromiseConstructor(VM& vm, Structure* structure) + : InternalFunction(vm, structure) +{ +} + +void JSPromiseConstructor::finishCreation(VM& vm, JSPromisePrototype* promisePrototype) +{ + Base::finishCreation(vm, "Promise"); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, promisePrototype, DontEnum | DontDelete | ReadOnly); + putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(1), ReadOnly | DontEnum | DontDelete); +} + +static EncodedJSValue JSC_HOST_CALL constructPromise(ExecState* exec) +{ + // NOTE: We ignore steps 1-4 as they only matter if you support subclassing, which we do not yet. + // 1. Let promise be the this value. + // 2. If Type(promise) is not Object, then throw a TypeError exception. + // 3. If promise does not have a [[PromiseStatus]] internal slot, then throw a TypeError exception. + // 4. If promise's [[PromiseStatus]] internal slot is not undefined, then throw a TypeError exception. + + JSValue resolver = exec->argument(0); + + // 5. IsCallable(resolver) is false, then throw a TypeError exception + CallData callData; + CallType callType = getCallData(resolver, callData); + if (callType == CallTypeNone) + return JSValue::encode(throwTypeError(exec, ASCIILiteral("Promise constructor takes a function argument"))); + + VM& vm = exec->vm(); + JSGlobalObject* globalObject = exec->callee()->globalObject(); + + JSPromise* promise = JSPromise::create(vm, globalObject, jsCast(exec->callee())); + + // NOTE: Steps 6-8 are handled by JSPromise::create(). + // 6. Set promise's [[PromiseStatus]] internal slot to "unresolved". + // 7. Set promise's [[ResolveReactions]] internal slot to a new empty List. + // 8. Set promise's [[RejectReactions]] internal slot to a new empty List. + + // 9. Let 'resolve' be a new built-in function object as defined in Resolve Promise Functions. + JSFunction* resolve = createResolvePromiseFunction(vm, globalObject); + + // 10. Set the [[Promise]] internal slot of 'resolve' to 'promise'. + resolve->putDirect(vm, vm.propertyNames->promisePrivateName, promise); + + // 11. Let 'reject' be a new built-in function object as defined in Reject Promise Functions + JSFunction* reject = createRejectPromiseFunction(vm, globalObject); + + // 12. Set the [[Promise]] internal slot of 'reject' to 'promise'. + reject->putDirect(vm, vm.propertyNames->promisePrivateName, promise); + + // 13. Let 'result' be the result of calling the [[Call]] internal method of resolver with + // undefined as thisArgument and a List containing resolve and reject as argumentsList. + MarkedArgumentBuffer arguments; + arguments.append(resolve); + arguments.append(reject); + call(exec, resolver, callType, callData, jsUndefined(), arguments); + + // 14. If result is an abrupt completion, call PromiseReject(promise, result.[[value]]). + if (exec->hadException()) { + JSValue exception = exec->exception(); + exec->clearException(); + + promise->reject(vm, exception); + } + + // 15. Return promise. + return JSValue::encode(promise); +} + +ConstructType JSPromiseConstructor::getConstructData(JSCell*, ConstructData& constructData) +{ + constructData.native.function = constructPromise; + return ConstructTypeHost; +} + +CallType JSPromiseConstructor::getCallData(JSCell*, CallData& callData) +{ + callData.native.function = constructPromise; + return CallTypeHost; +} + +bool JSPromiseConstructor::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) +{ + return getStaticFunctionSlot(exec, ExecState::promiseConstructorTable(exec->vm()), jsCast(object), propertyName, slot); +} + +EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncCast(ExecState* exec) +{ + // -- Promise.cast(x) -- + JSValue x = exec->argument(0); + + // 1. Let 'C' be the this value. + JSValue C = exec->thisValue(); + + // 2. If IsPromise(x) is true, + JSPromise* promise = jsDynamicCast(x); + if (promise) { + // i. Let 'constructor' be the value of x's [[PromiseConstructor]] internal slot. + JSValue constructor = promise->constructor(); + // ii. If SameValue(constructor, C) is true, return x. + if (sameValue(exec, constructor, C)) + return JSValue::encode(x); + } + + // 3. Let 'deferred' be the result of calling GetDeferred(C). + JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C); + + // 4. ReturnIfAbrupt(deferred). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + JSPromiseDeferred* deferred = jsCast(deferredValue); + + // 5. Let 'resolveResult' be the result of calling the [[Call]] internal method + // of deferred.[[Resolve]] with undefined as thisArgument and a List containing x + // as argumentsList. + performDeferredResolve(exec, deferred, x); + + // 6. ReturnIfAbrupt(resolveResult). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + // 7. Return deferred.[[Promise]]. + return JSValue::encode(deferred->promise()); +} + +EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncResolve(ExecState* exec) +{ + // -- Promise.resolve(x) -- + JSValue x = exec->argument(0); + + // 1. Let 'C' be the this value. + JSValue C = exec->thisValue(); + + // 2. Let 'deferred' be the result of calling GetDeferred(C). + JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C); + + // 3. ReturnIfAbrupt(deferred). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + JSPromiseDeferred* deferred = jsCast(deferredValue); + + // 4. Let 'resolveResult' be the result of calling the [[Call]] internal method + // of deferred.[[Resolve]] with undefined as thisArgument and a List containing x + // as argumentsList. + performDeferredResolve(exec, deferred, x); + + // 5. ReturnIfAbrupt(resolveResult). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + // 6. Return deferred.[[Promise]]. + return JSValue::encode(deferred->promise()); +} + +EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState* exec) +{ + // -- Promise.reject(x) -- + JSValue r = exec->argument(0); + + // 1. Let 'C' be the this value. + JSValue C = exec->thisValue(); + + // 2. Let 'deferred' be the result of calling GetDeferred(C). + JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C); + + // 3. ReturnIfAbrupt(deferred). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + JSPromiseDeferred* deferred = jsCast(deferredValue); + + // 4. Let 'rejectResult' be the result of calling the [[Call]] internal method + // of deferred.[[Reject]] with undefined as thisArgument and a List containing r + // as argumentsList. + performDeferredReject(exec, deferred, r); + + // 5. ReturnIfAbrupt(resolveResult). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + // 6. Return deferred.[[Promise]]. + return JSValue::encode(deferred->promise()); +} + +EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState* exec) +{ + // -- Promise.race(iterable) -- + JSValue iterable = exec->argument(0); + VM& vm = exec->vm(); + + // 1. Let 'C' be the this value. + JSValue C = exec->thisValue(); + + // 2. Let 'deferred' be the result of calling GetDeferred(C). + JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C); + + // 3. ReturnIfAbrupt(deferred). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + JSPromiseDeferred* deferred = jsCast(deferredValue); + + // 4. Let 'iterator' be the result of calling GetIterator(iterable). + JSValue iteratorFunction = iterable.get(exec, vm.propertyNames->iteratorPrivateName); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData iteratorFunctionCallData; + CallType iteratorFunctionCallType = getCallData(iteratorFunction, iteratorFunctionCallData); + if (iteratorFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + ArgList iteratorFunctionArguments; + JSValue iterator = call(exec, iteratorFunction, iteratorFunctionCallType, iteratorFunctionCallData, iterable, iteratorFunctionArguments); + + // 5. RejectIfAbrupt(iterator, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // 6. Repeat + do { + // i. Let 'next' be the result of calling IteratorStep(iterator). + JSValue nextFunction = iterator.get(exec, exec->vm().propertyNames->iteratorNextPrivateName); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData nextFunctionCallData; + CallType nextFunctionCallType = getCallData(nextFunction, nextFunctionCallData); + if (nextFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer nextFunctionArguments; + nextFunctionArguments.append(jsUndefined()); + JSValue next = call(exec, nextFunction, nextFunctionCallType, nextFunctionCallData, iterator, nextFunctionArguments); + + // ii. RejectIfAbrupt(next, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // iii. If 'next' is false, return deferred.[[Promise]]. + // Note: We implement this as an iterationTerminator + if (next == vm.iterationTerminator.get()) + return JSValue::encode(deferred->promise()); + + // iv. Let 'nextValue' be the result of calling IteratorValue(next). + // v. RejectIfAbrupt(nextValue, deferred). + // Note: 'next' is already the value, so there is nothing to do here. + + // vi. Let 'nextPromise' be the result of calling Invoke(C, "cast", (nextValue)). + JSValue castFunction = C.get(exec, vm.propertyNames->cast); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData castFunctionCallData; + CallType castFunctionCallType = getCallData(castFunction, castFunctionCallData); + if (castFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer castFunctionArguments; + castFunctionArguments.append(next); + JSValue nextPromise = call(exec, castFunction, castFunctionCallType, castFunctionCallData, C, castFunctionArguments); + + // vii. RejectIfAbrupt(nextPromise, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // viii. Let 'result' be the result of calling Invoke(nextPromise, "then", (deferred.[[Resolve]], deferred.[[Reject]])). + JSValue thenFunction = nextPromise.get(exec, vm.propertyNames->then); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData thenFunctionCallData; + CallType thenFunctionCallType = getCallData(thenFunction, thenFunctionCallData); + if (thenFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer thenFunctionArguments; + thenFunctionArguments.append(deferred->resolve()); + thenFunctionArguments.append(deferred->reject()); + + call(exec, thenFunction, thenFunctionCallType, thenFunctionCallData, nextPromise, thenFunctionArguments); + + // ix. RejectIfAbrupt(result, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + } while (true); +} + +EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncAll(ExecState* exec) +{ + // -- Promise.all(iterable) -- + + JSValue iterable = exec->argument(0); + VM& vm = exec->vm(); + + // 1. Let 'C' be the this value. + JSValue C = exec->thisValue(); + + // 2. Let 'deferred' be the result of calling GetDeferred(C). + JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C); + + // 3. ReturnIfAbrupt(deferred). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + + // NOTE: A non-abrupt completion of createJSPromiseDeferredFromConstructor implies that + // C and deferredValue are objects. + JSObject* thisObject = asObject(C); + JSPromiseDeferred* deferred = jsCast(deferredValue); + + // 4. Let 'iterator' be the result of calling GetIterator(iterable). + JSValue iteratorFunction = iterable.get(exec, vm.propertyNames->iteratorPrivateName); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData iteratorFunctionCallData; + CallType iteratorFunctionCallType = getCallData(iteratorFunction, iteratorFunctionCallData); + if (iteratorFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + ArgList iteratorFunctionArguments; + JSValue iterator = call(exec, iteratorFunction, iteratorFunctionCallType, iteratorFunctionCallData, iterable, iteratorFunctionArguments); + + // 5. RejectIfAbrupt(iterator, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // 6. Let 'values' be the result of calling ArrayCreate(0). + JSArray* values = constructEmptyArray(exec, nullptr, thisObject->globalObject()); + + // 7. Let 'countdownHolder' be Record { [[Countdown]]: 0 }. + NumberObject* countdownHolder = constructNumber(exec, thisObject->globalObject(), JSValue(0)); + + // 8. Let 'index' be 0. + unsigned index = 0; + + // 9. Repeat. + do { + // i. Let 'next' be the result of calling IteratorStep(iterator). + JSValue nextFunction = iterator.get(exec, exec->vm().propertyNames->iteratorNextPrivateName); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData nextFunctionCallData; + CallType nextFunctionCallType = getCallData(nextFunction, nextFunctionCallData); + if (nextFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer nextFunctionArguments; + nextFunctionArguments.append(jsUndefined()); + JSValue next = call(exec, nextFunction, nextFunctionCallType, nextFunctionCallData, iterator, nextFunctionArguments); + + // ii. RejectIfAbrupt(next, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // iii. If 'next' is false, + // Note: We implement this as an iterationTerminator + if (next == vm.iterationTerminator.get()) { + // a. If 'index' is 0, + if (!index) { + // a. Let 'resolveResult' be the result of calling the [[Call]] internal method + // of deferred.[[Resolve]] with undefined as thisArgument and a List containing + // values as argumentsList. + performDeferredResolve(exec, deferred, values); + + // b. ReturnIfAbrupt(resolveResult). + if (exec->hadException()) + return JSValue::encode(jsUndefined()); + } + + // b. Return deferred.[[Promise]]. + return JSValue::encode(deferred->promise()); + } + + // iv. Let 'nextValue' be the result of calling IteratorValue(next). + // v. RejectIfAbrupt(nextValue, deferred). + // Note: 'next' is already the value, so there is nothing to do here. + + // vi. Let 'nextPromise' be the result of calling Invoke(C, "cast", (nextValue)). + JSValue castFunction = C.get(exec, vm.propertyNames->cast); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData castFunctionCallData; + CallType castFunctionCallType = getCallData(castFunction, castFunctionCallData); + if (castFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer castFunctionArguments; + castFunctionArguments.append(next); + JSValue nextPromise = call(exec, castFunction, castFunctionCallType, castFunctionCallData, C, castFunctionArguments); + + // vii. RejectIfAbrupt(nextPromise, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // viii. Let 'countdownFunction' be a new built-in function object as defined in Promise.all Countdown Functions. + JSFunction* countdownFunction = createPromiseAllCountdownFunction(vm, thisObject->globalObject()); + + // ix. Set the [[Index]] internal slot of 'countdownFunction' to 'index'. + countdownFunction->putDirect(vm, vm.propertyNames->indexPrivateName, JSValue(index)); + + // x. Set the [[Values]] internal slot of 'countdownFunction' to 'values'. + countdownFunction->putDirect(vm, vm.propertyNames->valuesPrivateName, values); + + // xi. Set the [[Deferred]] internal slot of 'countdownFunction' to 'deferred'. + countdownFunction->putDirect(vm, vm.propertyNames->deferredPrivateName, deferred); + + // xii. Set the [[CountdownHolder]] internal slot of 'countdownFunction' to 'countdownHolder'. + countdownFunction->putDirect(vm, vm.propertyNames->countdownHolderPrivateName, countdownHolder); + + // xiii. Let 'result' be the result of calling Invoke(nextPromise, "then", (countdownFunction, deferred.[[Reject]])). + JSValue thenFunction = nextPromise.get(exec, vm.propertyNames->then); + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + CallData thenFunctionCallData; + CallType thenFunctionCallType = getCallData(thenFunction, thenFunctionCallData); + if (thenFunctionCallType == CallTypeNone) { + throwTypeError(exec); + return JSValue::encode(abruptRejection(exec, deferred)); + } + + MarkedArgumentBuffer thenFunctionArguments; + thenFunctionArguments.append(countdownFunction); + thenFunctionArguments.append(deferred->reject()); + + call(exec, thenFunction, thenFunctionCallType, thenFunctionCallData, nextPromise, thenFunctionArguments); + + // xiv. RejectIfAbrupt(result, deferred). + if (exec->hadException()) + return JSValue::encode(abruptRejection(exec, deferred)); + + // xv. Set index to index + 1. + index++; + + // xvi. Set countdownHolder.[[Countdown]] to countdownHolder.[[Countdown]] + 1. + uint32_t newCountdownValue = countdownHolder->internalValue().asUInt32() + 1; + countdownHolder->setInternalValue(vm, JSValue(newCountdownValue)); + } while (true); +} + +JSPromise* constructPromise(ExecState* exec, JSGlobalObject* globalObject, JSFunction* resolver) +{ + JSPromiseConstructor* promiseConstructor = globalObject->promiseConstructor(); + + ConstructData constructData; + ConstructType constructType = getConstructData(promiseConstructor, constructData); + ASSERT(constructType != ConstructTypeNone); + + MarkedArgumentBuffer arguments; + arguments.append(resolver); + + return jsCast(construct(exec, promiseConstructor, constructType, constructData, arguments)); +} + +} // namespace JSC + +#endif // ENABLE(PROMISES)