343 lines
12 KiB
C++
343 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "vm/GeneratorObject.h"
|
|
|
|
#include "jsatominlines.h"
|
|
#include "jsscriptinlines.h"
|
|
|
|
#include "vm/NativeObject-inl.h"
|
|
#include "vm/Stack-inl.h"
|
|
|
|
using namespace js;
|
|
|
|
JSObject*
|
|
GeneratorObject::create(JSContext* cx, AbstractFramePtr frame)
|
|
{
|
|
MOZ_ASSERT(frame.script()->isGenerator());
|
|
MOZ_ASSERT(frame.script()->nfixed() == 0);
|
|
|
|
Rooted<GlobalObject*> global(cx, cx->global());
|
|
RootedNativeObject obj(cx);
|
|
if (frame.script()->isStarGenerator()) {
|
|
RootedValue pval(cx);
|
|
RootedObject fun(cx, frame.fun());
|
|
// FIXME: This would be faster if we could avoid doing a lookup to get
|
|
// the prototype for the instance. Bug 906600.
|
|
if (!GetProperty(cx, fun, fun, cx->names().prototype, &pval))
|
|
return nullptr;
|
|
RootedObject proto(cx, pval.isObject() ? &pval.toObject() : nullptr);
|
|
if (!proto) {
|
|
proto = GlobalObject::getOrCreateStarGeneratorObjectPrototype(cx, global);
|
|
if (!proto)
|
|
return nullptr;
|
|
}
|
|
obj = NewNativeObjectWithGivenProto(cx, &StarGeneratorObject::class_, proto);
|
|
} else {
|
|
MOZ_ASSERT(frame.script()->isLegacyGenerator());
|
|
RootedObject proto(cx, GlobalObject::getOrCreateLegacyGeneratorObjectPrototype(cx, global));
|
|
if (!proto)
|
|
return nullptr;
|
|
obj = NewNativeObjectWithGivenProto(cx, &LegacyGeneratorObject::class_, proto);
|
|
}
|
|
if (!obj)
|
|
return nullptr;
|
|
|
|
GeneratorObject* genObj = &obj->as<GeneratorObject>();
|
|
genObj->setCallee(*frame.callee());
|
|
genObj->setNewTarget(frame.newTarget());
|
|
genObj->setScopeChain(*frame.scopeChain());
|
|
if (frame.script()->needsArgsObj())
|
|
genObj->setArgsObj(frame.argsObj());
|
|
genObj->clearExpressionStack();
|
|
|
|
return obj;
|
|
}
|
|
|
|
bool
|
|
GeneratorObject::suspend(JSContext* cx, HandleObject obj, AbstractFramePtr frame, jsbytecode* pc,
|
|
Value* vp, unsigned nvalues)
|
|
{
|
|
MOZ_ASSERT(*pc == JSOP_INITIALYIELD || *pc == JSOP_YIELD);
|
|
|
|
Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
|
|
MOZ_ASSERT(!genObj->hasExpressionStack());
|
|
|
|
if (*pc == JSOP_YIELD && genObj->isClosing() && genObj->is<LegacyGeneratorObject>()) {
|
|
RootedValue val(cx, ObjectValue(*frame.callee()));
|
|
ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD, JSDVG_IGNORE_STACK, val, nullptr);
|
|
return false;
|
|
}
|
|
|
|
uint32_t yieldIndex = GET_UINT24(pc);
|
|
genObj->setYieldIndex(yieldIndex);
|
|
genObj->setScopeChain(*frame.scopeChain());
|
|
|
|
if (nvalues) {
|
|
ArrayObject* stack = NewDenseCopiedArray(cx, nvalues, vp);
|
|
if (!stack)
|
|
return false;
|
|
genObj->setExpressionStack(*stack);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
GeneratorObject::finalSuspend(JSContext* cx, HandleObject obj)
|
|
{
|
|
Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
|
|
MOZ_ASSERT(genObj->isRunning() || genObj->isClosing());
|
|
|
|
bool closing = genObj->isClosing();
|
|
genObj->setClosed();
|
|
|
|
if (genObj->is<LegacyGeneratorObject>() && !closing)
|
|
return ThrowStopIteration(cx);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
js::SetReturnValueForClosingGenerator(JSContext* cx, AbstractFramePtr frame)
|
|
{
|
|
CallObject& callObj = frame.callObj();
|
|
|
|
// Get the generator object stored on the scope chain and close it.
|
|
Shape* shape = callObj.lookup(cx, cx->names().dotGenerator);
|
|
GeneratorObject& genObj = callObj.getSlot(shape->slot()).toObject().as<GeneratorObject>();
|
|
genObj.setClosed();
|
|
|
|
// Return value is already set in GeneratorThrowOrClose.
|
|
if (genObj.is<StarGeneratorObject>())
|
|
return;
|
|
|
|
// Legacy generator .close() always returns |undefined|.
|
|
MOZ_ASSERT(genObj.is<LegacyGeneratorObject>());
|
|
frame.setReturnValue(UndefinedValue());
|
|
}
|
|
|
|
bool
|
|
js::GeneratorThrowOrClose(JSContext* cx, AbstractFramePtr frame, Handle<GeneratorObject*> genObj,
|
|
HandleValue arg, uint32_t resumeKind)
|
|
{
|
|
if (resumeKind == GeneratorObject::THROW) {
|
|
cx->setPendingException(arg);
|
|
genObj->setRunning();
|
|
} else {
|
|
MOZ_ASSERT(resumeKind == GeneratorObject::CLOSE);
|
|
|
|
if (genObj->is<StarGeneratorObject>()) {
|
|
MOZ_ASSERT(arg.isObject());
|
|
frame.setReturnValue(arg);
|
|
} else {
|
|
MOZ_ASSERT(arg.isUndefined());
|
|
}
|
|
|
|
cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING));
|
|
genObj->setClosing();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
GeneratorObject::resume(JSContext* cx, InterpreterActivation& activation,
|
|
HandleObject obj, HandleValue arg, GeneratorObject::ResumeKind resumeKind)
|
|
{
|
|
Rooted<GeneratorObject*> genObj(cx, &obj->as<GeneratorObject>());
|
|
MOZ_ASSERT(genObj->isSuspended());
|
|
|
|
RootedFunction callee(cx, &genObj->callee());
|
|
RootedValue newTarget(cx, genObj->newTarget());
|
|
RootedObject scopeChain(cx, &genObj->scopeChain());
|
|
if (!activation.resumeGeneratorFrame(callee, newTarget, scopeChain))
|
|
return false;
|
|
activation.regs().fp()->setResumedGenerator();
|
|
|
|
if (genObj->hasArgsObj())
|
|
activation.regs().fp()->initArgsObj(genObj->argsObj());
|
|
|
|
if (genObj->hasExpressionStack()) {
|
|
uint32_t len = genObj->expressionStack().length();
|
|
MOZ_ASSERT(activation.regs().spForStackDepth(len));
|
|
const Value* src = genObj->expressionStack().getDenseElements();
|
|
mozilla::PodCopy(activation.regs().sp, src, len);
|
|
activation.regs().sp += len;
|
|
genObj->clearExpressionStack();
|
|
}
|
|
|
|
JSScript* script = callee->nonLazyScript();
|
|
uint32_t offset = script->yieldOffsets()[genObj->yieldIndex()];
|
|
activation.regs().pc = script->offsetToPC(offset);
|
|
|
|
// Always push on a value, even if we are raising an exception. In the
|
|
// exception case, the stack needs to have something on it so that exception
|
|
// handling doesn't skip the catch blocks. See TryNoteIter::settle.
|
|
activation.regs().sp++;
|
|
MOZ_ASSERT(activation.regs().spForStackDepth(activation.regs().stackDepth()));
|
|
activation.regs().sp[-1] = arg;
|
|
|
|
switch (resumeKind) {
|
|
case NEXT:
|
|
genObj->setRunning();
|
|
return true;
|
|
|
|
case THROW:
|
|
case CLOSE:
|
|
return GeneratorThrowOrClose(cx, activation.regs().fp(), genObj, arg, resumeKind);
|
|
|
|
default:
|
|
MOZ_CRASH("bad resumeKind");
|
|
}
|
|
}
|
|
|
|
bool
|
|
LegacyGeneratorObject::close(JSContext* cx, HandleObject obj)
|
|
{
|
|
Rooted<LegacyGeneratorObject*> genObj(cx, &obj->as<LegacyGeneratorObject>());
|
|
|
|
// Avoid calling back into JS unless it is necessary.
|
|
if (genObj->isClosed())
|
|
return true;
|
|
|
|
RootedValue rval(cx);
|
|
|
|
RootedValue closeValue(cx);
|
|
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), cx->names().LegacyGeneratorCloseInternal,
|
|
&closeValue))
|
|
{
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(closeValue.isObject());
|
|
MOZ_ASSERT(closeValue.toObject().is<JSFunction>());
|
|
|
|
InvokeArgs args(cx);
|
|
if (!args.init(cx, 0))
|
|
return false;
|
|
|
|
args.setCallee(closeValue);
|
|
args.setThis(ObjectValue(*genObj));
|
|
|
|
return Invoke(cx, args);
|
|
}
|
|
|
|
const Class LegacyGeneratorObject::class_ = {
|
|
"Generator",
|
|
JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS)
|
|
};
|
|
|
|
const Class StarGeneratorObject::class_ = {
|
|
"Generator",
|
|
JSCLASS_HAS_RESERVED_SLOTS(GeneratorObject::RESERVED_SLOTS)
|
|
};
|
|
|
|
static const JSFunctionSpec star_generator_methods[] = {
|
|
JS_SELF_HOSTED_FN("next", "StarGeneratorNext", 1, 0),
|
|
JS_SELF_HOSTED_FN("throw", "StarGeneratorThrow", 1, 0),
|
|
JS_SELF_HOSTED_FN("return", "StarGeneratorReturn", 1, 0),
|
|
JS_FS_END
|
|
};
|
|
|
|
#define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
|
|
|
|
static const JSFunctionSpec legacy_generator_methods[] = {
|
|
JS_SELF_HOSTED_SYM_FN(iterator, "LegacyGeneratorIteratorShim", 0, 0),
|
|
// "send" is an alias for "next".
|
|
JS_SELF_HOSTED_FN("next", "LegacyGeneratorNext", 1, JSPROP_ROPERM),
|
|
JS_SELF_HOSTED_FN("send", "LegacyGeneratorNext", 1, JSPROP_ROPERM),
|
|
JS_SELF_HOSTED_FN("throw", "LegacyGeneratorThrow", 1, JSPROP_ROPERM),
|
|
JS_SELF_HOSTED_FN("close", "LegacyGeneratorClose", 0, JSPROP_ROPERM),
|
|
JS_FS_END
|
|
};
|
|
|
|
#undef JSPROP_ROPERM
|
|
|
|
static JSObject*
|
|
NewSingletonObjectWithObjectPrototype(JSContext* cx, Handle<GlobalObject*> global)
|
|
{
|
|
RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
|
|
if (!proto)
|
|
return nullptr;
|
|
return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
|
|
}
|
|
|
|
static JSObject*
|
|
NewSingletonObjectWithFunctionPrototype(JSContext* cx, Handle<GlobalObject*> global)
|
|
{
|
|
RootedObject proto(cx, global->getOrCreateFunctionPrototype(cx));
|
|
if (!proto)
|
|
return nullptr;
|
|
return NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject);
|
|
}
|
|
|
|
/* static */ bool
|
|
GlobalObject::initLegacyGeneratorProto(JSContext* cx, Handle<GlobalObject*> global)
|
|
{
|
|
if (global->getReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO).isObject())
|
|
return true;
|
|
|
|
RootedObject proto(cx, NewSingletonObjectWithObjectPrototype(cx, global));
|
|
if (!proto || !proto->setDelegate(cx))
|
|
return false;
|
|
#if(0)
|
|
/* XXX: Stub @@toStringTag for legacy generators.
|
|
TenFourFox issue 392 */
|
|
if (!DefinePropertiesAndFunctions(cx, proto, nullptr, legacy_generator_methods) ||
|
|
!DefineToStringTag(cx, proto, cx->names().LegacyGenerator))
|
|
#else
|
|
if (!DefinePropertiesAndFunctions(cx, proto, nullptr, legacy_generator_methods))
|
|
#endif
|
|
return false;
|
|
|
|
global->setReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO, ObjectValue(*proto));
|
|
return true;
|
|
}
|
|
|
|
/* static */ bool
|
|
GlobalObject::initStarGenerators(JSContext* cx, Handle<GlobalObject*> global)
|
|
{
|
|
if (global->getReservedSlot(STAR_GENERATOR_OBJECT_PROTO).isObject())
|
|
return true;
|
|
|
|
RootedObject iteratorProto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, global));
|
|
if (!iteratorProto)
|
|
return false;
|
|
|
|
RootedObject genObjectProto(cx, global->createBlankPrototypeInheriting(cx,
|
|
&PlainObject::class_,
|
|
iteratorProto));
|
|
if (!genObjectProto)
|
|
return false;
|
|
if (!DefinePropertiesAndFunctions(cx, genObjectProto, nullptr, star_generator_methods) ||
|
|
!DefineToStringTag(cx, genObjectProto, cx->names().Generator))
|
|
return false;
|
|
|
|
RootedObject genFunctionProto(cx, NewSingletonObjectWithFunctionPrototype(cx, global));
|
|
if (!genFunctionProto || !genFunctionProto->setDelegate(cx))
|
|
return false;
|
|
if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto) ||
|
|
!DefineToStringTag(cx, genFunctionProto, cx->names().GeneratorFunction))
|
|
return false;
|
|
|
|
RootedValue function(cx, global->getConstructor(JSProto_Function));
|
|
if (!function.toObjectOrNull())
|
|
return false;
|
|
RootedObject proto(cx, &function.toObject());
|
|
RootedAtom name(cx, cx->names().GeneratorFunction);
|
|
RootedObject genFunction(cx, NewFunctionWithProto(cx, Generator, 1,
|
|
JSFunction::NATIVE_CTOR, nullptr, name,
|
|
proto));
|
|
if (!genFunction)
|
|
return false;
|
|
if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto))
|
|
return false;
|
|
|
|
global->setReservedSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto));
|
|
global->setReservedSlot(STAR_GENERATOR_FUNCTION, ObjectValue(*genFunction));
|
|
global->setReservedSlot(STAR_GENERATOR_FUNCTION_PROTO, ObjectValue(*genFunctionProto));
|
|
return true;
|
|
}
|