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