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