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