Closed Bug 958949 Opened 11 years ago Closed 10 years ago

yield in finally overrides return completion from try

Categories

(Core :: JavaScript Engine, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla37

People

(Reporter: anba, Assigned: arai)

References

(Blocks 1 open bug)

Details

Attachments

(1 file, 2 obsolete files)

Maybe related to bug 718531? Test case: --- js> function*g(){ try { return 0 } finally { yield 1 } } js> o = g(); o.next(); o.next() Expected: ({value:0, done:true}) Actual: ({value:1, done:false}) ---
Can you point out where the spec says that your expected result is correct? By my reading, this is correct behavior: - `return 0` sets the rval to `0` - the finally block is executed - `yield 1` sets the rval to the result of `GeneratorYield(CreateIterResultObject(1, false))` - control is returned to the callee, with the current rval as the returned value - the callee interprets the returned value as always
There are two calls to next() in the example code. The first call does return the correct result, i.e. {value: 1, done: false}, but the second call does not: --- js> function*g(){ try { return 0 } finally { yield 1 } } js> o = g() ({}) js> o.next() ({value:1, done:false}) js> o.next() ({value:1, done:false}) js> o.next() typein:5:0 TypeError: generator has already finished ---
Blocks: es6
Blocks: es6:let
This shouldn't be blocking bug 950547 now that generators are out of scope of it; I'm removing it.
No longer blocks: es6:let
Now situation is a bit different, but still incorrect. --- js> function*g(){ try { return 0 } finally { yield 1 } } js> o = g(); ({}) js> o.next() ({value:1, done:false}) js> o.next() js> o.next() ({value:(void 0), done:true}) --- the second next() returns undefined.
(In reply to Tooru Fujisawa [:arai] from comment #4) > the second next() returns undefined. Oh, this is interesting. What happens is this: (1) For the "return 0" we set the frame's rval slot to {value: 0, done: true}. We do *not* return. (2) We execute the finally block. This yields {value: 1, done: false}. (3) We resume and RETSUB returns to the return inside the try block, which does finalyieldrval but rval is |undefined| because we didn't save/restore the rval slot as part of (2). This is most likely a regression from bug 987560. We could add a GeneratorObject slot for the frame's rval but it'd be nice if we could avoid that.
Flags: needinfo?(jdemooij)
Added .genrval local variable (like .generator variable), save return value into it before executing finally-block, and restore the value from it after retsub. Also, changed PNK_RETURN to binary, to store name node of .genrval, which is needed to emit JSOP_SETALIASEDVAR, where hops might be != 0. Now bytecode for generator function `function*g(){ try { return 0 } finally { yield 1 } }` is following: --- 00000: generator 00001: setaliasedvar ".generator" (hops = 0, slot = 2) 00006: initialyield 0 00010: debugafteryield 00011: pop 00012: try 00013: newobject ({value:(void 0), done:(void 0)}) 00018: zero 00019: initprop "value" 00024: true 00025: initprop "done" 00030: setaliasedvar ".genrval" (hops = 0, slot = 3) // save return value 00035: pop 00036: gosub 63 (+27) 00041: getaliasedvar ".genrval" (hops = 0, slot = 3) // restore return value 00046: setrval 00047: getaliasedvar ".generator" (hops = 0, slot = 2) 00052: finalyieldrval 00053: gosub 63 (+10) 00058: goto 94 (+36) 00063: finally 00064: newobject ({value:(void 0), done:(void 0)}) 00069: one 00070: initprop "value" 00075: false 00076: initprop "done" 00081: getaliasedvar ".generator" (hops = 0, slot = 2) 00086: yield 1 00090: debugafteryield 00091: pop 00092: retsub 00093: nop 00094: newobject ({value:(void 0), done:(void 0)}) 00099: undefined 00100: initprop "value" 00105: true 00106: initprop "done" 00111: setrval 00112: getaliasedvar ".generator" (hops = 0, slot = 2) 00117: finalyieldrval 00118: retrval --- No bytecode change happened if there is no finally block. Green on try run: https://treeherder.mozilla.org/ui/#/jobs?repo=try&revision=862649f14379
Attachment #8531282 - Flags: review?(jdemooij)
just rebased :)
Attachment #8531282 - Attachment is obsolete: true
Attachment #8531282 - Flags: review?(jdemooij)
Attachment #8533519 - Flags: review?(jdemooij)
Comment on attachment 8533519 [details] [diff] [review] Save return value for generator function into local variable before running finally-block. Review of attachment 8533519 [details] [diff] [review]: ----------------------------------------------------------------- Hi arai, I'm very sorry for the delay. Excellent fix, thanks so much for taking this! Just some small comments, if you can post an updated patch I'll review quickly. ::: js/src/frontend/BytecodeEmitter.cpp @@ +5585,5 @@ > return EmitGoto(cx, bce, stmt, &stmt->continues, SRC_CONTINUE) >= 0; > } > > static bool > +hasFinally(BytecodeEmitter *bce) Style nit: HasFinally Also STMT_FINALLY is confusing because it's pushed when we're emitting the try-code, not the finally-block itself. So to make this more obvious, maybe InTryBlockWithFinally()? @@ +5637,5 @@ > bool isGenerator = bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isGenerator(); > + bool useGenRVal = false; > + if (isGenerator) { > + if (bce->sc->asFunctionBox()->isStarGenerator() && hasFinally(bce)) { > + useGenRVal = true; Nit: I'd add a short comment here to explain to the reader what's going on: Emit JSOP_SETALIASEDVAR .genrval to store the return value on the scope chain, so it's not lost when we yield in a finally block. ::: js/src/frontend/Parser.cpp @@ +1086,5 @@ > return null(); > if (!pc->define(tokenStream, context->names().dotGenerator, generator, Definition::VAR)) > return null(); > > + if (pc->generatorKind() == StarGenerator) { Nit: if (pc->isStarGenerator()) @@ +3119,5 @@ > * can potentially override any static bindings introduced by statements > * further up the stack, we have to abort the search. > */ > + if (stmt->type == STMT_WITH && atom != ct->sc->context->names().dotGenerator && > + atom != ct->sc->context->names().dotGenRVal) { Can you add a helper function somewhere to check if a name is dotGenerator or dotGenRVal and call that everywhere? Like IsDotVariable or something better. (Either make it a static helper function with an ExclusiveContext* argument, or add a method to sc and use context.) ::: js/src/jit-test/tests/generators/bug958949.js @@ +6,5 @@ > +function* g1() { > + try { > + return 42; > + } finally { > + yield 43; Great tests, thank you.
Attachment #8533519 - Flags: review?(jdemooij)
Flags: needinfo?(jdemooij)
Thank you for reviewing! :) (In reply to Jan de Mooij [:jandem] from comment #8) > @@ +3119,5 @@ > > * can potentially override any static bindings introduced by statements > > * further up the stack, we have to abort the search. > > */ > > + if (stmt->type == STMT_WITH && atom != ct->sc->context->names().dotGenerator && > > + atom != ct->sc->context->names().dotGenRVal) { > > Can you add a helper function somewhere to check if a name is dotGenerator > or dotGenRVal and call that everywhere? Like IsDotVariable or something > better. > > (Either make it a static helper function with an ExclusiveContext* argument, > or add a method to sc and use context.) Added IsDotVariable to SharedContext, and used the context property of itself to access `names()`, In following 3 functions, previously different `ExclusiveContext *` variable was used. * Parser<FullParseHandler>::withStatement -- Parser::context * LegacyCompExprTransplanter::transplant -- Parser::context * EmitAtomOp -- cx parameter Since `ThreadSafeContext::names()` returns JSRuntime::commonNames, they still return same JSAtomState instance even if those pointers refer different context instance, as long as those contexts are created in same JSRuntime. Is it possible situation that they are created in different JSRuntime's? if so, this patch is wrong, and we should add a helper function in some header file. Also, renamed test file to "yield-in-finally.js", and added one more test case with "with" statement. Green on try run: https://treeherder.mozilla.org/ui/#/jobs?repo=try&revision=09ab93fc9aab
Attachment #8533519 - Attachment is obsolete: true
Attachment #8536957 - Flags: review?(jdemooij)
Comment on attachment 8536957 [details] [diff] [review] Save return value for generator function into local variable before running finally-block. Review of attachment 8536957 [details] [diff] [review]: ----------------------------------------------------------------- Great work, thanks again. ::: js/src/frontend/SharedContext.h @@ +206,5 @@ > bool needStrictChecks() { > return strict || extraWarnings; > } > + > + bool IsDotVariable(JSAtom *atom) { Style nit: it's a class method so it should be isDotVariable. Also I think you can make it const: bool isDotVariable(JSAtom *atom) const {
Attachment #8536957 - Flags: review?(jdemooij) → review+
Assignee: nobody → arai_a
Status: NEW → RESOLVED
Closed: 10 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla37
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: