1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "vm/AsyncFunction.h"
8
9 #include "jscompartment.h"
10
11 #include "builtin/Promise.h"
12 #include "vm/GeneratorObject.h"
13 #include "vm/GlobalObject.h"
14 #include "vm/Interpreter.h"
15 #include "vm/SelfHosting.h"
16
17 using namespace js;
18 using namespace js::gc;
19
20 /* static */ bool
initAsyncFunction(JSContext * cx,Handle<GlobalObject * > global)21 GlobalObject::initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global)
22 {
23 if (global->getReservedSlot(ASYNC_FUNCTION_PROTO).isObject())
24 return true;
25
26 RootedObject asyncFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
27 if (!asyncFunctionProto)
28 return false;
29
30 if (!DefineToStringTag(cx, asyncFunctionProto, cx->names().AsyncFunction))
31 return false;
32
33 RootedValue function(cx, global->getConstructor(JSProto_Function));
34 if (!function.toObjectOrNull())
35 return false;
36 RootedObject proto(cx, &function.toObject());
37 RootedAtom name(cx, cx->names().AsyncFunction);
38 RootedObject asyncFunction(cx, NewFunctionWithProto(cx, AsyncFunctionConstructor, 1,
39 JSFunction::NATIVE_CTOR, nullptr, name,
40 proto));
41 if (!asyncFunction)
42 return false;
43 if (!LinkConstructorAndPrototype(cx, asyncFunction, asyncFunctionProto))
44 return false;
45
46 global->setReservedSlot(ASYNC_FUNCTION, ObjectValue(*asyncFunction));
47 global->setReservedSlot(ASYNC_FUNCTION_PROTO, ObjectValue(*asyncFunctionProto));
48 return true;
49 }
50
51 static MOZ_MUST_USE bool AsyncFunctionStart(JSContext* cx, Handle<PromiseObject*> resultPromise,
52 HandleValue generatorVal);
53
54 #define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
55 #define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
56
57 // Async Functions proposal 1.1.8 and 1.2.14.
58 static bool
WrappedAsyncFunction(JSContext * cx,unsigned argc,Value * vp)59 WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
60 {
61 CallArgs args = CallArgsFromVp(argc, vp);
62
63 RootedFunction wrapped(cx, &args.callee().as<JSFunction>());
64 RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT));
65 RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>());
66 RootedValue thisValue(cx, args.thisv());
67
68 // Step 2.
69 // Also does a part of 2.2 steps 1-2.
70 RootedValue generatorVal(cx);
71 InvokeArgs args2(cx);
72 if (!args2.init(cx, argc))
73 return false;
74 for (size_t i = 0, len = argc; i < len; i++)
75 args2[i].set(args[i]);
76 if (Call(cx, unwrappedVal, thisValue, args2, &generatorVal)) {
77 // Step 1.
78 Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx, generatorVal));
79 if (!resultPromise)
80 return false;
81
82 // Step 3.
83 if (!AsyncFunctionStart(cx, resultPromise, generatorVal))
84 return false;
85
86 // Step 5.
87 args.rval().setObject(*resultPromise);
88 return true;
89 }
90
91 // Steps 1, 4.
92 RootedValue exc(cx);
93 if (!GetAndClearException(cx, &exc))
94 return false;
95 RootedObject rejectPromise(cx, PromiseObject::unforgeableReject(cx, exc));
96 if (!rejectPromise)
97 return false;
98
99 // Step 5.
100 args.rval().setObject(*rejectPromise);
101 return true;
102 }
103
104 // Async Functions proposal 2.1 steps 1, 3 (partially).
105 // In the spec it creates a function, but we create 2 functions `unwrapped` and
106 // `wrapped`. `unwrapped` is a generator that corresponds to
107 // the async function's body, replacing `await` with `yield`. `wrapped` is a
108 // function that is visible to the outside, and handles yielded values.
109 JSObject*
WrapAsyncFunction(JSContext * cx,HandleFunction unwrapped)110 js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped)
111 {
112 MOZ_ASSERT(unwrapped->isStarGenerator());
113
114 // Create a new function with AsyncFunctionPrototype, reusing the name and
115 // the length of `unwrapped`.
116
117 // Step 1.
118 RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
119 if (!proto)
120 return nullptr;
121
122 RootedAtom funName(cx, unwrapped->name());
123 uint16_t length;
124 if (!unwrapped->getLength(cx, &length))
125 return nullptr;
126
127 // Steps 3 (partially).
128 RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncFunction, length,
129 JSFunction::NATIVE_FUN, nullptr,
130 funName, proto,
131 AllocKind::FUNCTION_EXTENDED,
132 TenuredObject));
133 if (!wrapped)
134 return nullptr;
135
136 // Link them to each other to make GetWrappedAsyncFunction and
137 // GetUnwrappedAsyncFunction work.
138 unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
139 wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
140
141 return wrapped;
142 }
143
144 enum class ResumeKind {
145 Normal,
146 Throw
147 };
148
149 // Async Functions proposal 2.2 steps 3.f, 3.g.
150 // Async Functions proposal 2.2 steps 3.d-e, 3.g.
151 // Implemented in js/src/builtin/Promise.cpp
152
153 // Async Functions proposal 2.2 steps 3-8, 2.4 steps 2-7, 2.5 steps 2-7.
154 static bool
AsyncFunctionResume(JSContext * cx,Handle<PromiseObject * > resultPromise,HandleValue generatorVal,ResumeKind kind,HandleValue valueOrReason)155 AsyncFunctionResume(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal,
156 ResumeKind kind, HandleValue valueOrReason)
157 {
158 // Execution context switching is handled in generator.
159 HandlePropertyName funName = kind == ResumeKind::Normal
160 ? cx->names().StarGeneratorNext
161 : cx->names().StarGeneratorThrow;
162 FixedInvokeArgs<1> args(cx);
163 args[0].set(valueOrReason);
164 RootedValue result(cx);
165 if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result))
166 return AsyncFunctionThrown(cx, resultPromise);
167
168 RootedObject resultObj(cx, &result.toObject());
169 RootedValue doneVal(cx);
170 RootedValue value(cx);
171 if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
172 return false;
173 if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
174 return false;
175
176 if (doneVal.toBoolean())
177 return AsyncFunctionReturned(cx, resultPromise, value);
178
179 return AsyncFunctionAwait(cx, resultPromise, value);
180 }
181
182 // Async Functions proposal 2.2 steps 3-8.
183 static MOZ_MUST_USE bool
AsyncFunctionStart(JSContext * cx,Handle<PromiseObject * > resultPromise,HandleValue generatorVal)184 AsyncFunctionStart(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue generatorVal)
185 {
186 return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Normal, UndefinedHandleValue);
187 }
188
189 // Async Functions proposal 2.3 steps 1-8.
190 // Implemented in js/src/builtin/Promise.cpp
191
192 // Async Functions proposal 2.4.
193 MOZ_MUST_USE bool
AsyncFunctionAwaitedFulfilled(JSContext * cx,Handle<PromiseObject * > resultPromise,HandleValue generatorVal,HandleValue value)194 js::AsyncFunctionAwaitedFulfilled(JSContext* cx, Handle<PromiseObject*> resultPromise,
195 HandleValue generatorVal, HandleValue value)
196 {
197 // Step 1 (implicit).
198
199 // Steps 2-7.
200 return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Normal, value);
201 }
202
203 // Async Functions proposal 2.5.
204 MOZ_MUST_USE bool
AsyncFunctionAwaitedRejected(JSContext * cx,Handle<PromiseObject * > resultPromise,HandleValue generatorVal,HandleValue reason)205 js::AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise,
206 HandleValue generatorVal, HandleValue reason)
207 {
208 // Step 1 (implicit).
209
210 // Step 2-7.
211 return AsyncFunctionResume(cx, resultPromise, generatorVal, ResumeKind::Throw, reason);
212 }
213
214 JSFunction*
GetWrappedAsyncFunction(JSFunction * unwrapped)215 js::GetWrappedAsyncFunction(JSFunction* unwrapped)
216 {
217 MOZ_ASSERT(unwrapped->isAsync());
218 return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
219 }
220
221 JSFunction*
GetUnwrappedAsyncFunction(JSFunction * wrapped)222 js::GetUnwrappedAsyncFunction(JSFunction* wrapped)
223 {
224 MOZ_ASSERT(IsWrappedAsyncFunction(wrapped));
225 JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT).toObject().as<JSFunction>();
226 MOZ_ASSERT(unwrapped->isAsync());
227 return unwrapped;
228 }
229
230 bool
IsWrappedAsyncFunction(JSFunction * fun)231 js::IsWrappedAsyncFunction(JSFunction* fun)
232 {
233 return fun->maybeNative() == WrappedAsyncFunction;
234 }
235
236 MOZ_MUST_USE bool
CheckAsyncResumptionValue(JSContext * cx,HandleValue v)237 js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v)
238 {
239 return CheckStarGeneratorResumptionValue(cx, v);
240 }
241