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/GeneratorObject.h"
8 
9 #include "jsatominlines.h"
10 #include "jsscriptinlines.h"
11 
12 #include "vm/NativeObject-inl.h"
13 #include "vm/Stack-inl.h"
14 
15 using namespace js;
16 
17 JSObject*
create(JSContext * cx,AbstractFramePtr frame)18 GeneratorObject::create(JSContext* cx, AbstractFramePtr frame)
19 {
20     MOZ_ASSERT(frame.script()->isGenerator());
21     MOZ_ASSERT(frame.script()->nfixed() == 0);
22 
23     Rooted<GlobalObject*> global(cx, cx->global());
24     RootedNativeObject obj(cx);
25     if (frame.script()->isStarGenerator()) {
26         RootedValue pval(cx);
27         RootedObject fun(cx, frame.fun());
28         // FIXME: This would be faster if we could avoid doing a lookup to get
29         // the prototype for the instance.  Bug 906600.
30         if (!GetProperty(cx, fun, fun, cx->names().prototype, &pval))
31             return nullptr;
32         RootedObject proto(cx, pval.isObject() ? &pval.toObject() : nullptr);
33         if (!proto) {
34             proto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global);
35             if (!proto)
36                 return nullptr;
37         }
38         obj = NewNativeObjectWithGivenProto(cx, &StarGeneratorObject::class_, proto);
39     } else {
40         MOZ_ASSERT(frame.script()->isLegacyGenerator());
41         RootedObject proto(cx, GlobalObject::getOrCreateLegacyGeneratorObjectPrototype(cx, global));
42         if (!proto)
43             return nullptr;
44         obj = NewNativeObjectWithGivenProto(cx, &LegacyGeneratorObject::class_, proto);
45     }
46     if (!obj)
47         return nullptr;
48 
49     GeneratorObject* genObj = &obj->as<GeneratorObject>();
50     genObj->setCallee(*frame.callee());
51     genObj->setNewTarget(frame.newTarget());
52     genObj->setScopeChain(*frame.scopeChain());
53     if (frame.script()->needsArgsObj())
54         genObj->setArgsObj(frame.argsObj());
55     genObj->clearExpressionStack();
56 
57     return obj;
58 }
59 
60 bool
suspend(JSContext * cx,HandleObject obj,AbstractFramePtr frame,jsbytecode * pc,Value * vp,unsigned nvalues)61 GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc,
62                          Value* vp, unsigned nvalues)
63 {
64     MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD);
65 
66     Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
67     MOZ_ASSERT(!genObj->hasExpressionStack());
68 
69     if (*pc == JSOP_YIELD && genObj->isClosing() && genObj->is<LegacyGeneratorObject>()) {
70         RootedValue val(cx, ObjectValue(*frame.callee()));
71         ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD, JSDVG_IGNORE_STACK, val, nullptr);
72         return false;
73     }
74 
75     uint32_t yieldIndex = GET_UINT24(pc);
76     genObj->setYieldIndex(yieldIndex);
77     genObj->setScopeChain(*frame.scopeChain());
78 
79     if (nvalues) {
80         ArrayObject* stack = NewDenseCopiedArray(cx, nvalues, vp);
81         if (!stack)
82             return false;
83         genObj->setExpressionStack(*stack);
84     }
85 
86     return true;
87 }
88 
89 bool
finalSuspend(JSContext * cx,HandleObject obj)90 GeneratorObject::finalSuspend(JSContext* cx, HandleObject obj)
91 {
92     Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
93     MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
94 
95     bool closing = genObj->isClosing();
96     genObj->setClosed();
97 
98     if (genObj->is<LegacyGeneratorObject>() && !closing)
99         return ThrowStopIteration(cx);
100 
101     return true;
102 }
103 
104 void
SetReturnValueForClosingGenerator(JSContext * cx,AbstractFramePtr frame)105 js::SetReturnValueForClosingGenerator(JSContext* cx, AbstractFramePtr frame)
106 {
107     CallObject& callObj = frame.callObj();
108 
109     // Get the generator object stored on the scope chain and close it.
110     Shape* shape = callObj.lookup(cx, cx->names().dotGenerator);
111     GeneratorObject& genObj = callObj.getSlot(shape->slot()).toObject().as<GeneratorObject>();
112     genObj.setClosed();
113 
114     // Return value is already set in GeneratorThrowOrClose.
115     if (genObj.is<StarGeneratorObject>())
116         return;
117 
118     // Legacy generator .close() always returns |undefined|.
119     MOZ_ASSERT(genObj.is<LegacyGeneratorObject>());
120     frame.setReturnValue(UndefinedValue());
121 }
122 
123 bool
GeneratorThrowOrClose(JSContext * cx,AbstractFramePtr frame,Handle<GeneratorObject * > genObj,HandleValue arg,uint32_t resumeKind)124 js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> genObj,
125                           HandleValue arg, uint32_t resumeKind)
126 {
127     if (resumeKind == GeneratorObject::THROW) {
128         cx->setPendingException(arg);
129         genObj->setRunning();
130     } else {
131         MOZ_ASSERT(resumeKind == GeneratorObject::CLOSE);
132 
133         if (genObj->is<StarGeneratorObject>()) {
134             MOZ_ASSERT(arg.isObject());
135             frame.setReturnValue(arg);
136         } else {
137             MOZ_ASSERT(arg.isUndefined());
138         }
139 
140         cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
141         genObj->setClosing();
142     }
143     return false;
144 }
145 
146 bool
resume(JSContext * cx,InterpreterActivation & activation,HandleObject obj,HandleValue arg,GeneratorObject::ResumeKind resumeKind)147 GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation,
148                         HandleObject obj, HandleValue arg, GeneratorObject::ResumeKind resumeKind)
149 {
150     Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
151     MOZ_ASSERT(genObj->isSuspended());
152 
153     RootedFunction callee(cx, &genObj->callee());
154     RootedValue newTarget(cx, genObj->newTarget());
155     RootedObject scopeChain(cx, &genObj->scopeChain());
156     if (!activation.resumeGeneratorFrame(callee, newTarget, scopeChain))
157         return false;
158     activation.regs().fp()->setResumedGenerator();
159 
160     if (genObj->hasArgsObj())
161         activation.regs().fp()->initArgsObj(genObj->argsObj());
162 
163     if (genObj->hasExpressionStack()) {
164         uint32_t len = genObj->expressionStack().length();
165         MOZ_ASSERT(activation.regs().spForStackDepth(len));
166         const Value* src = genObj->expressionStack().getDenseElements();
167         mozilla::PodCopy(activation.regs().sp, src, len);
168         activation.regs().sp += len;
169         genObj->clearExpressionStack();
170     }
171 
172     JSScript* script = callee->nonLazyScript();
173     uint32_t offset = script->yieldOffsets()[genObj->yieldIndex()];
174     activation.regs().pc = script->offsetToPC(offset);
175 
176     // Always push on a value, even if we are raising an exception. In the
177     // exception case, the stack needs to have something on it so that exception
178     // handling doesn't skip the catch blocks. See TryNoteIter::settle.
179     activation.regs().sp++;
180     MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth()));
181     activation.regs().sp[-1] = arg;
182 
183     switch (resumeKind) {
184       case NEXT:
185         genObj->setRunning();
186         return true;
187 
188       case THROW:
189       case CLOSE:
190         return GeneratorThrowOrClose(cx, activation.regs().fp(), genObj, arg, resumeKind);
191 
192       default:
193         MOZ_CRASH("bad resumeKind");
194     }
195 }
196 
197 bool
close(JSContext * cx,HandleObject obj)198 LegacyGeneratorObject::close(JSContext* cx, HandleObject obj)
199 {
200      Rooted<LegacyGeneratorObject*> genObj(cx, &obj->as<LegacyGeneratorObject>());
201 
202     // Avoid calling back into JS unless it is necessary.
203      if (genObj->isClosed())
204         return true;
205 
206     RootedValue rval(cx);
207 
208     RootedValue closeValue(cx);
209     if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().LegacyGeneratorCloseInternal,
210                                          &closeValue))
211     {
212         return false;
213     }
214     MOZ_ASSERT(closeValue.isObject());
215     MOZ_ASSERT(closeValue.toObject().is<JSFunction>());
216 
217     InvokeArgs args(cx);
218     if (!args.init(cx, 0))
219         return false;
220 
221     args.setCallee(closeValue);
222     args.setThis(ObjectValue(*genObj));
223 
224     return Invoke(cx, args);
225 }
226 
227 const Class LegacyGeneratorObject::class_ = {
228     "Generator",
229     JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS)
230 };
231 
232 const Class StarGeneratorObject::class_ = {
233     "Generator",
234     JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS)
235 };
236 
237 static const JSFunctionSpec star_generator_methods[] = {
238     JS_SELF_HOSTED_FN("next", "StarGeneratorNext", 1, 0),
239     JS_SELF_HOSTED_FN("throw", "StarGeneratorThrow", 1, 0),
240     JS_SELF_HOSTED_FN("return", "StarGeneratorReturn", 1, 0),
241     JS_FS_END
242 };
243 
244 #define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
245 
246 static const JSFunctionSpec legacy_generator_methods[] = {
247     JS_SELF_HOSTED_SYM_FN(iterator, "LegacyGeneratorIteratorShim", 0, 0),
248     // "send" is an alias for "next".
249     JS_SELF_HOSTED_FN("next", "LegacyGeneratorNext", 1, JSPROP_ROPERM),
250     JS_SELF_HOSTED_FN("send", "LegacyGeneratorNext", 1, JSPROP_ROPERM),
251     JS_SELF_HOSTED_FN("throw", "LegacyGeneratorThrow", 1, JSPROP_ROPERM),
252     JS_SELF_HOSTED_FN("close", "LegacyGeneratorClose", 0, JSPROP_ROPERM),
253     JS_FS_END
254 };
255 
256 #undef JSPROP_ROPERM
257 
258 static JSObject*
NewSingletonObjectWithObjectPrototype(JSContext * cx,Handle<GlobalObject * > global)259 NewSingletonObjectWithObjectPrototype(JSContext* cx, Handle<GlobalObject*> global)
260 {
261     RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
262     if (!proto)
263         return nullptr;
264     return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
265 }
266 
267 static JSObject*
NewSingletonObjectWithFunctionPrototype(JSContext * cx,Handle<GlobalObject * > global)268 NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global)
269 {
270     RootedObject proto(cx, global->getOrCreateFunctionPrototype(cx));
271     if (!proto)
272         return nullptr;
273     return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
274 }
275 
276 /* static */ bool
initLegacyGeneratorProto(JSContext * cx,Handle<GlobalObject * > global)277 GlobalObject::initLegacyGeneratorProto(JSContext* cx, Handle<GlobalObject*> global)
278 {
279     if (global->getReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO).isObject())
280         return true;
281 
282     RootedObject proto(cx, NewSingletonObjectWithObjectPrototype(cx, global));
283     if (!proto || !proto->setDelegate(cx))
284         return false;
285     if (!DefinePropertiesAndFunctions(cx, proto, nullptr, legacy_generator_methods))
286         return false;
287 
288     global->setReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO, ObjectValue(*proto));
289     return true;
290 }
291 
292 /* static */ bool
initStarGenerators(JSContext * cx,Handle<GlobalObject * > global)293 GlobalObject::initStarGenerators(JSContext* cx, Handle<GlobalObject*> global)
294 {
295     if (global->getReservedSlot(STAR_GENERATOR_OBJECT_PROTO).isObject())
296         return true;
297 
298     RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
299     if (!iteratorProto)
300         return false;
301 
302     RootedObject genObjectProto(cx, global->createBlankPrototypeInheriting(cx,
303                                                                            &PlainObject::class_,
304                                                                            iteratorProto));
305     if (!genObjectProto)
306         return false;
307     if (!DefinePropertiesAndFunctions(cx, genObjectProto, nullptr, star_generator_methods))
308         return false;
309 
310     RootedObject genFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
311     if (!genFunctionProto || !genFunctionProto->setDelegate(cx))
312         return false;
313     if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto))
314         return false;
315 
316     RootedValue function(cx, global->getConstructor(JSProto_Function));
317     if (!function.toObjectOrNull())
318         return false;
319     RootedObject proto(cx, &function.toObject());
320     RootedAtom name(cx, cx->names().GeneratorFunction);
321     RootedObject genFunction(cx, NewFunctionWithProto(cx, Generator, 1,
322                                                       JSFunction::NATIVE_CTOR, nullptr, name,
323                                                       proto));
324     if (!genFunction)
325         return false;
326     if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto))
327         return false;
328 
329     global->setReservedSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto));
330     global->setReservedSlot(STAR_GENERATOR_FUNCTION, ObjectValue(*genFunction));
331     global->setReservedSlot(STAR_GENERATOR_FUNCTION_PROTO, ObjectValue(*genFunctionProto));
332     return true;
333 }
334