Bug 904701 - Implement prototype madness for ES6 generators. r=bhackett, r=jorendorff

Add a Generator constructor, like the Function constructor.

Define the GeneratorFunctionPrototype, GeneratorFunction, and
GeneratorObjectPrototype meta-objects.

When cloning or creating a star generator, give it
GeneratorFunctionPrototype as its prototype.

Each star generator function has a ".prototype" property, which has
GeneratorObjectPrototype as its prototype.  That prototype does not have
a ".constructor" link.

If Function.prototype.toSource is called on a non-function, chain up to
Object.prototype.toSource instead.

Prototypes of generator objects are no longer made with
GeneratorObject::class_.  This allows us to elide some "null" checks
from bug 352885.  Instead calling a generator on a method now signals a
typeerror.

Make the "send" generator method a simple alias to "next".
This commit is contained in:
Andy Wingo 2013-08-23 11:07:10 -04:00
parent 7549ab3ad1
commit bb8edee39e
15 changed files with 490 additions and 156 deletions

View File

@ -87,8 +87,8 @@ obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp)
}
#if JS_HAS_TOSOURCE
static bool
obj_toSource(JSContext *cx, unsigned argc, Value *vp)
bool
js::obj_toSource(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_CHECK_RECURSION(cx, return false);

View File

@ -14,10 +14,14 @@ namespace js {
extern const JSFunctionSpec object_methods[];
extern const JSFunctionSpec object_static_methods[];
/* Object constructor native. Exposed only so the JIT can know its address. */
// Object constructor native. Exposed only so the JIT can know its address.
extern bool
obj_construct(JSContext *cx, unsigned argc, js::Value *vp);
// Object.prototype.toSource. Exposed so that Function.prototype.toSource can chain up.
extern bool
obj_toSource(JSContext *cx, unsigned argc, js::Value *vp);
} /* namespace js */
#endif /* builtin_Object_h */

View File

@ -451,9 +451,10 @@ frontend::CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *cha
// Compile a JS function body, which might appear as the value of an event
// handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
bool
frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
const AutoNameVector &formals, const jschar *chars, size_t length)
static bool
CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
const AutoNameVector &formals, const jschar *chars, size_t length,
GeneratorKind generatorKind)
{
#if JS_TRACE_LOGGING
js::AutoTraceLog logger(js::TraceLogging::defaultLogger(),
@ -520,7 +521,7 @@ frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileO
ParseNode *fn;
while (true) {
Directives newDirectives = directives;
fn = parser.standaloneFunctionBody(fun, formals, NotGenerator, directives, &newDirectives);
fn = parser.standaloneFunctionBody(fun, formals, generatorKind, directives, &newDirectives);
if (fn)
break;
@ -585,3 +586,18 @@ frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileO
return true;
}
bool
frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
const AutoNameVector &formals, const jschar *chars, size_t length)
{
return CompileFunctionBody(cx, fun, options, formals, chars, length, NotGenerator);
}
bool
frontend::CompileStarGeneratorBody(JSContext *cx, MutableHandleFunction fun,
CompileOptions options, const AutoNameVector &formals,
const jschar *chars, size_t length)
{
return CompileFunctionBody(cx, fun, options, formals, chars, length, StarGenerator);
}

View File

@ -33,6 +33,9 @@ CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *chars, size_t
bool
CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
const AutoNameVector &formals, const jschar *chars, size_t length);
bool
CompileStarGeneratorBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
const AutoNameVector &formals, const jschar *chars, size_t length);
/*
* This should be called while still on the main thread if compilation will

View File

@ -1244,7 +1244,7 @@ struct BindData
template <typename ParseHandler>
JSFunction *
Parser<ParseHandler>::newFunction(GenericParseContext *pc, HandleAtom atom,
FunctionSyntaxKind kind)
FunctionSyntaxKind kind, JSObject *proto)
{
JS_ASSERT_IF(kind == Statement, atom != NULL);
@ -1267,8 +1267,8 @@ Parser<ParseHandler>::newFunction(GenericParseContext *pc, HandleAtom atom,
: (kind == Arrow)
? JSFunction::INTERPRETED_LAMBDA_ARROW
: JSFunction::INTERPRETED;
fun = NewFunction(context, NullPtr(), NULL, 0, flags, parent, atom,
JSFunction::FinalizeKind, MaybeSingletonObject);
fun = NewFunctionWithProto(context, NullPtr(), NULL, 0, flags, parent, atom, proto,
JSFunction::FinalizeKind, MaybeSingletonObject);
if (!fun)
return NULL;
if (options().selfHostingMode)
@ -1999,7 +1999,17 @@ Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream:
if (bodyProcessed)
return pn;
RootedFunction fun(context, newFunction(pc, funName, kind));
RootedObject proto(context);
if (generatorKind == StarGenerator) {
// If we are off the main thread, the generator meta-objects have
// already been created by js::StartOffThreadParseScript, so cx will not
// be necessary.
JSContext *cx = context->maybeJSContext();
proto = context->global()->getOrCreateStarGeneratorFunctionPrototype(cx);
if (!proto)
return null();
}
RootedFunction fun(context, newFunction(pc, funName, kind, proto));
if (!fun)
return null();

View File

@ -407,7 +407,8 @@ class Parser : private AutoGCRooter, public StrictModeGetter
* Create a new function object given parse context (pc) and a name (which
* is optional if this is a function expression).
*/
JSFunction *newFunction(GenericParseContext *pc, HandleAtom atom, FunctionSyntaxKind kind);
JSFunction *newFunction(GenericParseContext *pc, HandleAtom atom, FunctionSyntaxKind kind,
JSObject *proto = NULL);
void trace(JSTracer *trc);

View File

@ -2904,7 +2904,7 @@ struct JSClass {
* with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
* prevously allowed, but is now an ES5 violation and thus unsupported.
*/
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 25)
#define JSCLASS_GLOBAL_SLOT_COUNT (JSProto_LIMIT * 3 + 26)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \

View File

@ -27,6 +27,7 @@
#include "jswrapper.h"
#include "builtin/Eval.h"
#include "builtin/Object.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/TokenStream.h"
#include "gc/Marking.h"
@ -195,40 +196,51 @@ ResolveInterpretedFunctionPrototype(JSContext *cx, HandleObject obj)
JS_ASSERT(!fun->isFunctionPrototype());
#endif
/*
* Assert that fun is not a compiler-created function object, which
* must never leak to script or embedding code and then be mutated.
* Also assert that obj is not bound, per the ES5 15.3.4.5 ref above.
*/
// Assert that fun is not a compiler-created function object, which
// must never leak to script or embedding code and then be mutated.
// Also assert that obj is not bound, per the ES5 15.3.4.5 ref above.
JS_ASSERT(!IsInternalFunctionObject(obj));
JS_ASSERT(!obj->isBoundFunction());
/*
* Make the prototype object an instance of Object with the same parent
* as the function object itself.
*/
JSObject *objProto = obj->global().getOrCreateObjectPrototype(cx);
// Make the prototype object an instance of Object with the same parent as
// the function object itself, unless the function is an ES6 generator. In
// that case, per the 15 July 2013 ES6 draft, section 15.19.3, its parent is
// the GeneratorObjectPrototype singleton.
bool isStarGenerator = obj->as<JSFunction>().isStarGenerator();
JSObject *objProto;
if (isStarGenerator)
objProto = obj->global().getOrCreateStarGeneratorObjectPrototype(cx);
else
objProto = obj->global().getOrCreateObjectPrototype(cx);
if (!objProto)
return NULL;
RootedObject proto(cx, NewObjectWithGivenProto(cx, &JSObject::class_, objProto, NULL, SingletonObject));
Class *clasp = &JSObject::class_;
RootedObject proto(cx, NewObjectWithGivenProto(cx, clasp, objProto, NULL, SingletonObject));
if (!proto)
return NULL;
/*
* Per ES5 15.3.5.2 a user-defined function's .prototype property is
* initially non-configurable, non-enumerable, and writable. Per ES5 13.2
* the prototype's .constructor property is configurable, non-enumerable,
* and writable.
*/
// Per ES5 15.3.5.2 a user-defined function's .prototype property is
// initially non-configurable, non-enumerable, and writable.
RootedValue protoVal(cx, ObjectValue(*proto));
RootedValue objVal(cx, ObjectValue(*obj));
if (!JSObject::defineProperty(cx, obj, cx->names().classPrototype,
protoVal, JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_PERMANENT) ||
!JSObject::defineProperty(cx, proto, cx->names().constructor,
objVal, JS_PropertyStub, JS_StrictPropertyStub, 0))
JSPROP_PERMANENT))
{
return NULL;
return NULL;
}
// Per ES5 13.2 the prototype's .constructor property is configurable,
// non-enumerable, and writable. However, per the 15 July 2013 ES6 draft,
// section 15.19.3, the .prototype of a generator function does not link
// back with a .constructor.
if (!isStarGenerator) {
RootedValue objVal(cx, ObjectValue(*obj));
if (!JSObject::defineProperty(cx, proto, cx->names().constructor,
objVal, JS_PropertyStub, JS_StrictPropertyStub, 0))
{
return NULL;
}
}
return proto;
@ -327,12 +339,15 @@ bool
js::XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
MutableHandleObject objp)
{
enum FirstWordFlag {
HasAtom = 0x1,
IsStarGenerator = 0x2
};
/* NB: Keep this in sync with CloneFunctionAndScript. */
RootedAtom atom(xdr->cx());
uint32_t firstword; /* flag telling whether fun->atom is non-null,
plus for fun->u.i.skipmin, fun->u.i.wrapper,
and 14 bits reserved for future use */
uint32_t flagsword; /* word for argument count and fun->flags */
uint32_t firstword = 0; /* bitmask of FirstWordFlag */
uint32_t flagsword = 0; /* word for argument count and fun->flags */
JSContext *cx = xdr->cx();
RootedFunction fun(cx);
@ -347,24 +362,38 @@ js::XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope, Han
}
return false;
}
firstword = !!fun->atom();
if (fun->atom())
firstword |= HasAtom;
if (fun->isStarGenerator())
firstword |= IsStarGenerator;
script = fun->getOrCreateScript(cx);
if (!script)
return false;
atom = fun->atom();
flagsword = (fun->nargs << 16) | fun->flags;
if (!xdr->codeUint32(&firstword))
return false;
} else {
fun = NewFunction(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED, NullPtr(), NullPtr(),
JSFunction::FinalizeKind, TenuredObject);
if (!xdr->codeUint32(&firstword))
return false;
JSObject *proto = NULL;
if (firstword & IsStarGenerator) {
proto = cx->global()->getOrCreateStarGeneratorFunctionPrototype(cx);
if (!proto)
return false;
}
fun = NewFunctionWithProto(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED,
NullPtr(), NullPtr(), proto,
JSFunction::FinalizeKind, TenuredObject);
if (!fun)
return false;
atom = NULL;
script = NULL;
}
if (!xdr->codeUint32(&firstword))
return false;
if ((firstword & 1U) && !XDRAtom(xdr, &atom))
if ((firstword & HasAtom) && !XDRAtom(xdr, &atom))
return false;
if (!xdr->codeUint32(&flagsword))
return false;
@ -399,10 +428,15 @@ JSObject *
js::CloneFunctionAndScript(JSContext *cx, HandleObject enclosingScope, HandleFunction srcFun)
{
/* NB: Keep this in sync with XDRInterpretedFunction. */
RootedFunction clone(cx, NewFunction(cx, NullPtr(), NULL, 0,
JSFunction::INTERPRETED, NullPtr(), NullPtr(),
JSFunction::FinalizeKind, TenuredObject));
JSObject *cloneProto = NULL;
if (srcFun->isStarGenerator()) {
cloneProto = cx->global()->getOrCreateStarGeneratorFunctionPrototype(cx);
if (!cloneProto)
return NULL;
}
RootedFunction clone(cx, NewFunctionWithProto(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED,
NullPtr(), NullPtr(), cloneProto,
JSFunction::FinalizeKind, TenuredObject));
if (!clone)
return NULL;
@ -605,7 +639,7 @@ js::FunctionToString(JSContext *cx, HandleFunction fun, bool bodyOnly, bool lamb
return NULL;
}
if (!fun->isArrow()) {
if (!out.append("function "))
if (!(fun->isStarGenerator() ? out.append("function* ") : out.append("function ")))
return NULL;
}
if (fun->atom()) {
@ -795,6 +829,9 @@ fun_toSource(JSContext *cx, unsigned argc, Value *vp)
if (!obj)
return false;
if (!obj->is<JSFunction>() && !obj->is<FunctionProxyObject>())
return obj_toSource(cx, argc, vp);
RootedString str(cx, fun_toStringHelper(cx, obj, JS_DONT_PRETTY_PRINT));
if (!str)
return false;
@ -1327,8 +1364,8 @@ const JSFunctionSpec js::function_methods[] = {
JS_FS_END
};
bool
js::Function(JSContext *cx, unsigned argc, Value *vp)
static bool
FunctionConstructor(JSContext *cx, unsigned argc, Value *vp, GeneratorKind generatorKind)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedString arg(cx); // used multiple times below
@ -1345,6 +1382,9 @@ js::Function(JSContext *cx, unsigned argc, Value *vp)
bool hasRest = false;
bool isStarGenerator = generatorKind == StarGenerator;
JS_ASSERT(generatorKind != LegacyGenerator);
const char *filename;
unsigned lineno;
JSPrincipals *originPrincipals;
@ -1437,6 +1477,7 @@ js::Function(JSContext *cx, unsigned argc, Value *vp)
*/
TokenStream ts(cx, options, collected_args.get(), args_length,
/* strictModeGetter = */ NULL);
bool yieldIsValidName = ts.versionNumber() < JSVERSION_1_7 && !isStarGenerator;
/* The argument string may be empty or contain no tokens. */
TokenKind tt = ts.getToken();
@ -1451,14 +1492,14 @@ js::Function(JSContext *cx, unsigned argc, Value *vp)
return false;
}
if (tt == TOK_YIELD && ts.versionNumber() < JSVERSION_1_7)
if (tt == TOK_YIELD && yieldIsValidName)
tt = TOK_NAME;
if (tt != TOK_NAME) {
if (tt == TOK_TRIPLEDOT) {
hasRest = true;
tt = ts.getToken();
if (tt == TOK_YIELD && ts.versionNumber() < JSVERSION_1_7)
if (tt == TOK_YIELD && yieldIsValidName)
tt = TOK_NAME;
if (tt != TOK_NAME) {
if (tt != TOK_ERROR)
@ -1519,24 +1560,47 @@ js::Function(JSContext *cx, unsigned argc, Value *vp)
* and so would a call to f from another top-level's script or function.
*/
RootedAtom anonymousAtom(cx, cx->names().anonymous);
RootedFunction fun(cx, NewFunction(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED_LAMBDA,
global, anonymousAtom, JSFunction::FinalizeKind,
TenuredObject));
JSObject *proto = NULL;
if (isStarGenerator) {
proto = global->getOrCreateStarGeneratorFunctionPrototype(cx);
if (!proto)
return false;
}
RootedFunction fun(cx, NewFunctionWithProto(cx, NullPtr(), NULL, 0,
JSFunction::INTERPRETED_LAMBDA, global,
anonymousAtom, proto,
JSFunction::FinalizeKind, TenuredObject));
if (!fun)
return false;
if (hasRest)
fun->setHasRest();
bool ok = frontend::CompileFunctionBody(cx, &fun, options, formals, chars, length);
bool ok;
if (isStarGenerator)
ok = frontend::CompileStarGeneratorBody(cx, &fun, options, formals, chars, length);
else
ok = frontend::CompileFunctionBody(cx, &fun, options, formals, chars, length);
args.rval().setObject(*fun);
return ok;
}
bool
js::Function(JSContext *cx, unsigned argc, Value *vp)
{
return FunctionConstructor(cx, argc, vp, NotGenerator);
}
bool
js::Generator(JSContext *cx, unsigned argc, Value *vp)
{
return FunctionConstructor(cx, argc, vp, StarGenerator);
}
bool
JSFunction::isBuiltinFunctionConstructor()
{
return maybeNative() == Function;
return maybeNative() == Function || maybeNative() == Generator;
}
JSFunction *
@ -1544,6 +1608,17 @@ js::NewFunction(ExclusiveContext *cx, HandleObject funobjArg, Native native, uns
JSFunction::Flags flags, HandleObject parent, HandleAtom atom,
gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
NewObjectKind newKind /* = GenericObject */)
{
return NewFunctionWithProto(cx, funobjArg, native, nargs, flags, parent, atom, NULL,
allocKind, newKind);
}
JSFunction *
js::NewFunctionWithProto(ExclusiveContext *cx, HandleObject funobjArg, Native native,
unsigned nargs, JSFunction::Flags flags, HandleObject parent,
HandleAtom atom, JSObject *proto,
gc::AllocKind allocKind /* = JSFunction::FinalizeKind */,
NewObjectKind newKind /* = GenericObject */)
{
JS_ASSERT(allocKind == JSFunction::FinalizeKind || allocKind == JSFunction::ExtendedFinalizeKind);
JS_ASSERT(sizeof(JSFunction) <= gc::Arena::thingSize(JSFunction::FinalizeKind));
@ -1560,7 +1635,7 @@ js::NewFunction(ExclusiveContext *cx, HandleObject funobjArg, Native native, uns
// that hasSingletonType implies isInterpreted.
if (native && !IsAsmJSModuleNative(native))
newKind = SingletonObject;
funobj = NewObjectWithClassProto(cx, &JSFunction::class_, NULL,
funobj = NewObjectWithClassProto(cx, &JSFunction::class_, proto,
SkipScopeParent(parent), allocKind, newKind);
if (!funobj)
return NULL;
@ -1603,7 +1678,13 @@ js::CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
return NULL;
NewObjectKind newKind = useSameScript ? newKindArg : SingletonObject;
JSObject *cloneobj = NewObjectWithClassProto(cx, &JSFunction::class_, NULL,
JSObject *cloneProto = NULL;
if (fun->isStarGenerator()) {
cloneProto = cx->global()->getOrCreateStarGeneratorFunctionPrototype(cx);
if (!cloneProto)
return NULL;
}
JSObject *cloneobj = NewObjectWithClassProto(cx, &JSFunction::class_, cloneProto,
SkipScopeParent(parent), allocKind, newKind);
if (!cloneobj)
return NULL;

View File

@ -410,12 +410,22 @@ namespace js {
extern bool
Function(JSContext *cx, unsigned argc, Value *vp);
extern bool
Generator(JSContext *cx, unsigned argc, Value *vp);
extern JSFunction *
NewFunction(ExclusiveContext *cx, HandleObject funobj, JSNative native, unsigned nargs,
JSFunction::Flags flags, HandleObject parent, HandleAtom atom,
gc::AllocKind allocKind = JSFunction::FinalizeKind,
NewObjectKind newKind = GenericObject);
// If proto is NULL, Function.prototype is used instead.
extern JSFunction *
NewFunctionWithProto(ExclusiveContext *cx, HandleObject funobj, JSNative native, unsigned nargs,
JSFunction::Flags flags, HandleObject parent, HandleAtom atom,
JSObject *proto, gc::AllocKind allocKind = JSFunction::FinalizeKind,
NewObjectKind newKind = GenericObject);
extern JSFunction *
DefineFunction(JSContext *cx, HandleObject obj, HandleId id, JSNative native,
unsigned nargs, unsigned flags,

View File

@ -958,7 +958,7 @@ const JSFunctionSpec ElementIteratorObject::methods[] = {
};
static bool
CloseGenerator(JSContext *cx, HandleObject genobj);
CloseLegacyGenerator(JSContext *cx, HandleObject genobj);
bool
js::ValueToIterator(JSContext *cx, unsigned flags, MutableHandleValue vp)
@ -999,6 +999,30 @@ js::ValueToIterator(JSContext *cx, unsigned flags, MutableHandleValue vp)
return GetIterator(cx, obj, flags, vp);
}
bool
IsGenerator(HandleValue v)
{
return v.isObject() && v.toObject().is<GeneratorObject>();
}
static bool
IsLegacyGenerator(HandleObject obj)
{
if (!obj->is<GeneratorObject>())
return false;
JSGenerator *gen = obj->as<GeneratorObject>().getGenerator();
return gen->regs.fp()->script()->isLegacyGenerator();
}
bool
IsLegacyGenerator(HandleValue v)
{
if (!IsGenerator(v))
return false;
JSGenerator *gen = v.toObject().as<GeneratorObject>().getGenerator();
return gen->regs.fp()->script()->isLegacyGenerator();
}
bool
js::CloseIterator(JSContext *cx, HandleObject obj)
{
@ -1020,9 +1044,8 @@ js::CloseIterator(JSContext *cx, HandleObject obj)
*/
ni->props_cursor = ni->props_array;
}
} else if (obj->is<GeneratorObject>()) {
// FIXME: Only close legacy generators.
return CloseGenerator(cx, obj);
} else if (IsLegacyGenerator(obj)) {
return CloseLegacyGenerator(cx, obj);
}
return true;
}
@ -1477,15 +1500,25 @@ js_NewGenerator(JSContext *cx, const FrameRegs &stackRegs)
JS_ASSERT(stackfp->script()->isGenerator());
if (stackfp->script()->isStarGenerator()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ES6_UNIMPLEMENTED);
return NULL;
}
Rooted<GlobalObject*> global(cx, &stackfp->global());
RootedObject obj(cx);
{
JSObject *proto = global->getOrCreateGeneratorPrototype(cx);
if (stackfp->script()->isStarGenerator()) {
RootedValue pval(cx);
RootedObject fun(cx, stackfp->fun());
// FIXME: This would be faster if we could avoid doing a lookup to get
// the prototype for the instance. Bug 906600.
if (!JSObject::getProperty(cx, fun, fun, cx->names().classPrototype, &pval))
return NULL;
JSObject *proto = pval.isObject() ? &pval.toObject() : NULL;
if (!proto) {
proto = global->getOrCreateStarGeneratorObjectPrototype(cx);
if (!proto)
return NULL;
}
obj = NewObjectWithGivenProto(cx, &GeneratorObject::class_, proto, global);
} else {
JS_ASSERT(stackfp->script()->isLegacyGenerator());
JSObject *proto = global->getOrCreateLegacyGeneratorObjectPrototype(cx);
if (!proto)
return NULL;
obj = NewObjectWithGivenProto(cx, &GeneratorObject::class_, proto, global);
@ -1623,66 +1656,17 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, HandleObject obj,
}
static bool
CloseGenerator(JSContext *cx, HandleObject obj)
CloseLegacyGenerator(JSContext *cx, HandleObject obj)
{
JS_ASSERT(IsLegacyGenerator(obj));
JSGenerator *gen = obj->as<GeneratorObject>().getGenerator();
if (!gen) {
/* Generator prototype object. */
return true;
}
// FIXME: Assert that gen is a legacy generator.
if (gen->state == JSGEN_CLOSED)
return true;
return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JS::UndefinedHandleValue);
}
JS_ALWAYS_INLINE bool
IsGenerator(HandleValue v)
{
return v.isObject() && v.toObject().is<GeneratorObject>();
}
JS_ALWAYS_INLINE bool
generator_send_impl(JSContext *cx, CallArgs args)
{
// FIXME: Change assertion to IsLegacyGenerator().
JS_ASSERT(IsGenerator(args.thisv()));
RootedObject thisObj(cx, &args.thisv().toObject());
JSGenerator *gen = thisObj->as<GeneratorObject>().getGenerator();
if (!gen || gen->state == JSGEN_CLOSED) {
/* This happens when obj is the generator prototype. See bug 352885. */
return js_ThrowStopIteration(cx);
}
if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) {
RootedValue val(cx, args[0]);
js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
JSDVG_SEARCH_STACK, val, NullPtr());
return false;
}
// FIXME: next() takes the send value as an optional argument in ES6
// generator objects.
if (!SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0)))
return false;
args.rval().set(gen->fp->returnValue());
return true;
}
bool
generator_send(JSContext *cx, unsigned argc, Value *vp)
{
// FIXME: send() is only a method on legacy generator objects.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsGenerator, generator_send_impl>(cx, args);
}
JS_ALWAYS_INLINE bool
generator_next_impl(JSContext *cx, CallArgs args)
{
@ -1691,12 +1675,17 @@ generator_next_impl(JSContext *cx, CallArgs args)
RootedObject thisObj(cx, &args.thisv().toObject());
JSGenerator *gen = thisObj->as<GeneratorObject>().getGenerator();
if (!gen || gen->state == JSGEN_CLOSED) {
/* This happens when obj is the generator prototype. See bug 352885. */
if (gen->state == JSGEN_CLOSED)
return js_ThrowStopIteration(cx);
if (gen->state == JSGEN_NEWBORN && args.hasDefined(0)) {
RootedValue val(cx, args[0]);
js_ReportValueError(cx, JSMSG_BAD_GENERATOR_SEND,
JSDVG_SEARCH_STACK, val, NullPtr());
return false;
}
if (!SendToGenerator(cx, JSGENOP_NEXT, thisObj, gen, JS::UndefinedHandleValue))
if (!SendToGenerator(cx, JSGENOP_SEND, thisObj, gen, args.get(0)))
return false;
args.rval().set(gen->fp->returnValue());
@ -1718,8 +1707,7 @@ generator_throw_impl(JSContext *cx, CallArgs args)
RootedObject thisObj(cx, &args.thisv().toObject());
JSGenerator *gen = thisObj->as<GeneratorObject>().getGenerator();
if (!gen || gen->state == JSGEN_CLOSED) {
/* This happens when obj is the generator prototype. See bug 352885. */
if (gen->state == JSGEN_CLOSED) {
cx->setPendingException(args.length() >= 1 ? args[0] : UndefinedValue());
return false;
}
@ -1741,14 +1729,12 @@ generator_throw(JSContext *cx, unsigned argc, Value *vp)
JS_ALWAYS_INLINE bool
generator_close_impl(JSContext *cx, CallArgs args)
{
// FIXME: Change assertion to IsLegacyGenerator().
JS_ASSERT(IsGenerator(args.thisv()));
JS_ASSERT(IsLegacyGenerator(args.thisv()));
RootedObject thisObj(cx, &args.thisv().toObject());
JSGenerator *gen = thisObj->as<GeneratorObject>().getGenerator();
if (!gen || gen->state == JSGEN_CLOSED) {
/* This happens when obj is the generator prototype. See bug 352885. */
if (gen->state == JSGEN_CLOSED) {
args.rval().setUndefined();
return true;
}
@ -1769,22 +1755,47 @@ generator_close_impl(JSContext *cx, CallArgs args)
bool
generator_close(JSContext *cx, unsigned argc, Value *vp)
{
// FIXME: close() is only a method on legacy generator objects.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsGenerator, generator_close_impl>(cx, args);
return CallNonGenericMethod<IsLegacyGenerator, generator_close_impl>(cx, args);
}
#define JSPROP_ROPERM (JSPROP_READONLY | JSPROP_PERMANENT)
static const JSFunctionSpec generator_methods[] = {
static const JSFunctionSpec legacy_generator_methods[] = {
JS_FN("iterator", iterator_iterator, 0, 0),
JS_FN("next", generator_next, 0,JSPROP_ROPERM),
JS_FN("send", generator_send, 1,JSPROP_ROPERM),
JS_FN("next", generator_next, 1,JSPROP_ROPERM),
// Send is exactly the same as next.
JS_FN("send", generator_next, 1,JSPROP_ROPERM),
JS_FN("throw", generator_throw, 1,JSPROP_ROPERM),
JS_FN("close", generator_close, 0,JSPROP_ROPERM),
JS_FS_END
};
static const JSFunctionSpec star_generator_methods[] = {
JS_FN("iterator", iterator_iterator, 0, 0),
JS_FN("next", generator_next, 1,JSPROP_ROPERM),
JS_FN("throw", generator_throw, 1,JSPROP_ROPERM),
JS_FS_END
};
static JSObject*
NewObjectWithObjectPrototype(JSContext *cx, Handle<GlobalObject *> global)
{
JSObject *proto = global->getOrCreateObjectPrototype(cx);
if (!proto)
return NULL;
return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global);
}
static JSObject*
NewObjectWithFunctionPrototype(JSContext *cx, Handle<GlobalObject *> global)
{
JSObject *proto = global->getOrCreateFunctionPrototype(cx);
if (!proto)
return NULL;
return NewObjectWithGivenProto(cx, &JSObject::class_, proto, global);
}
/* static */ bool
GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global)
{
@ -1826,11 +1837,41 @@ GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global)
global->setReservedSlot(ELEMENT_ITERATOR_PROTO, ObjectValue(*proto));
}
if (global->getSlot(GENERATOR_PROTO).isUndefined()) {
proto = global->createBlankPrototype(cx, &GeneratorObject::class_);
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
if (global->getSlot(LEGACY_GENERATOR_OBJECT_PROTO).isUndefined()) {
proto = NewObjectWithObjectPrototype(cx, global);
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, legacy_generator_methods))
return false;
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
global->setReservedSlot(LEGACY_GENERATOR_OBJECT_PROTO, ObjectValue(*proto));
}
if (global->getSlot(STAR_GENERATOR_OBJECT_PROTO).isUndefined()) {
RootedObject genObjectProto(cx, NewObjectWithObjectPrototype(cx, global));
if (!genObjectProto)
return false;
if (!DefinePropertiesAndBrand(cx, genObjectProto, NULL, star_generator_methods))
return false;
RootedObject genFunctionProto(cx, NewObjectWithFunctionPrototype(cx, global));
if (!genFunctionProto)
return false;
if (!LinkConstructorAndPrototype(cx, genFunctionProto, genObjectProto))
return false;
RootedValue function(cx, global->getConstructor(JSProto_Function));
if (!function.toObjectOrNull())
return false;
RootedAtom name(cx, cx->names().GeneratorFunction);
RootedObject genFunction(cx, NewFunctionWithProto(cx, NullPtr(), Generator, 1,
JSFunction::NATIVE_CTOR, global, name,
&function.toObject()));
if (!genFunction)
return false;
if (!LinkConstructorAndPrototype(cx, genFunction, genFunctionProto))
return false;
global->setSlot(STAR_GENERATOR_OBJECT_PROTO, ObjectValue(*genObjectProto));
global->setSlot(JSProto_GeneratorFunction, ObjectValue(*genFunction));
global->setSlot(JSProto_GeneratorFunction + JSProto_LIMIT, ObjectValue(*genFunctionProto));
}
if (global->getPrototype(JSProto_StopIteration).isUndefined()) {

View File

@ -69,5 +69,6 @@
macro(ArrayType, 50, js_InitBinaryDataClasses) \
macro(StructType, 51, js_InitBinaryDataClasses) \
macro(ArrayTypeObject, 52, js_InitBinaryDataClasses) \
macro(GeneratorFunction, 53, js_InitIteratorClasses) \
#endif /* jsprototypes_h */

View File

@ -236,7 +236,8 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
// pointers can be changed infallibly after parsing finishes.
if (!js_GetClassObject(cx, cx->global(), JSProto_Function, &obj) ||
!js_GetClassObject(cx, cx->global(), JSProto_Array, &obj) ||
!js_GetClassObject(cx, cx->global(), JSProto_RegExp, &obj))
!js_GetClassObject(cx, cx->global(), JSProto_RegExp, &obj) ||
!js_GetClassObject(cx, cx->global(), JSProto_GeneratorFunction, &obj))
{
return false;
}
@ -244,7 +245,8 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
AutoCompartment ac(cx, global);
if (!js_GetClassObject(cx, global, JSProto_Function, &obj) ||
!js_GetClassObject(cx, global, JSProto_Array, &obj) ||
!js_GetClassObject(cx, global, JSProto_RegExp, &obj))
!js_GetClassObject(cx, global, JSProto_RegExp, &obj) ||
!js_GetClassObject(cx, global, JSProto_GeneratorFunction, &obj))
{
return false;
}

View File

@ -0,0 +1,150 @@
// This file was written by Andy Wingo <wingo@igalia.com> and originally
// contributed to V8 as generators-runtime.js, available here:
//
// http://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/harmony/generators-runtime.js
// Test aspects of the generator runtime.
// See http://people.mozilla.org/~jorendorff/es6-draft.html#sec-15.19.3.
function assertSyntaxError(str) {
var msg;
var evil = eval;
try {
// Non-direct eval.
evil(str);
} catch (exc) {
if (exc instanceof SyntaxError)
return;
msg = "Assertion failed: expected SyntaxError, got " + exc;
}
if (msg === undefined)
msg = "Assertion failed: expected SyntaxError, but no exception thrown";
throw new Error(msg + " - " + str);
}
function assertFalse(a) { assertEq(a, false) }
function assertTrue(a) { assertEq(a, true) }
function assertNotEq(found, not_expected) { assertFalse(found === expected) }
function assertArrayEq(found, expected) {
assertEq(found.length, expected.length);
for (var i = 0; i < expected.length; i++)
assertEq(found[i], expected[i]);
}
function f() { }
function* g() { yield 1; }
var GeneratorFunctionPrototype = Object.getPrototypeOf(g);
var GeneratorFunction = GeneratorFunctionPrototype.constructor;
var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
// A generator function should have the same set of properties as any
// other function.
function TestGeneratorFunctionInstance() {
var f_own_property_names = Object.getOwnPropertyNames(f);
var g_own_property_names = Object.getOwnPropertyNames(g);
f_own_property_names.sort();
g_own_property_names.sort();
assertArrayEq(f_own_property_names, g_own_property_names);
var i;
for (i = 0; i < f_own_property_names.length; i++) {
var prop = f_own_property_names[i];
var f_desc = Object.getOwnPropertyDescriptor(f, prop);
var g_desc = Object.getOwnPropertyDescriptor(g, prop);
assertEq(f_desc.configurable, g_desc.configurable, prop);
assertEq(f_desc.writable, g_desc.writable, prop);
assertEq(f_desc.enumerable, g_desc.enumerable, prop);
}
}
TestGeneratorFunctionInstance();
// Generators have an additional object interposed in the chain between
// themselves and Function.prototype.
function TestGeneratorFunctionPrototype() {
// Sanity check.
assertEq(Object.getPrototypeOf(f), Function.prototype);
assertNotEq(GeneratorFunctionPrototype, Function.prototype);
assertEq(Object.getPrototypeOf(GeneratorFunctionPrototype),
Function.prototype);
assertEq(Object.getPrototypeOf(function* () {}),
GeneratorFunctionPrototype);
}
TestGeneratorFunctionPrototype();
// Functions that we associate with generator objects are actually defined by
// a common prototype.
function TestGeneratorObjectPrototype() {
assertEq(Object.getPrototypeOf(GeneratorObjectPrototype),
Object.prototype);
assertEq(Object.getPrototypeOf((function*(){yield 1}).prototype),
GeneratorObjectPrototype);
var expected_property_names = ["iterator", "next", "throw", "constructor"];
var found_property_names =
Object.getOwnPropertyNames(GeneratorObjectPrototype);
expected_property_names.sort();
found_property_names.sort();
assertArrayEq(found_property_names, expected_property_names);
}
TestGeneratorObjectPrototype();
// This tests the object that would be called "GeneratorFunction", if it were
// like "Function".
function TestGeneratorFunction() {
assertEq(GeneratorFunctionPrototype, GeneratorFunction.prototype);
assertTrue(g instanceof GeneratorFunction);
assertEq(Function, Object.getPrototypeOf(GeneratorFunction));
assertTrue(g instanceof Function);
assertEq("function* g() { yield 1; }", g.toString());
// Not all functions are generators.
assertTrue(f instanceof Function); // Sanity check.
assertFalse(f instanceof GeneratorFunction);
assertTrue((new GeneratorFunction()) instanceof GeneratorFunction);
assertTrue(GeneratorFunction() instanceof GeneratorFunction);
assertTrue(GeneratorFunction('yield 1') instanceof GeneratorFunction);
assertTrue(GeneratorFunction('return 1') instanceof GeneratorFunction);
assertTrue(GeneratorFunction('a', 'yield a') instanceof GeneratorFunction);
assertTrue(GeneratorFunction('a', 'return a') instanceof GeneratorFunction);
assertTrue(GeneratorFunction('a', 'return a') instanceof GeneratorFunction);
assertSyntaxError("GeneratorFunction('yield', 'return yield')");
assertTrue(GeneratorFunction('with (x) return foo;') instanceof GeneratorFunction);
assertSyntaxError("GeneratorFunction('\"use strict\"; with (x) return foo;')");
// Doesn't matter particularly what string gets serialized, as long
// as it contains "function*" and "yield 10".
assertEq(GeneratorFunction('yield 10').toString(),
"function* anonymous() {\nyield 10\n}");
}
TestGeneratorFunction();
function TestPerGeneratorPrototype() {
assertNotEq((function*(){}).prototype, (function*(){}).prototype);
assertNotEq((function*(){}).prototype, g.prototype);
assertEq(typeof GeneratorFunctionPrototype, "object");
assertEq(g.prototype.__proto__.constructor, GeneratorFunctionPrototype, "object");
assertEq(Object.getPrototypeOf(g.prototype), GeneratorObjectPrototype);
assertFalse(g.prototype instanceof Function);
assertEq(typeof (g.prototype), "object");
assertArrayEq(Object.getOwnPropertyNames(g.prototype), []);
}
TestPerGeneratorPrototype();
if (typeof reportCompare == "function")
reportCompare(true, true);

View File

@ -26,34 +26,35 @@ function test()
try {
proto.next();
throw "generatorProto.next() does not throw StopIteration";
throw "generatorProto.next() does not throw TypeError";
} catch (e) {
if (!(e instanceof StopIteration))
if (!(e instanceof TypeError))
throw "generatorProto.next() throws unexpected exception: "+uneval(e);
}
try {
proto.send();
throw "generatorProto.send() does not throw StopIteration";
throw "generatorProto.send() does not throw TypeError";
} catch (e) {
if (!(e instanceof StopIteration))
if (!(e instanceof TypeError))
throw "generatorProto.send() throws unexpected exception: "+uneval(e);
}
var obj = {};
try {
proto.throw(obj);
throw "generatorProto.throw(obj) does not throw obj";
throw "generatorProto.throw(obj) does not throw TypeError";
} catch (e) {
if (e !== obj)
if (!(e instanceof TypeError))
throw "generatorProto.throw() throws unexpected exception: "+uneval(e);
}
var obj = {};
try {
proto.close();
throw "generatorProto.close() does not throw TypeError";
} catch (e) {
throw "generatorProto.throw() throws exception: "+uneval(e);
if (!(e instanceof TypeError))
throw "generatorProto.close() throws unexpected exception: "+uneval(e);
}
}

View File

@ -90,8 +90,9 @@ class GlobalObject : public JSObject
/* One-off properties stored after slots for built-ins. */
static const unsigned ELEMENT_ITERATOR_PROTO = FROM_BUFFER_UINT8CLAMPED + 1;
static const unsigned GENERATOR_PROTO = ELEMENT_ITERATOR_PROTO + 1;
static const unsigned MAP_ITERATOR_PROTO = GENERATOR_PROTO + 1;
static const unsigned LEGACY_GENERATOR_OBJECT_PROTO = ELEMENT_ITERATOR_PROTO + 1;
static const unsigned STAR_GENERATOR_OBJECT_PROTO = LEGACY_GENERATOR_OBJECT_PROTO + 1;
static const unsigned MAP_ITERATOR_PROTO = STAR_GENERATOR_OBJECT_PROTO + 1;
static const unsigned SET_ITERATOR_PROTO = MAP_ITERATOR_PROTO + 1;
static const unsigned COLLATOR_PROTO = SET_ITERATOR_PROTO + 1;
static const unsigned NUMBER_FORMAT_PROTO = COLLATOR_PROTO + 1;
@ -364,8 +365,21 @@ class GlobalObject : public JSObject
return getOrCreateObject(cx, ELEMENT_ITERATOR_PROTO, initIteratorClasses);
}
JSObject *getOrCreateGeneratorPrototype(JSContext *cx) {
return getOrCreateObject(cx, GENERATOR_PROTO, initIteratorClasses);
JSObject *getOrCreateLegacyGeneratorObjectPrototype(JSContext *cx) {
return getOrCreateObject(cx, LEGACY_GENERATOR_OBJECT_PROTO, initIteratorClasses);
}
JSObject *getOrCreateStarGeneratorObjectPrototype(JSContext *cx) {
return getOrCreateObject(cx, STAR_GENERATOR_OBJECT_PROTO, initIteratorClasses);
}
JSObject *getOrCreateStarGeneratorFunctionPrototype(JSContext *cx) {
return getOrCreateObject(cx, JSProto_LIMIT + JSProto_GeneratorFunction,
initIteratorClasses);
}
JSObject *getOrCreateStarGeneratorFunction(JSContext *cx) {
return getOrCreateObject(cx, JSProto_GeneratorFunction, initIteratorClasses);
}
JSObject *getOrCreateMapIteratorPrototype(JSContext *cx) {