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