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