1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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/GeneratorObject.h"
8
9 #include "frontend/ParserAtom.h"
10 #ifdef DEBUG
11 # include "js/friend/DumpFunctions.h" // js::DumpObject, js::DumpValue
12 #endif
13 #include "js/PropertySpec.h"
14 #include "vm/AsyncFunction.h"
15 #include "vm/AsyncIteration.h"
16 #include "vm/FunctionFlags.h" // js::FunctionFlags
17 #include "vm/GlobalObject.h"
18 #include "vm/JSObject.h"
19 #include "vm/PlainObject.h" // js::PlainObject
20
21 #include "debugger/DebugAPI-inl.h"
22 #include "vm/ArrayObject-inl.h"
23 #include "vm/JSAtom-inl.h"
24 #include "vm/JSScript-inl.h"
25 #include "vm/NativeObject-inl.h"
26 #include "vm/Stack-inl.h"
27
28 using namespace js;
29
create(JSContext * cx,HandleFunction callee,HandleScript script,HandleObject environmentChain,Handle<ArgumentsObject * > argsObject)30 AbstractGeneratorObject* AbstractGeneratorObject::create(
31 JSContext* cx, HandleFunction callee, HandleScript script,
32 HandleObject environmentChain, Handle<ArgumentsObject*> argsObject) {
33 Rooted<AbstractGeneratorObject*> genObj(cx);
34 if (!callee->isAsync()) {
35 genObj = GeneratorObject::create(cx, callee);
36 } else if (callee->isGenerator()) {
37 genObj = AsyncGeneratorObject::create(cx, callee);
38 } else {
39 genObj = AsyncFunctionGeneratorObject::create(cx, callee);
40 }
41 if (!genObj) {
42 return nullptr;
43 }
44
45 genObj->setCallee(*callee);
46 genObj->setEnvironmentChain(*environmentChain);
47 if (argsObject) {
48 genObj->setArgsObj(*argsObject.get());
49 }
50
51 ArrayObject* stack = NewDenseFullyAllocatedArray(cx, script->nslots());
52 if (!stack) {
53 return nullptr;
54 }
55
56 genObj->setStackStorage(*stack);
57
58 // Note: This assumes that a Warp frame cannot be the target of
59 // the debugger, as we do not call OnNewGenerator.
60 return genObj;
61 }
62
createFromFrame(JSContext * cx,AbstractFramePtr frame)63 JSObject* AbstractGeneratorObject::createFromFrame(JSContext* cx,
64 AbstractFramePtr frame) {
65 MOZ_ASSERT(frame.isGeneratorFrame());
66 MOZ_ASSERT(!frame.isConstructing());
67
68 if (frame.isModuleFrame()) {
69 return createModuleGenerator(cx, frame);
70 }
71
72 RootedFunction fun(cx, frame.callee());
73 Rooted<ArgumentsObject*> maybeArgs(
74 cx, frame.script()->needsArgsObj() ? &frame.argsObj() : nullptr);
75 RootedObject environmentChain(cx, frame.environmentChain());
76
77 RootedScript script(cx, frame.script());
78 Rooted<AbstractGeneratorObject*> genObj(
79 cx, AbstractGeneratorObject::create(cx, fun, script, environmentChain,
80 maybeArgs));
81 if (!genObj) {
82 return nullptr;
83 }
84
85 if (!DebugAPI::onNewGenerator(cx, frame, genObj)) {
86 return nullptr;
87 }
88
89 return genObj;
90 }
91
createModuleGenerator(JSContext * cx,AbstractFramePtr frame)92 JSObject* AbstractGeneratorObject::createModuleGenerator(
93 JSContext* cx, AbstractFramePtr frame) {
94 Rooted<ModuleObject*> module(cx, frame.script()->module());
95 Rooted<AbstractGeneratorObject*> genObj(cx);
96 genObj = AsyncFunctionGeneratorObject::create(cx, module);
97 if (!genObj) {
98 return nullptr;
99 }
100
101 // Create a handler function to wrap the module's script. This way
102 // we can access it later and restore the state.
103 HandlePropertyName funName = cx->names().empty;
104 RootedFunction handlerFun(
105 cx, NewFunctionWithProto(cx, nullptr, 0,
106 FunctionFlags::INTERPRETED_GENERATOR_OR_ASYNC,
107 nullptr, funName, nullptr,
108 gc::AllocKind::FUNCTION, GenericObject));
109 if (!handlerFun) {
110 return nullptr;
111 }
112 handlerFun->initScript(module->script());
113
114 genObj->setCallee(*handlerFun);
115 genObj->setEnvironmentChain(*frame.environmentChain());
116
117 ArrayObject* stack =
118 NewDenseFullyAllocatedArray(cx, module->script()->nslots());
119 if (!stack) {
120 return nullptr;
121 }
122
123 genObj->setStackStorage(*stack);
124
125 if (!DebugAPI::onNewGenerator(cx, frame, genObj)) {
126 return nullptr;
127 }
128
129 return genObj;
130 }
131
trace(JSTracer * trc)132 void AbstractGeneratorObject::trace(JSTracer* trc) {
133 DebugAPI::traceGeneratorFrame(trc, this);
134 }
135
suspend(JSContext * cx,HandleObject obj,AbstractFramePtr frame,jsbytecode * pc,unsigned nvalues)136 bool AbstractGeneratorObject::suspend(JSContext* cx, HandleObject obj,
137 AbstractFramePtr frame, jsbytecode* pc,
138 unsigned nvalues) {
139 MOZ_ASSERT(JSOp(*pc) == JSOp::InitialYield || JSOp(*pc) == JSOp::Yield ||
140 JSOp(*pc) == JSOp::Await);
141
142 auto genObj = obj.as<AbstractGeneratorObject>();
143 MOZ_ASSERT(!genObj->hasStackStorage() || genObj->isStackStorageEmpty());
144 MOZ_ASSERT_IF(JSOp(*pc) == JSOp::Await, genObj->callee().isAsync());
145 MOZ_ASSERT_IF(JSOp(*pc) == JSOp::Yield, genObj->callee().isGenerator());
146
147 if (nvalues > 0) {
148 ArrayObject* stack = nullptr;
149 MOZ_ASSERT(genObj->hasStackStorage());
150 stack = &genObj->stackStorage();
151 MOZ_ASSERT(stack->getDenseCapacity() >= nvalues);
152 if (!frame.saveGeneratorSlots(cx, nvalues, stack)) {
153 return false;
154 }
155 }
156
157 genObj->setResumeIndex(pc);
158 genObj->setEnvironmentChain(*frame.environmentChain());
159 return true;
160 }
161
162 #ifdef DEBUG
dump() const163 void AbstractGeneratorObject::dump() const {
164 fprintf(stderr, "(AbstractGeneratorObject*) %p {\n", (void*)this);
165 fprintf(stderr, " callee: (JSFunction*) %p,\n", (void*)&callee());
166 fprintf(stderr, " environmentChain: (JSObject*) %p,\n",
167 (void*)&environmentChain());
168 if (hasArgsObj()) {
169 fprintf(stderr, " argsObj: Some((ArgumentsObject*) %p),\n",
170 (void*)&argsObj());
171 } else {
172 fprintf(stderr, " argsObj: None,\n");
173 }
174 if (hasStackStorage()) {
175 fprintf(stderr, " stackStorage: Some(ArrayObject {\n");
176 ArrayObject& stack = stackStorage();
177 uint32_t denseLen = uint32_t(stack.getDenseInitializedLength());
178 fprintf(stderr, " denseInitializedLength: %u\n,", denseLen);
179 uint32_t len = stack.length();
180 fprintf(stderr, " length: %u\n,", len);
181 fprintf(stderr, " data: [\n");
182 const Value* elements = getDenseElements();
183 for (uint32_t i = 0; i < std::max(len, denseLen); i++) {
184 fprintf(stderr, " [%u]: ", i);
185 js::DumpValue(elements[i]);
186 }
187 fprintf(stderr, " ],\n");
188 fprintf(stderr, " }),\n");
189 } else {
190 fprintf(stderr, " stackStorage: None\n");
191 }
192 if (isSuspended()) {
193 fprintf(stderr, " resumeIndex: Some(%u),\n", resumeIndex());
194 } else {
195 fprintf(stderr, " resumeIndex: None, /* (not suspended) */\n");
196 }
197 fprintf(stderr, "}\n");
198 }
199 #endif
200
finalSuspend(HandleObject obj)201 void AbstractGeneratorObject::finalSuspend(HandleObject obj) {
202 auto* genObj = &obj->as<AbstractGeneratorObject>();
203 MOZ_ASSERT(genObj->isRunning());
204 genObj->setClosed();
205 }
206
GetGeneratorObjectForCall(JSContext * cx,CallObject & callObj)207 static AbstractGeneratorObject* GetGeneratorObjectForCall(JSContext* cx,
208 CallObject& callObj) {
209 // The ".generator" binding is always present and always "aliased".
210 mozilla::Maybe<PropertyInfo> prop =
211 callObj.lookup(cx, cx->names().dotGenerator);
212 if (prop.isNothing()) {
213 return nullptr;
214 }
215 Value genValue = callObj.getSlot(prop->slot());
216
217 // If the `Generator; SetAliasedVar ".generator"; InitialYield` bytecode
218 // sequence has not run yet, genValue is undefined.
219 return genValue.isObject()
220 ? &genValue.toObject().as<AbstractGeneratorObject>()
221 : nullptr;
222 }
223
GetGeneratorObjectForFrame(JSContext * cx,AbstractFramePtr frame)224 AbstractGeneratorObject* js::GetGeneratorObjectForFrame(
225 JSContext* cx, AbstractFramePtr frame) {
226 cx->check(frame);
227 MOZ_ASSERT(frame.isGeneratorFrame());
228
229 if (frame.isModuleFrame()) {
230 ModuleEnvironmentObject* moduleEnv =
231 frame.script()->module()->environment();
232 mozilla::Maybe<PropertyInfo> prop =
233 moduleEnv->lookup(cx, cx->names().dotGenerator);
234 Value genValue = moduleEnv->getSlot(prop->slot());
235 return genValue.isObject()
236 ? &genValue.toObject().as<AbstractGeneratorObject>()
237 : nullptr;
238 }
239 if (!frame.hasInitialEnvironment()) {
240 return nullptr;
241 }
242
243 return GetGeneratorObjectForCall(cx, frame.callObj());
244 }
245
GetGeneratorObjectForEnvironment(JSContext * cx,HandleObject env)246 AbstractGeneratorObject* js::GetGeneratorObjectForEnvironment(
247 JSContext* cx, HandleObject env) {
248 auto* call = CallObject::find(env);
249 return call ? GetGeneratorObjectForCall(cx, *call) : nullptr;
250 }
251
GeneratorThrowOrReturn(JSContext * cx,AbstractFramePtr frame,Handle<AbstractGeneratorObject * > genObj,HandleValue arg,GeneratorResumeKind resumeKind)252 bool js::GeneratorThrowOrReturn(JSContext* cx, AbstractFramePtr frame,
253 Handle<AbstractGeneratorObject*> genObj,
254 HandleValue arg,
255 GeneratorResumeKind resumeKind) {
256 MOZ_ASSERT(genObj->isRunning());
257 if (resumeKind == GeneratorResumeKind::Throw) {
258 cx->setPendingExceptionAndCaptureStack(arg);
259 } else {
260 MOZ_ASSERT(resumeKind == GeneratorResumeKind::Return);
261
262 MOZ_ASSERT_IF(genObj->is<GeneratorObject>(), arg.isObject());
263 frame.setReturnValue(arg);
264
265 RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING));
266 cx->setPendingException(closing, nullptr);
267 }
268 return false;
269 }
270
resume(JSContext * cx,InterpreterActivation & activation,Handle<AbstractGeneratorObject * > genObj,HandleValue arg,HandleValue resumeKind)271 bool AbstractGeneratorObject::resume(JSContext* cx,
272 InterpreterActivation& activation,
273 Handle<AbstractGeneratorObject*> genObj,
274 HandleValue arg, HandleValue resumeKind) {
275 MOZ_ASSERT(genObj->isSuspended());
276
277 RootedFunction callee(cx, &genObj->callee());
278 RootedObject envChain(cx, &genObj->environmentChain());
279 if (!activation.resumeGeneratorFrame(callee, envChain)) {
280 return false;
281 }
282 activation.regs().fp()->setResumedGenerator();
283
284 if (genObj->hasArgsObj()) {
285 activation.regs().fp()->initArgsObj(genObj->argsObj());
286 }
287
288 if (genObj->hasStackStorage() && !genObj->isStackStorageEmpty()) {
289 JSScript* script = activation.regs().fp()->script();
290 ArrayObject* storage = &genObj->stackStorage();
291 uint32_t len = storage->getDenseInitializedLength();
292 activation.regs().fp()->restoreGeneratorSlots(storage);
293 activation.regs().sp += len - script->nfixed();
294 storage->setDenseInitializedLength(0);
295 }
296
297 JSScript* script = callee->nonLazyScript();
298 uint32_t offset = script->resumeOffsets()[genObj->resumeIndex()];
299 activation.regs().pc = script->offsetToPC(offset);
300
301 // Push arg, generator, resumeKind Values on the generator's stack.
302 activation.regs().sp += 3;
303 MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth()));
304 activation.regs().sp[-3] = arg;
305 activation.regs().sp[-2] = ObjectValue(*genObj);
306 activation.regs().sp[-1] = resumeKind;
307
308 genObj->setRunning();
309 return true;
310 }
311
create(JSContext * cx,HandleFunction fun)312 GeneratorObject* GeneratorObject::create(JSContext* cx, HandleFunction fun) {
313 MOZ_ASSERT(fun->isGenerator() && !fun->isAsync());
314
315 // FIXME: This would be faster if we could avoid doing a lookup to get
316 // the prototype for the instance. Bug 906600.
317 RootedValue pval(cx);
318 if (!GetProperty(cx, fun, fun, cx->names().prototype, &pval)) {
319 return nullptr;
320 }
321 RootedObject proto(cx, pval.isObject() ? &pval.toObject() : nullptr);
322 if (!proto) {
323 proto = GlobalObject::getOrCreateGeneratorObjectPrototype(cx, cx->global());
324 if (!proto) {
325 return nullptr;
326 }
327 }
328 return NewObjectWithGivenProto<GeneratorObject>(cx, proto);
329 }
330
331 const JSClass GeneratorObject::class_ = {
332 "Generator",
333 JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS),
334 &classOps_,
335 };
336
337 const JSClassOps GeneratorObject::classOps_ = {
338 nullptr, // addProperty
339 nullptr, // delProperty
340 nullptr, // enumerate
341 nullptr, // newEnumerate
342 nullptr, // resolve
343 nullptr, // mayResolve
344 nullptr, // finalize
345 nullptr, // call
346 nullptr, // hasInstance
347 nullptr, // construct
348 CallTraceMethod<AbstractGeneratorObject>, // trace
349 };
350
351 static const JSFunctionSpec generator_methods[] = {
352 JS_SELF_HOSTED_FN("next", "GeneratorNext", 1, 0),
353 JS_SELF_HOSTED_FN("throw", "GeneratorThrow", 1, 0),
354 JS_SELF_HOSTED_FN("return", "GeneratorReturn", 1, 0), JS_FS_END};
355
NewTenuredObjectWithFunctionPrototype(JSContext * cx,Handle<GlobalObject * > global)356 JSObject* js::NewTenuredObjectWithFunctionPrototype(
357 JSContext* cx, Handle<GlobalObject*> global) {
358 RootedObject proto(cx,
359 GlobalObject::getOrCreateFunctionPrototype(cx, global));
360 if (!proto) {
361 return nullptr;
362 }
363 return NewTenuredObjectWithGivenProto<PlainObject>(cx, proto);
364 }
365
CreateGeneratorFunction(JSContext * cx,JSProtoKey key)366 static JSObject* CreateGeneratorFunction(JSContext* cx, JSProtoKey key) {
367 RootedObject proto(
368 cx, GlobalObject::getOrCreateFunctionConstructor(cx, cx->global()));
369 if (!proto) {
370 return nullptr;
371 }
372
373 HandlePropertyName name = cx->names().GeneratorFunction;
374 return NewFunctionWithProto(cx, Generator, 1, FunctionFlags::NATIVE_CTOR,
375 nullptr, name, proto, gc::AllocKind::FUNCTION,
376 TenuredObject);
377 }
378
CreateGeneratorFunctionPrototype(JSContext * cx,JSProtoKey key)379 static JSObject* CreateGeneratorFunctionPrototype(JSContext* cx,
380 JSProtoKey key) {
381 return NewTenuredObjectWithFunctionPrototype(cx, cx->global());
382 }
383
GeneratorFunctionClassFinish(JSContext * cx,HandleObject genFunction,HandleObject genFunctionProto)384 static bool GeneratorFunctionClassFinish(JSContext* cx,
385 HandleObject genFunction,
386 HandleObject genFunctionProto) {
387 Handle<GlobalObject*> global = cx->global();
388
389 // Change the "constructor" property to non-writable before adding any other
390 // properties, so it's still the last property and can be modified without a
391 // dictionary-mode transition.
392 MOZ_ASSERT(genFunctionProto->as<NativeObject>().getLastProperty().key() ==
393 NameToId(cx->names().constructor));
394 MOZ_ASSERT(!genFunctionProto->as<NativeObject>().inDictionaryMode());
395
396 RootedValue genFunctionVal(cx, ObjectValue(*genFunction));
397 if (!DefineDataProperty(cx, genFunctionProto, cx->names().constructor,
398 genFunctionVal, JSPROP_READONLY)) {
399 return false;
400 }
401 MOZ_ASSERT(!genFunctionProto->as<NativeObject>().inDictionaryMode());
402
403 RootedObject iteratorProto(
404 cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
405 if (!iteratorProto) {
406 return false;
407 }
408
409 RootedObject genObjectProto(cx, GlobalObject::createBlankPrototypeInheriting(
410 cx, &PlainObject::class_, iteratorProto));
411 if (!genObjectProto) {
412 return false;
413 }
414 if (!DefinePropertiesAndFunctions(cx, genObjectProto, nullptr,
415 generator_methods) ||
416 !DefineToStringTag(cx, genObjectProto, cx->names().Generator)) {
417 return false;
418 }
419
420 if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto,
421 JSPROP_READONLY, JSPROP_READONLY) ||
422 !DefineToStringTag(cx, genFunctionProto, cx->names().GeneratorFunction)) {
423 return false;
424 }
425
426 global->setGeneratorObjectPrototype(genObjectProto);
427
428 return true;
429 }
430
431 static const ClassSpec GeneratorFunctionClassSpec = {
432 CreateGeneratorFunction,
433 CreateGeneratorFunctionPrototype,
434 nullptr,
435 nullptr,
436 nullptr,
437 nullptr,
438 GeneratorFunctionClassFinish,
439 ClassSpec::DontDefineConstructor};
440
441 const JSClass js::GeneratorFunctionClass = {
442 "GeneratorFunction", 0, JS_NULL_CLASS_OPS, &GeneratorFunctionClassSpec};
443
getUnaliasedLocal(uint32_t slot) const444 const Value& AbstractGeneratorObject::getUnaliasedLocal(uint32_t slot) const {
445 MOZ_ASSERT(isSuspended());
446 MOZ_ASSERT(hasStackStorage());
447 MOZ_ASSERT(slot < callee().nonLazyScript()->nfixed());
448 return stackStorage().getDenseElement(slot);
449 }
450
setUnaliasedLocal(uint32_t slot,const Value & value)451 void AbstractGeneratorObject::setUnaliasedLocal(uint32_t slot,
452 const Value& value) {
453 MOZ_ASSERT(isSuspended());
454 MOZ_ASSERT(hasStackStorage());
455 MOZ_ASSERT(slot < callee().nonLazyScript()->nfixed());
456 return stackStorage().setDenseElement(slot, value);
457 }
458
isAfterYield()459 bool AbstractGeneratorObject::isAfterYield() {
460 return isAfterYieldOrAwait(JSOp::Yield);
461 }
462
isAfterAwait()463 bool AbstractGeneratorObject::isAfterAwait() {
464 return isAfterYieldOrAwait(JSOp::Await);
465 }
466
isAfterYieldOrAwait(JSOp op)467 bool AbstractGeneratorObject::isAfterYieldOrAwait(JSOp op) {
468 if (isClosed() || isRunning()) {
469 return false;
470 }
471
472 JSScript* script = callee().nonLazyScript();
473 jsbytecode* code = script->code();
474 uint32_t nextOffset = script->resumeOffsets()[resumeIndex()];
475 if (JSOp(code[nextOffset]) != JSOp::AfterYield) {
476 return false;
477 }
478
479 static_assert(JSOpLength_Yield == JSOpLength_InitialYield,
480 "JSOp::Yield and JSOp::InitialYield must have the same length");
481 static_assert(JSOpLength_Yield == JSOpLength_Await,
482 "JSOp::Yield and JSOp::Await must have the same length");
483
484 uint32_t offset = nextOffset - JSOpLength_Yield;
485 JSOp prevOp = JSOp(code[offset]);
486 MOZ_ASSERT(prevOp == JSOp::InitialYield || prevOp == JSOp::Yield ||
487 prevOp == JSOp::Await);
488
489 return prevOp == op;
490 }
491
492 template <>
is() const493 bool JSObject::is<js::AbstractGeneratorObject>() const {
494 return is<GeneratorObject>() || is<AsyncFunctionGeneratorObject>() ||
495 is<AsyncGeneratorObject>();
496 }
497
ParserAtomToResumeKind(JSContext * cx,frontend::TaggedParserAtomIndex atom)498 GeneratorResumeKind js::ParserAtomToResumeKind(
499 JSContext* cx, frontend::TaggedParserAtomIndex atom) {
500 if (atom == frontend::TaggedParserAtomIndex::WellKnown::next()) {
501 return GeneratorResumeKind::Next;
502 }
503 if (atom == frontend::TaggedParserAtomIndex::WellKnown::throw_()) {
504 return GeneratorResumeKind::Throw;
505 }
506 MOZ_ASSERT(atom == frontend::TaggedParserAtomIndex::WellKnown::return_());
507 return GeneratorResumeKind::Return;
508 }
509
ResumeKindToAtom(JSContext * cx,GeneratorResumeKind kind)510 JSAtom* js::ResumeKindToAtom(JSContext* cx, GeneratorResumeKind kind) {
511 switch (kind) {
512 case GeneratorResumeKind::Next:
513 return cx->names().next;
514
515 case GeneratorResumeKind::Throw:
516 return cx->names().throw_;
517
518 case GeneratorResumeKind::Return:
519 return cx->names().return_;
520 }
521 MOZ_CRASH("Invalid resume kind");
522 }
523