From 747b5e9fffa9333be108b832f4e364040a4ef596 Mon Sep 17 00:00:00 2001 From: "igor.bukanov%gmail.com" Date: Sat, 26 Aug 2006 20:24:45 +0000 Subject: [PATCH] Bug 349331: Implementation of generator.close now uses asynchronous return instead of GeneratorExit exception. r=brendan --- js/src/js.msg | 3 +-- js/src/jsapi.c | 1 - js/src/jsemit.c | 37 ++++++++++++++++++++++++++ js/src/jsgc.c | 19 +++++++++----- js/src/jsinterp.c | 29 ++++++++++++++++----- js/src/jsiter.c | 66 +++-------------------------------------------- js/src/jsiter.h | 11 ++++++-- js/src/jsscript.h | 8 ++++++ 8 files changed, 94 insertions(+), 80 deletions(-) diff --git a/js/src/js.msg b/js/src/js.msg index cb22a5213290..6793a221fd63 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -254,7 +254,7 @@ MSG_DEF(JSMSG_BAD_XML_CHARACTER, 171, 0, JSEXN_SYNTAXERR, "illegal XML char MSG_DEF(JSMSG_BAD_DEFAULT_XML_NAMESPACE,172,0,JSEXN_SYNTAXERR, "invalid default XML namespace") MSG_DEF(JSMSG_BAD_XML_NAME_SYNTAX, 173, 0, JSEXN_SYNTAXERR, "invalid XML name") MSG_DEF(JSMSG_BRACKET_AFTER_ATTR_EXPR,174, 0, JSEXN_SYNTAXERR, "missing ] after attribute expression") -MSG_DEF(JSMSG_BAD_GENERATOR_EXIT, 175, 1, JSEXN_TYPEERR, "GeneratorExit ignored by generator {0}") +MSG_DEF(JSMSG_NESTING_GENERATOR, 175, 1, JSEXN_TYPEERR, "already executing generator {0}") MSG_DEF(JSMSG_CURLY_IN_XML_EXPR, 176, 0, JSEXN_SYNTAXERR, "missing } in XML expression") MSG_DEF(JSMSG_BAD_XML_NAMESPACE, 177, 1, JSEXN_TYPEERR, "invalid XML namespace {0}") MSG_DEF(JSMSG_BAD_XML_ATTR_NAME, 178, 1, JSEXN_TYPEERR, "invalid XML attribute name {0}") @@ -294,4 +294,3 @@ MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 211, 0, JSEXN_SYNTAXERR, "missing in after MSG_DEF(JSMSG_BAD_ITERATOR_RETURN, 212, 2, JSEXN_TYPEERR, "{0}.{1} returned a primitive value") MSG_DEF(JSMSG_KEYWORD_NOT_NS, 213, 0, JSEXN_SYNTAXERR, "keyword is used as namespace") MSG_DEF(JSMSG_BAD_GENERATOR_YIELD, 214, 1, JSEXN_TYPEERR, "yield from closing generator {0}") -MSG_DEF(JSMSG_NESTING_GENERATOR, 215, 1, JSEXN_TYPEERR, "already executing generator {0}") diff --git a/js/src/jsapi.c b/js/src/jsapi.c index 9a4e97c950f7..aa3e582890bc 100644 --- a/js/src/jsapi.c +++ b/js/src/jsapi.c @@ -1367,7 +1367,6 @@ static JSStdName standard_class_names[] = { #if JS_HAS_GENERATORS {js_InitIteratorClasses, EAGER_ATOM_AND_CLASP(Iterator)}, {js_InitIteratorClasses, EAGER_ATOM_AND_CLASP(Generator)}, - {js_InitIteratorClasses, EAGER_ATOM_AND_CLASP(GeneratorExit)}, #endif {NULL, 0, NULL, NULL} diff --git a/js/src/jsemit.c b/js/src/jsemit.c index 007c7434de07..1dd2aee4b3cc 100644 --- a/js/src/jsemit.c +++ b/js/src/jsemit.c @@ -6377,3 +6377,40 @@ js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote *notes) notes[count].length = CG_OFFSET(cg); notes[count].catchStart = 0; } + +#if JS_HAS_GENERATORS + +jsbytecode * +js_FindFinallyHandler(JSScript *script, jsbytecode *pc) +{ + JSTryNote *tn; + ptrdiff_t off; + + tn = script->trynotes; + if (!tn) + return NULL; + + off = pc - script->main; + if (off < 0) + return NULL; + + JS_ASSERT(tn->catchStart != 0); + do { + if ((jsuword)(off - tn->start) < (jsuword)tn->length) { + /* + * We have a handler, is it finally? + * + * Catch bytecode begins with: JSOP_SETSP JSOP_ENTERBLOCK + * Finally bytecode begins with: JSOP_SETSP JSOP_EXCEPTION + */ + pc = script->main + tn->catchStart; + JS_ASSERT(*pc == JSOP_SETSP); + if (pc[js_CodeSpec[JSOP_SETSP].length] == JSOP_EXCEPTION) + return pc; + JS_ASSERT(pc[js_CodeSpec[JSOP_SETSP].length] == JSOP_ENTERBLOCK); + } + } while ((++tn)->catchStart != 0); + return NULL; +} + +#endif diff --git a/js/src/jsgc.c b/js/src/jsgc.c index 1c22fe443fc9..09295d2d7444 100644 --- a/js/src/jsgc.c +++ b/js/src/jsgc.c @@ -933,17 +933,24 @@ FindAndMarkObjectsToClose(JSContext *cx, JSGCInvocationKind gckind) if (*js_GetGCThingFlags(gen->obj) & GCF_MARK) { genp = &gen->next; } else { + /* + * Only generators that yielded or were already closed can become + * unreachable while still on reachableList. + */ + JS_ASSERT(gen->state == JSGEN_OPEN || gen->state == JSGEN_CLOSED); + *genp = gen->next; gen->next = NULL; - if (gen->state != JSGEN_CLOSED) { + if (gen->state == JSGEN_OPEN && + js_FindFinallyHandler(gen->frame.script, gen->frame.pc)) { /* - * Generator cannot be nesting, i.e., running or closing, and - * newborn generator is never registered with GC. + * Generator yielded inside a try with a finally block. + * Schedule it for closing. * - * XXX: we do need to run the close hook if the last yield - * happened outside a try block. + * We keep generators that yielded outside try-with-finally + * with gen->state == JSGEN_OPEN. The finalizer must deal with + * open generators as we may skip the close hooks, see below. */ - JS_ASSERT(gen->state == JSGEN_OPEN); *rt->gcCloseState.todoTail = gen; rt->gcCloseState.todoTail = &gen->next; rt->gcCloseState.todoCount++; diff --git a/js/src/jsinterp.c b/js/src/jsinterp.c index e8429ea76497..6a696677b695 100644 --- a/js/src/jsinterp.c +++ b/js/src/jsinterp.c @@ -6214,13 +6214,30 @@ out: /* * Look for a try block in script that can catch this exception. */ - SCRIPT_FIND_CATCH_START(script, pc, pc); - if (pc) { - /* Don't clear cx->throwing to save cx->exception from GC. */ - len = 0; - ok = JS_TRUE; - DO_NEXT_OP(len); +#if JS_HAS_GENERATORS + if (JS_LIKELY(cx->exception != JSVAL_ARETURN)) { + SCRIPT_FIND_CATCH_START(script, pc, pc); + if (!pc) + goto no_catch; + } else { + pc = js_FindFinallyHandler(script, pc); + if (!pc) { + cx->throwing = JS_FALSE; + ok = JS_TRUE; + fp->rval = JSVAL_VOID; + goto no_catch; + } } +#else + SCRIPT_FIND_CATCH_START(script, pc, pc); + if (!pc) + goto no_catch; +#endif + + /* Don't clear cx->throwing to save cx->exception from GC. */ + len = 0; + ok = JS_TRUE; + DO_NEXT_OP(len); } no_catch:; } diff --git a/js/src/jsiter.c b/js/src/jsiter.c index 45e5c54a5ddf..0aad1ccb4cab 100644 --- a/js/src/jsiter.c +++ b/js/src/jsiter.c @@ -560,27 +560,6 @@ JSClass js_StopIterationClass = { NULL, NULL }; -static JSBool -genexit_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) -{ - *bp = !JSVAL_IS_PRIMITIVE(v) && - OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)) == &js_GeneratorExitClass; - return JS_TRUE; -} - -JSClass js_GeneratorExitClass = { - js_GeneratorExit_str, - JSCLASS_HAS_CACHED_PROTO(JSProto_GeneratorExit), - JS_PropertyStub, JS_PropertyStub, - JS_PropertyStub, JS_PropertyStub, - JS_EnumerateStub, JS_ResolveStub, - JS_ConvertStub, JS_FinalizeStub, - NULL, NULL, - NULL, NULL, - NULL, genexit_hasInstance, - NULL, NULL -}; - JSBool js_ThrowStopIteration(JSContext *cx, JSObject *obj) { @@ -746,14 +725,10 @@ static JSBool SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, JSGenerator *gen, jsval arg, jsval *rval) { - jsval genexit; JSStackFrame *fp; jsval junk; JSArena *arena; JSBool ok; - jsval exn; - JSClass *clasp; - JSString *str; JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); switch (op) { @@ -776,11 +751,7 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, default: JS_ASSERT(op == JSGENOP_CLOSE); - if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_GeneratorExit), - &genexit)) { - return JS_FALSE; - } - JS_SetPendingException(cx, genexit); + JS_SetPendingException(cx, JSVAL_ARETURN); gen->state = JSGEN_CLOSING; break; } @@ -815,33 +786,9 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, return js_ThrowStopIteration(cx, obj); } - if (op == JSGENOP_CLOSE && cx->throwing) { - /* - * Generator terminated with an exception. Clear if it is a normal - * exit signal. - */ - exn = cx->exception; - if (!JSVAL_IS_PRIMITIVE(exn)) { - clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(exn)); - if (clasp == &js_GeneratorExitClass || - clasp == &js_StopIterationClass) { - JS_ClearPendingException(cx); - return JS_TRUE; - } - } - str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, - OBJECT_TO_JSVAL(obj), NULL); - if (str) { - JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, - JSMSG_BAD_GENERATOR_EXIT, - JSSTRING_CHARS(str)); - } - return JS_FALSE; - } - /* - * An error, silent termination by branch callback or an exception thrown - * in resposnse to next|send|throw. Propagate the condition to the caller. + * An error, silent termination by branch callback or an exception. + * Propagate the condition to the caller. */ return JS_FALSE; } @@ -1052,13 +999,6 @@ js_InitIteratorClasses(JSContext *cx, JSObject *obj) } #endif -#if JS_HAS_GENERATORS - if (!JS_InitClass(cx, obj, NULL, &js_GeneratorExitClass, NULL, 0, - NULL, NULL, NULL, NULL)) { - return NULL; - } -#endif - return JS_InitClass(cx, obj, NULL, &js_StopIterationClass, NULL, 0, NULL, NULL, NULL, NULL); } diff --git a/js/src/jsiter.h b/js/src/jsiter.h index 274818b18156..b7f41586f944 100644 --- a/js/src/jsiter.h +++ b/js/src/jsiter.h @@ -101,7 +101,7 @@ typedef enum JSGeneratorState { JSGEN_NEWBORN, /* not yet started */ JSGEN_OPEN, /* started by a .next() or .send(undefined) call */ JSGEN_RUNNING, /* currently executing via .next(), etc., call */ - JSGEN_CLOSING, /* close method is doing .send(GeneratorExit) */ + JSGEN_CLOSING, /* close method is doing asynchronous return */ JSGEN_CLOSED /* closed, cannot be started or closed again */ } JSGeneratorState; @@ -123,12 +123,19 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp); extern JSBool js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen); + +/* + * Special unique value to implement asynchronous return for + * generator.close(). Scripts never see it. + */ + +#define JSVAL_ARETURN BOOLEAN_TO_JSVAL(JS_TRUE + 1) + #endif extern JSClass js_GeneratorClass; extern JSClass js_IteratorClass; extern JSClass js_StopIterationClass; -extern JSClass js_GeneratorExitClass; extern JSObject * js_InitIteratorClasses(JSContext *cx, JSObject *obj); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index bb5e72515923..10cd330fc9c5 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -97,6 +97,14 @@ struct JSScript { catchpc = catchpc_; \ JS_END_MACRO +/* + * Find the innermost finally block that handles the given pc. This is a + * version of SCRIPT_FIND_CATCH_START that ignore catch blocks and is used + * to implement generator.close(). + */ +jsbytecode * +js_FindFinallyHandler(JSScript *script, jsbytecode *pc); + extern JS_FRIEND_DATA(JSClass) js_ScriptClass; extern JSObject *