Bug 349326: for-in loop now always closes iterator objects. r=brendan

This commit is contained in:
igor@mir2.org 2007-07-02 05:13:23 -07:00
Родитель 7d3de0c8ce
Коммит 5414afce47
12 изменённых файлов: 319 добавлений и 283 удалений

Просмотреть файл

@ -1070,8 +1070,9 @@ Notes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
JS_STATIC_ASSERT(JSTN_CATCH == 0);
JS_STATIC_ASSERT(JSTN_FINALLY == 1);
JS_STATIC_ASSERT(JSTN_ITER == 2);
static const char* const TryNoteNames[] = { "catch", "finally" };
static const char* const TryNoteNames[] = { "catch", "finally", "iter" };
static JSBool
TryNotes(JSContext *cx, JSScript *script)
@ -1086,7 +1087,7 @@ TryNotes(JSContext *cx, JSScript *script)
fprintf(gOutFile, "\nException table:\n"
"kind stack start end\n");
do {
JS_ASSERT(tn->kind == JSTN_CATCH || tn->kind == JSTN_FINALLY);
JS_ASSERT(tn->kind < JS_ARRAY_LENGTH(TryNoteNames));
fprintf(gOutFile, " %-7s %6u %8u %8u\n",
TryNoteNames[tn->kind], tn->stackDepth,
tn->start, tn->start + tn->length);

Просмотреть файл

@ -75,6 +75,10 @@
#define SRCNOTE_SIZE(n) ((n) * sizeof(jssrcnote))
#define TRYNOTE_SIZE(n) ((n) * sizeof(JSTryNote))
static JSBool
NewTryNote(JSContext *cx, JSCodeGenerator *cg, JSTryNoteKind kind,
uintN stackDepth, size_t start, size_t end);
JS_FRIEND_API(JSBool)
js_InitCodeGenerator(JSContext *cx, JSCodeGenerator *cg,
JSArenaPool *codePool, JSArenaPool *notePool,
@ -756,7 +760,7 @@ OptimizeSpanDeps(JSContext *cx, JSCodeGenerator *cg)
jssrcnote *sn, *snlimit;
JSSrcNoteSpec *spec;
uintN i, n, noteIndex;
JSTryNote *tn, *tnlimit;
JSTryNode *tryNode;
#ifdef DEBUG_brendan
int passes = 0;
#endif
@ -1064,25 +1068,27 @@ OptimizeSpanDeps(JSContext *cx, JSCodeGenerator *cg)
* Fix try/catch notes (O(numTryNotes * log2(numSpanDeps)), but it's
* not clear how we can beat that).
*/
for (tn = cg->tryBase, tnlimit = cg->tryNext; tn < tnlimit; tn++) {
for (tryNode = cg->lastTryNode; tryNode; tryNode = tryNode->prev) {
/*
* First, look for the nearest span dependency at/above tn->start.
* There may not be any such spandep, in which case the guard will
* be returned.
*/
offset = tn->start;
offset = tryNode->note.start;
sd = FindNearestSpanDep(cg, offset, 0, &guard);
delta = sd->offset - sd->before;
tn->start = offset + delta;
tryNode->note.start = offset + delta;
/*
* Next, find the nearest spandep at/above tn->start + tn->length.
* Use its delta minus tn->start's delta to increase tn->length.
*/
length = tn->length;
length = tryNode->note.length;
sd2 = FindNearestSpanDep(cg, offset + length, sd - sdbase, &guard);
if (sd2 != sd)
tn->length = length + sd2->offset - sd2->before - delta;
if (sd2 != sd) {
tryNode->note.length =
length + sd2->offset - sd2->before - delta;
}
}
}
@ -1330,19 +1336,21 @@ EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
ptrdiff_t jmp;
/*
* Return from within a try block that has a finally clause must be split
* into two ops: JSOP_SETRVAL, to pop the r.v. and store it in fp->rval;
* and JSOP_RETRVAL, which makes control flow go back to the caller, who
* picks up fp->rval as usual. Otherwise, the stack will be unbalanced
* when executing the finally clause.
* Return from a try block that has a finally clause or from a for-in loop
* must be split into two ops: JSOP_SETRVAL, to pop the r.v. and store it
* in fp->rval; and JSOP_RETRVAL, which makes control flow go back to the
* caller, who picks up fp->rval as usual. Otherwise, the stack will be
* unbalanced when executing the finally clause.
*
* We mutate *returnop once only if we find an enclosing try-block (viz,
* STMT_FINALLY) to ensure that we emit just one JSOP_SETRVAL before one
* or more JSOP_GOSUBs and other fixup opcodes emitted by this function.
* STMT_FINALLY) or a for-in loop to ensure that we emit just one
* JSOP_SETRVAL before one or more JSOP_GOSUBs/JSOP_ENDITERs and other
* fixup opcodes emitted by this function.
*
* Our caller (the TOK_RETURN case of js_EmitTree) then emits *returnop.
* The fixup opcodes and gosubs must interleave in the proper order, from
* inner statement to outer, so that finally clauses run at the correct
* stack depth.
* The fixup opcodes and gosubs/enditers must interleave in the proper
* order, from inner statement to outer, so that finally clauses run at
* the correct stack depth.
*/
if (returnop) {
JS_ASSERT(*returnop == JSOP_RETURN);
@ -1350,7 +1358,8 @@ EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
stmt = stmt->down) {
if (stmt->type == STMT_FINALLY ||
((cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT) &&
STMT_MAYBE_SCOPE(stmt))) {
STMT_MAYBE_SCOPE(stmt)) ||
stmt->type == STMT_FOR_IN_LOOP) {
if (js_Emit1(cx, cg, JSOP_SETRVAL) < 0)
return JS_FALSE;
*returnop = JSOP_RETRVAL;
@ -3225,9 +3234,6 @@ bad:
JSBool
js_EmitFunctionBytecode(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body)
{
if (!js_AllocTryNotes(cx, cg))
return JS_FALSE;
if (cg->treeContext.flags & TCF_FUN_IS_GENERATOR) {
if (js_Emit1(cx, cg, JSOP_GENERATOR) < 0)
return JS_FALSE;
@ -4063,7 +4069,6 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
return JS_FALSE;
}
cg2->treeContext.flags = (uint16) (pn->pn_flags | TCF_IN_FUNCTION);
cg2->treeContext.tryCount = pn->pn_tryCount;
cg2->parent = cg;
fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(pn->pn_funAtom));
if (!js_EmitFunctionBody(cx, cg2, pn->pn_body, fun))
@ -4734,8 +4739,16 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
return JS_FALSE;
if (pn2->pn_type == TOK_IN) {
if (js_Emit1(cx, cg, JSOP_ENDITER) < 0)
/*
* JSOP_ENDITER needs a slot to save an exception thrown from the
* body of for-in loop when closing the iterator object.
*/
JS_ASSERT(js_CodeSpec[JSOP_ENDITER].format & JOF_TMPSLOT);
if (!NewTryNote(cx, cg, JSTN_ITER, cg->stackDepth, top,
CG_OFFSET(cg)) ||
js_Emit1(cx, cg, JSOP_ENDITER) < 0) {
return JS_FALSE;
}
}
break;
@ -5057,7 +5070,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* (first to last for a given nesting level, inner to outer by level).
*/
if (pn->pn_kid2 &&
!js_NewTryNote(cx, cg, JSTN_CATCH, depth, tryStart, tryEnd)) {
!NewTryNote(cx, cg, JSTN_CATCH, depth, tryStart, tryEnd)) {
return JS_FALSE;
}
@ -5067,8 +5080,7 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
* for the try{}finally{} case.
*/
if (pn->pn_kid3 &&
!js_NewTryNote(cx, cg, JSTN_FINALLY, depth, tryStart,
finallyStart)) {
!NewTryNote(cx, cg, JSTN_FINALLY, depth, tryStart, finallyStart)) {
return JS_FALSE;
}
break;
@ -6834,69 +6846,43 @@ js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg, jssrcnote *notes)
return JS_TRUE;
}
JSBool
js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg)
{
size_t size, incr;
ptrdiff_t delta;
size = TRYNOTE_SIZE(cg->treeContext.tryCount);
if (size <= cg->tryNoteSpace)
return JS_TRUE;
/*
* Allocate trynotes from cx->tempPool.
* XXX Too much growing and we bloat, as other tempPool allocators block
* in-place growth, and we never recycle old free space in an arena.
* YYY But once we consume an entire arena, we'll realloc it, letting the
* malloc heap recycle old space, while still freeing _en masse_ via the
* arena pool.
*/
if (!cg->tryBase) {
size = JS_ROUNDUP(size, TRYNOTE_SIZE(TRYNOTE_CHUNK));
JS_ARENA_ALLOCATE_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size);
if (!cg->tryBase)
return JS_FALSE;
cg->tryNoteSpace = size;
cg->tryNext = cg->tryBase;
} else {
delta = PTRDIFF((char *)cg->tryNext, (char *)cg->tryBase, char);
incr = size - cg->tryNoteSpace;
incr = JS_ROUNDUP(incr, TRYNOTE_SIZE(TRYNOTE_CHUNK));
size = cg->tryNoteSpace;
JS_ARENA_GROW_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size, incr);
if (!cg->tryBase)
return JS_FALSE;
cg->tryNoteSpace = size + incr;
cg->tryNext = (JSTryNote *)((char *)cg->tryBase + delta);
}
return JS_TRUE;
}
JSTryNote *
js_NewTryNote(JSContext *cx, JSCodeGenerator *cg, JSTryNoteKind kind,
static JSBool
NewTryNote(JSContext *cx, JSCodeGenerator *cg, JSTryNoteKind kind,
uintN stackDepth, size_t start, size_t end)
{
JSTryNote *tn;
JSTryNode *tryNode;
JS_ASSERT(cg->tryBase <= cg->tryNext);
JS_ASSERT(kind == JSTN_FINALLY || kind == JSTN_CATCH);
JS_ASSERT((uintN)(uint16)stackDepth == stackDepth);
JS_ASSERT(start <= end);
JS_ASSERT((size_t)(uint32)start == start);
JS_ASSERT((size_t)(uint32)end == end);
tn = cg->tryNext++;
tn->kind = kind;
tn->stackDepth = (uint16)stackDepth;
tn->start = (uint32)start;
tn->length = (uint32)(end - start);
return tn;
JS_ARENA_ALLOCATE_TYPE(tryNode, JSTryNode, &cx->tempPool);
if (!tryNode)
return JS_FALSE;
tryNode->note.kind = kind;
tryNode->note.stackDepth = (uint16)stackDepth;
tryNode->note.start = (uint32)start;
tryNode->note.length = (uint32)(end - start);
tryNode->prev = cg->lastTryNode;
cg->lastTryNode = tryNode;
cg->ntrynotes++;
return JS_TRUE;
}
void
js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg,
JSTryNoteArray *array)
{
JS_ASSERT(cg->tryNext - cg->tryBase == (ptrdiff_t) array->length);
memcpy(array->notes, cg->tryBase, TRYNOTE_SIZE(array->length));
JSTryNode *tryNode;
JSTryNote *tn;
JS_ASSERT(array->length > 0 && array->length == cg->ntrynotes);
tn = array->notes + array->length;
tryNode = cg->lastTryNode;
do {
*--tn = tryNode->note;
} while ((tryNode = tryNode->prev) != NULL);
JS_ASSERT(tn == array->notes);
}

Просмотреть файл

@ -157,7 +157,6 @@ struct JSStmtInfo {
struct JSTreeContext { /* tree context for semantic checks */
uint16 flags; /* statement state flags, see below */
uint16 numGlobalVars; /* max. no. of global variables/regexps */
uint32 tryCount; /* total count of try statements parsed */
uint32 globalUses; /* optimizable global var uses in total */
uint32 loopyGlobalUses;/* optimizable global var uses in loops */
JSStmtInfo *topStmt; /* top of statement info stack */
@ -188,7 +187,7 @@ struct JSTreeContext { /* tree context for semantic checks */
#define TREE_CONTEXT_INIT(tc) \
((tc)->flags = (tc)->numGlobalVars = 0, \
(tc)->tryCount = (tc)->globalUses = (tc)->loopyGlobalUses = 0, \
(tc)->globalUses = (tc)->loopyGlobalUses = 0, \
(tc)->topStmt = (tc)->topScopeStmt = NULL, \
(tc)->blockChain = NULL, \
ATOM_LIST_INIT(&(tc)->decls), \
@ -258,6 +257,13 @@ struct JSJumpTarget {
? JT_CLR_TAG((sd)->target)->offset - (pivot) \
: 0)
typedef struct JSTryNode JSTryNode;
struct JSTryNode {
JSTryNote note;
JSTryNode *prev;
};
struct JSCodeGenerator {
JSTreeContext treeContext; /* base state: statement info stack, etc. */
@ -286,9 +292,8 @@ struct JSCodeGenerator {
intN stackDepth; /* current stack depth in script frame */
uintN maxStackDepth; /* maximum stack depth so far */
JSTryNote *tryBase; /* first exception handling note */
JSTryNote *tryNext; /* next available note */
size_t tryNoteSpace; /* # of bytes allocated at tryBase */
uintN ntrynotes; /* number of allocated so far try notes */
JSTryNode *lastTryNode; /* the last allocated try node */
JSSpanDep *spanDeps; /* span dependent instruction records */
JSJumpTarget *jumpTargets; /* AVL tree of jump target offsets */
@ -707,21 +712,6 @@ js_SetSrcNoteOffset(JSContext *cx, JSCodeGenerator *cg, uintN index,
extern JSBool
js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg, jssrcnote *notes);
/*
* Allocate cg->treeContext.tryCount notes (plus one for the end sentinel)
* from cx->tempPool and set up cg->tryBase/tryNext for exactly tryCount
* js_NewTryNote calls. The storage is freed by js_FinishCodeGenerator.
*/
extern JSBool
js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg);
/*
* Grab the next trynote slot in cg, filling it in appropriately.
*/
extern JSTryNote *
js_NewTryNote(JSContext *cx, JSCodeGenerator *cg, JSTryNoteKind kind,
uintN stackDepth, size_t start, size_t end);
extern void
js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg,
JSTryNoteArray *array);

Просмотреть файл

@ -989,7 +989,7 @@ js_RegisterCloseableIterator(JSContext *cx, JSObject *obj)
}
static void
CloseIteratorStates(JSContext *cx)
CloseNativeIterators(JSContext *cx)
{
JSRuntime *rt;
size_t count, newCount, i;
@ -1004,7 +1004,7 @@ CloseIteratorStates(JSContext *cx)
for (i = 0; i != count; ++i) {
obj = (JSObject *)array[i];
if (js_IsAboutToBeFinalized(cx, obj))
js_CloseIteratorState(cx, obj);
js_CloseNativeIterator(cx, obj);
else
array[newCount++] = obj;
}
@ -1321,7 +1321,7 @@ js_RunCloseHooks(JSContext *cx)
METER(deferCount++);
continue;
}
ok = js_CloseGeneratorObject(cx, gen);
ok = js_CloseGenerator(cx, gen->obj);
/*
* Unlink the generator after closing it to make sure it always stays
@ -2778,7 +2778,7 @@ restart:
rt->gcMarkingTracer = NULL;
/* Finalize iterator states before the objects they iterate over. */
CloseIteratorStates(cx);
CloseNativeIterators(cx);
#ifdef DUMP_CALL_TABLE
/*

Просмотреть файл

@ -5829,18 +5829,15 @@ interrupt:
#undef FAST_LOCAL_INCREMENT_OP
BEGIN_CASE(JSOP_ENDITER)
JS_ASSERT(!JSVAL_IS_PRIMITIVE(sp[-1]));
iterobj = JSVAL_TO_OBJECT(sp[-1]);
/*
* js_CloseNativeIterator checks whether the iterator is not
* native, and also detects the case of a native iterator that
* has already escaped, even though a for-in loop caused it to
* be created. See jsiter.c.
* Decrease the stack pointer even when !ok, see comments in the
* exception capturing code for details.
*/
SAVE_SP_AND_PC(fp);
js_CloseNativeIterator(cx, iterobj);
*--sp = JSVAL_NULL;
ok = js_CloseIterator(cx, sp[-1]);
--sp;
if (!ok)
goto out;
END_CASE(JSOP_ENDITER)
#if JS_HAS_GENERATORS
@ -5970,12 +5967,12 @@ interrupt:
out:
JS_ASSERT((size_t)(pc - script->code) < script->length);
if (!ok) {
if (!ok && cx->throwing && !(fp->flags & JSFRAME_FILTERING)) {
/*
* Has an exception been raised? Also insist that we are not in an
* XML filtering predicate expression, to avoid catching exceptions
* within the filtering predicate, such as this example taken from
* tests/e4x/Regress/regress-301596.js:
* An exception has been raised and we are not in an XML filtering
* predicate expression. The latter check is necessary to avoid
* catching exceptions within the filtering predicate, such as this
* example taken from tests/e4x/Regress/regress-301596.js:
*
* try {
* <xml/>.(@a == 1);
@ -5995,132 +5992,183 @@ out:
*
* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=309894
*/
if (cx->throwing && !(fp->flags & JSFRAME_FILTERING)) {
JSTrapHandler handler;
JSTryNote *tn, *tnlimit;
uint32 offset;
JSTrapHandler handler;
JSTryNote *tn, *tnlimit;
uint32 offset;
/*
* Call debugger throw hook if set (XXX thread safety?).
*/
handler = cx->debugHooks->throwHook;
if (handler) {
SAVE_SP_AND_PC(fp);
switch (handler(cx, script, pc, &rval,
cx->debugHooks->throwHookData)) {
case JSTRAP_ERROR:
cx->throwing = JS_FALSE;
goto no_catch;
case JSTRAP_RETURN:
ok = JS_TRUE;
cx->throwing = JS_FALSE;
fp->rval = rval;
goto no_catch;
case JSTRAP_THROW:
cx->exception = rval;
case JSTRAP_CONTINUE:
default:;
}
LOAD_INTERRUPT_HANDLER(cx);
}
/*
* Call debugger throw hook if set (XXX thread safety?).
*/
handler = cx->debugHooks->throwHook;
if (handler) {
SAVE_SP_AND_PC(fp);
switch (handler(cx, script, pc, &rval,
cx->debugHooks->throwHookData)) {
case JSTRAP_ERROR:
cx->throwing = JS_FALSE;
goto no_catch;
case JSTRAP_RETURN:
ok = JS_TRUE;
cx->throwing = JS_FALSE;
fp->rval = rval;
goto no_catch;
case JSTRAP_THROW:
cx->exception = rval;
case JSTRAP_CONTINUE:
default:;
}
LOAD_INTERRUPT_HANDLER(cx);
}
/*
* Look for a try block in script that can catch this exception.
*/
if (!script->trynotes)
goto no_catch;
/*
* Look for a try block in script that can catch this exception.
*/
if (!script->trynotes)
goto no_catch;
offset = (uint32)(pc - script->main);
tn = script->trynotes->notes;
tnlimit = tn + script->trynotes->length;
do {
if (offset - tn->start >= tn->length)
continue;
/*
* We have a note that covers the exception pc but we must check
* whether the interpreter has already executed the corresponding
* handler. This is possible when the executed bytecode
* implements break or return from inside a for-in loop.
*
* In this case the emitter generates additional [enditer] and
* [gosub] opcodes to close all outstanding iterators and execute
* the finally blocks. If such an [enditer] throws an exception,
* its pc can still be inside several nested for-in loops and
* try-finally statements even if we have already closed the
* corresponding iterators and invoked the finally blocks.
*
* To address this, we make [enditer] always decrease the stack
* even when its implementation throws an exception. Thus already
* executed [enditer] and [gosub] opcodes will have try notes
* with the stack depth exceeding the current one and this
* condition is what we use to filter them out.
*/
if (tn->stackDepth > sp - fp->spbase)
continue;
/*
* Prepare to execute the try note handler and unwind the block
* and scope chains until we match the stack depth of the try
* note. Note that we set sp after we call js_PutBlockObject to
* avoid potential GC hazards.
*/
ok = JS_TRUE;
i = tn->stackDepth;
for (obj = fp->blockChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
if (OBJ_BLOCK_DEPTH(cx, obj) < i)
break;
}
fp->blockChain = obj;
JS_ASSERT(ok);
for (obj = fp->scopeChain; ; obj = OBJ_GET_PARENT(cx, obj)) {
clasp = OBJ_GET_CLASS(cx, obj);
if (clasp != &js_WithClass && clasp != &js_BlockClass)
break;
if (JS_GetPrivate(cx, obj) != fp ||
OBJ_BLOCK_DEPTH(cx, obj) < i) {
break;
}
if (clasp == &js_BlockClass) {
/* Don't fail until after we've updated all stacks. */
ok &= js_PutBlockObject(cx, obj);
} else {
JS_SetPrivate(cx, obj, NULL);
}
}
fp->scopeChain = obj;
sp = fp->spbase + i;
/*
* Set pc to the first bytecode after the the try note to point
* to the beginning of catch or finally or to [enditer] closing
* the for-in loop.
*
* We do it before checking for ok so, when failing during the
* scope recovery, we restart the exception search with the
* updated stack and pc avoiding calling the handler again.
*/
offset = tn->start + tn->length;
pc = (script)->main + offset;
if (!ok)
goto out;
switch (tn->kind) {
case JSTN_CATCH:
JS_ASSERT(*pc == JSOP_ENTERBLOCK);
offset = (uint32)(pc - script->main);
tn = script->trynotes->notes;
tnlimit = tn + script->trynotes->length;
for (;;) {
if (offset - tn->start < tn->length) {
if (tn->kind == JSTN_FINALLY)
break;
JS_ASSERT(tn->kind == JSTN_CATCH);
#if JS_HAS_GENERATORS
/* Catch can not intercept closing of a generator. */
if (JS_LIKELY(cx->exception != JSVAL_ARETURN))
break;
#else
break;
/* Catch cannot intercept the closing of a generator. */
if (JS_UNLIKELY(cx->exception == JSVAL_ARETURN))
break;
#endif
}
if (++tn == tnlimit)
goto no_catch;
}
ok = JS_TRUE;
/*
* Don't clear cx->throwing to save cx->exception from GC
* until it is pushed to the stack via [exception] in the
* catch block.
*/
len = 0;
DO_NEXT_OP(len);
/*
* Unwind the block and scope chains until we match the stack
* depth of the try note.
*/
i = tn->stackDepth;
for (obj = fp->blockChain; obj; obj = OBJ_GET_PARENT(cx, obj)) {
JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
if (OBJ_BLOCK_DEPTH(cx, obj) < i)
break;
}
fp->blockChain = obj;
case JSTN_FINALLY:
/*
* Push (true, exception) pair for finally to indicate that
* [retsub] should rethrow the exception.
*/
PUSH(JSVAL_TRUE);
PUSH(cx->exception);
cx->throwing = JS_FALSE;
len = 0;
DO_NEXT_OP(len);
JS_ASSERT(ok);
for (obj = fp->scopeChain;
(clasp = OBJ_GET_CLASS(cx, obj)) == &js_WithClass ||
clasp == &js_BlockClass;
obj = OBJ_GET_PARENT(cx, obj)) {
if (JS_GetPrivate(cx, obj) != fp ||
OBJ_BLOCK_DEPTH(cx, obj) < i) {
break;
}
if (clasp == &js_BlockClass) {
/* Don't fail until after we've updated all stacks. */
ok &= js_PutBlockObject(cx, obj);
} else {
JS_SetPrivate(cx, obj, NULL);
}
}
case JSTN_ITER:
/*
* This is similar to JSOP_ENDITER in the interpreter loop
* except the code now uses a reserved stack slot to save and
* restore the exception.
*/
JS_ASSERT(*pc == JSOP_ENDITER);
PUSH(cx->exception);
cx->throwing = JS_FALSE;
SAVE_SP_AND_PC(fp);
ok = js_CloseIterator(cx, sp[-2]);
sp -= 2;
if (!ok) {
/*
* close generated a new exception error or an error,
* restart the handler search to properly notify the
* debugger.
*/
goto out;
}
cx->throwing = JS_TRUE;
cx->exception = sp[1];
fp->scopeChain = obj;
/*
* Reset ok to false so, if this is the last try note, the
* exception will be propagated outside the function or
* script.
*/
ok = JS_FALSE;
break;
}
} while (++tn != tnlimit);
/* Set sp after js_PutBlockObject to avoid potential GC hazards. */
sp = fp->spbase + i;
/* The catch or finally begins right after the code they protect. */
pc = (script)->main + tn->start + tn->length;
/*
* When failing during the scope recovery, restart the exception
* search with the updated stack and pc.
*/
if (!ok)
goto out;
JS_ASSERT(cx->exception != JSVAL_HOLE);
if (tn->kind == JSTN_FINALLY) {
/*
* Push (false, exception) pair for finally to indicate that
* [retsub] should rethrow the exception.
*/
PUSH(JSVAL_TRUE);
PUSH(cx->exception);
cx->throwing = JS_FALSE;
} else {
/*
* Don't clear cx->throwing to save cx->exception from GC
* until it is pushed to the stack via [exception] in the
* catch block.
*/
}
len = 0;
ok = JS_TRUE;
DO_NEXT_OP(len);
}
no_catch:;
no_catch:;
#if JS_HAS_GENERATORS
if (JS_UNLIKELY(cx->exception == JSVAL_ARETURN)) {
if (JS_UNLIKELY(cx->throwing && cx->exception == JSVAL_ARETURN)) {
cx->throwing = JS_FALSE;
ok = JS_TRUE;
fp->rval = JSVAL_VOID;

Просмотреть файл

@ -82,12 +82,12 @@ extern const char js_throw_str[]; /* from jsscan.h */
* when GC detects that the iterator is no longer reachable.
*/
void
js_CloseIteratorState(JSContext *cx, JSObject *iterobj)
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
{
jsval state;
JSObject *iterable;
JS_ASSERT(JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL));
JS_ASSERT(STOBJ_GET_CLASS(iterobj) == &js_IteratorClass);
/* Avoid double work if js_CloseNativeIterator was called on obj. */
state = STOBJ_GET_SLOT(iterobj, JSSLOT_ITER_STATE);
@ -315,29 +315,6 @@ js_GetNativeIteratorFlags(JSContext *cx, JSObject *iterobj)
return JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
}
void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj)
{
uintN flags;
/*
* If this iterator is not an instance of the native default iterator
* class, leave it to be GC'ed.
*/
if (!JS_InstanceOf(cx, iterobj, &js_IteratorClass, NULL))
return;
/*
* If this iterator was not created by js_ValueToIterator called from the
* for-in loop code in js_Interpret, leave it to be GC'ed.
*/
flags = JSVAL_TO_INT(OBJ_GET_SLOT(cx, iterobj, JSSLOT_ITER_FLAGS));
if (!(flags & JSITER_ENUMERATE))
return;
js_CloseIteratorState(cx, iterobj);
}
/*
* Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
* Otherwise construct the defualt iterator.
@ -438,6 +415,28 @@ js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp)
goto out;
}
JSBool
js_CloseIterator(JSContext *cx, jsval v)
{
JSObject *obj;
JSClass *clasp;
JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
obj = JSVAL_TO_OBJECT(v);
clasp = OBJ_GET_CLASS(cx, obj);
if (clasp == &js_IteratorClass) {
js_CloseNativeIterator(cx, obj);
}
#if JS_HAS_GENERATORS
else if (clasp == &js_GeneratorClass) {
if (!js_CloseGenerator(cx, obj))
return JS_FALSE;
}
#endif
return JS_TRUE;
}
static JSBool
CallEnumeratorNext(JSContext *cx, JSObject *iterobj, uintN flags, jsval *rval)
{
@ -911,10 +910,23 @@ SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj,
* unreachable.
*/
JSBool
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen)
js_CloseGenerator(JSContext *cx, JSObject *obj)
{
JSGenerator *gen;
JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_GeneratorClass);
gen = (JSGenerator *) JS_GetPrivate(cx, obj);
if (!gen) {
/* Generator prototype object. */
return JS_TRUE;
}
JS_ASSERT(gen->state != JSGEN_RUNNING && gen->state != JSGEN_CLOSING);
if (gen->state == JSGEN_CLOSED)
return JS_TRUE;
/* We pass null as rval since SendToGenerator never uses it with CLOSE. */
return SendToGenerator(cx, JSGENOP_CLOSE, gen->obj, gen, JSVAL_VOID, NULL);
return SendToGenerator(cx, JSGENOP_CLOSE, obj, gen, JSVAL_VOID, NULL);
}
/*

Просмотреть файл

@ -49,12 +49,6 @@
#define JSITER_FOREACH 0x2 /* return [key, value] pair rather than key */
#define JSITER_KEYVALUE 0x4 /* destructuring for-in wants [key, value] */
extern void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj);
extern void
js_CloseIteratorState(JSContext *cx, JSObject *iterobj);
/*
* Convert the value stored in *vp to its iteration object. The flags should
* contain JSITER_ENUMERATE if js_ValueToIterator is called when enumerating
@ -64,6 +58,9 @@ js_CloseIteratorState(JSContext *cx, JSObject *iterobj);
extern JSBool
js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp);
extern JSBool
js_CloseIterator(JSContext *cx, jsval v);
/*
* Given iterobj, call iterobj.next(). If the iterator stopped, set *rval to
* JSVAL_HOLE. Otherwise set it to the result of the next call.
@ -71,6 +68,12 @@ js_ValueToIterator(JSContext *cx, uintN flags, jsval *vp);
extern JSBool
js_CallIteratorNext(JSContext *cx, JSObject *iterobj, jsval *rval);
/*
* Close iterobj, whose class must be js_IteratorClass.
*/
extern void
js_CloseNativeIterator(JSContext *cx, JSObject *iterobj);
#if JS_HAS_GENERATORS
/*
@ -100,7 +103,7 @@ extern JSObject *
js_NewGenerator(JSContext *cx, JSStackFrame *fp);
extern JSBool
js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen);
js_CloseGenerator(JSContext *cx, JSObject *obj);
#endif

Просмотреть файл

@ -465,7 +465,7 @@ OPDEF(JSOP_FORLOCAL, 207,"forlocal", NULL, 3, 0, 1, 19, JOF_LOCAL|
* Iterator, generator, and array comprehension support.
*/
OPDEF(JSOP_FORCONST, 208,"forconst", NULL, 3, 0, 1, 19, JOF_QVAR|JOF_NAME|JOF_FOR)
OPDEF(JSOP_ENDITER, 209,"enditer", NULL, 1, 1, 0, 0, JOF_BYTE)
OPDEF(JSOP_ENDITER, 209,"enditer", NULL, 1, 1, 0, 0, JOF_BYTE|JOF_TMPSLOT)
OPDEF(JSOP_GENERATOR, 210,"generator", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_YIELD, 211,"yield", NULL, 1, 1, 1, 1, JOF_BYTE)
OPDEF(JSOP_ARRAYPUSH, 212,"arraypush", NULL, 3, 1, 0, 3, JOF_LOCAL)

Просмотреть файл

@ -1474,7 +1474,6 @@ FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
pn->pn_op = op;
pn->pn_body = body;
pn->pn_flags = funtc.flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS);
pn->pn_tryCount = funtc.tryCount;
TREE_CONTEXT_FINISH(&funtc);
return result;
}
@ -1549,7 +1548,6 @@ Statements(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
tc->flags &= ~TCF_RETURN_EXPR;
}
if (!js_FoldConstants(cx, pn2, tc) ||
!js_AllocTryNotes(cx, (JSCodeGenerator *)tc) ||
!js_EmitTree(cx, (JSCodeGenerator *)tc, pn2)) {
tt = TOK_ERROR;
break;
@ -3185,7 +3183,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
pn->pn_kid2 = catchList;
if (tt == TOK_FINALLY) {
tc->tryCount++;
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
js_PushStatement(tc, &stmtInfo, STMT_FINALLY, -1);
pn->pn_kid3 = Statements(cx, ts, tc);
@ -3201,7 +3198,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
JSMSG_CATCH_OR_FINALLY);
return NULL;
}
tc->tryCount++;
return pn;
}
@ -4294,6 +4290,7 @@ ComprehensionTail(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
JSTokenType tt;
JSAtom *atom;
JS_ASSERT(type == TOK_SEMI || type == TOK_ARRAYPUSH);
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_FOR);
/*

Просмотреть файл

@ -68,7 +68,6 @@ JS_BEGIN_EXTERN_C
* pn_body: TOK_LC node for function body statements
* pn_flags: TCF_FUN_* flags (see jsemit.h) collected
* while parsing the function's body
* pn_tryCount: of try statements in function
*
* <Statements>
* TOK_LC list pn_head: list of pn_count statements
@ -281,7 +280,6 @@ struct JSParseNode {
JSAtom *funAtom; /* atomized function object */
JSParseNode *body; /* TOK_LC list of statements */
uint32 flags; /* accumulated tree context flags */
uint32 tryCount; /* count of try statements in body */
} func;
struct { /* list of next-linked nodes */
JSParseNode *head; /* first node in list */
@ -323,7 +321,6 @@ struct JSParseNode {
#define pn_funAtom pn_u.func.funAtom
#define pn_body pn_u.func.body
#define pn_flags pn_u.func.flags
#define pn_tryCount pn_u.func.tryCount
#define pn_head pn_u.list.head
#define pn_tail pn_u.list.tail
#define pn_count pn_u.list.count

Просмотреть файл

@ -1398,15 +1398,15 @@ js_NewScript(JSContext *cx, uint32 length, uint32 nsrcnotes, uint32 ntrynotes)
JS_FRIEND_API(JSScript *)
js_NewScriptFromCG(JSContext *cx, JSCodeGenerator *cg, JSFunction *fun)
{
uint32 mainLength, prologLength, nsrcnotes, ntrynotes;
uint32 mainLength, prologLength, nsrcnotes;
JSScript *script;
const char *filename;
mainLength = CG_OFFSET(cg);
prologLength = CG_PROLOG_OFFSET(cg);
CG_COUNT_FINAL_SRCNOTES(cg, nsrcnotes);
ntrynotes = (uint32)(cg->tryNext - cg->tryBase);
script = js_NewScript(cx, prologLength + mainLength, nsrcnotes, ntrynotes);
script = js_NewScript(cx, prologLength + mainLength, nsrcnotes,
cg->ntrynotes);
if (!script)
return NULL;

Просмотреть файл

@ -50,11 +50,13 @@
JS_BEGIN_EXTERN_C
/*
* Type of try note associated with each catch block or finally block.
* Type of try note associated with each catch or finally block or with for-in
* loop.
*/
typedef enum JSTryNoteKind {
JSTN_CATCH,
JSTN_FINALLY
JSTN_FINALLY,
JSTN_ITER
} JSTryNoteKind;
/*
@ -64,9 +66,9 @@ struct JSTryNote {
uint8 kind; /* one of JSTryNoteKind */
uint8 padding; /* explicit padding on uint16 boundary */
uint16 stackDepth; /* stack depth upon exception handler entry */
uint32 start; /* start of the try statement relative to
script->main */
uint32 length; /* length of the try statement */
uint32 start; /* start of the try statement or for-in loop
relative to script->main */
uint32 length; /* length of the try statement or for-in loop */
};
typedef struct JSTryNoteArray {