Bug 1448880 - Part 1: Always close a generator on early forced return. r=jimb
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 23 Oct 2018 23:21:01 +0000
changeset 491037 2f3da01d8816a0117e39eca43cd4c52c0a381a4d
parent 491036 faaec607a74f01e81bc5a6b54d0c4141ad5cf8f5
child 491038 64810935a7516678a1e655b8d1034ac8700b53d7
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjimb
bugs1448880
milestone65.0a1
Bug 1448880 - Part 1: Always close a generator on early forced return. r=jimb The previous code failed to close the generator in the case where JSOP_GENERATOR had run but JSOP_INITIAL_YIELD had not, a bit of sloppiness that created yet another special case. Things will get more complicated when we start keeping frames live while suspended; it's better to not have this special case. Differential Revision: https://phabricator.services.mozilla.com/D6982
js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js
js/src/vm/Debugger.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-01.js
@@ -0,0 +1,42 @@
+// The debugger can force an early return from any instruction before the initial yield.
+
+let g = newGlobal();
+g.eval(`
+  function* f() {
+    yield 1;
+  }
+`);
+
+function test(ttl) {
+    let dbg = new Debugger(g);
+    let exiting = false;  // we ran out of time-to-live and have forced return
+    let done = false;  // we reached the initial yield without forced return
+    dbg.onEnterFrame = frame => {
+        assertEq(frame.callee.name, "f");
+        frame.onEnterFrame = undefined;
+        frame.onStep = () => {
+            if (ttl == 0) {
+                exiting = true;
+                // Forced return here causes the generator object, if any, not
+                // to be exposed.
+                return {return: "ponies"};
+            }
+            ttl--;
+        };
+        frame.onPop = completion => {
+            if (!exiting)
+                done = true;
+        };
+    };
+
+    let result = g.f();
+    if (done)
+        assertEq(result instanceof g.f, true);
+    else
+        assertEq(result, "ponies");
+
+    dbg.enabled = false;
+    return done;
+}
+
+for (let ttl = 0; !test(ttl); ttl++) {}
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1538,28 +1538,30 @@ AdjustGeneratorResumptionValue(JSContext
     {
         // Treat `{return: <value>}` like a `return` statement. For generators,
         // that means doing the work below. It's only what the debuggee would
         // do for an ordinary `return` statement--using a few bytecode
         // instructions--and it's simpler to do the work manually than to count
         // on that bytecode sequence existing in the debuggee, somehow jump to
         // it, and then avoid re-entering the debugger from it.
         Rooted<GeneratorObject*> genObj(cx, GetGeneratorObjectForFrame(cx, frame));
-        if (genObj && !genObj->isBeforeInitialYield()) {
+        if (genObj) {
             // 1.  `return <value>` creates and returns a new object,
             //     `{value: <value>, done: true}`.
-            JSObject *pair = CreateIterResultObject(cx, vp, true);
-            if (!pair) {
-                // Out of memory in debuggee code. Arrange for this to propagate.
-                MOZ_ALWAYS_TRUE(cx->getPendingException(vp));
-                cx->clearPendingException();
-                resumeMode = ResumeMode::Throw;
-                return;
-            }
-            vp.setObject(*pair);
+            if (!genObj->isBeforeInitialYield()) {
+                JSObject *pair = CreateIterResultObject(cx, vp, true);
+                if (!pair) {
+                    // Out of memory in debuggee code. Arrange for this to propagate.
+                    MOZ_ALWAYS_TRUE(cx->getPendingException(vp));
+                    cx->clearPendingException();
+                    resumeMode = ResumeMode::Throw;
+                    return;
+                }
+                vp.setObject(*pair);
+            }
 
             // 2.  The generator must be closed.
             genObj->setClosed();
         } else {
             // We're before the initial yield. Carry on with the forced return.
             // The debuggee will see a call to a generator returning the
             // non-generator value *vp.
         }