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