Bug 1644699 - Allow Debugger.Frame to have GeneratorInfo that is closed. r=arai
authorLogan Smyth <loganfsmyth@gmail.com>
Mon, 15 Jun 2020 14:26:35 +0000
changeset 535699 2d470b425ce9d8fc12c89f087d76ff1a337619ab
parent 535698 d56e40dc9c0f47584569667d25fe81ba76586bbd
child 535700 88d5337be8e0687053897bc0d9e28e3eb5a8a94a
push id119023
push userloganfsmyth@gmail.com
push dateMon, 15 Jun 2020 14:37:01 +0000
treeherderautoland@2d470b425ce9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1644699
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1644699 - Allow Debugger.Frame to have GeneratorInfo that is closed. r=arai The GeneratorInfo data associated with a Debugger.Frame is not guaranteed to be cleared when a generator transitions from running/suspended to closed. In the current codebase, we have broadly assumed that to be true, which was incorrect of us to do. This patch separates "hasGeneratorInfo" from "isSuspended" so that the two usecases can be properly separated. This both fixes this bug, and helps make the codebase clearer about its intent. Differential Revision: https://phabricator.services.mozilla.com/D79603
js/src/debugger/Debugger.cpp
js/src/debugger/Frame-inl.h
js/src/debugger/Frame.cpp
js/src/debugger/Frame.h
js/src/jit-test/tests/debug/bug1644699-terminated-generator.js
--- a/js/src/debugger/Debugger.cpp
+++ b/js/src/debugger/Debugger.cpp
@@ -106,17 +106,17 @@
 #include "vm/WrapperObject.h"         // for CrossCompartmentWrapperObject
 #include "wasm/WasmDebug.h"           // for DebugState
 #include "wasm/WasmInstance.h"        // for Instance
 #include "wasm/WasmJS.h"              // for WasmInstanceObject
 #include "wasm/WasmRealm.h"           // for Realm
 #include "wasm/WasmTypes.h"           // for WasmInstanceObjectVector
 
 #include "debugger/DebugAPI-inl.h"
-#include "debugger/Frame-inl.h"    // for DebuggerFrame::hasGenerator
+#include "debugger/Frame-inl.h"    // for DebuggerFrame::hasGeneratorInfo
 #include "debugger/Script-inl.h"   // for DebuggerScript::getReferent
 #include "gc/GC-inl.h"             // for ZoneCellIter
 #include "gc/Marking-inl.h"        // for MaybeForwarded
 #include "gc/WeakMap-inl.h"        // for DebuggerWeakMap::trace
 #include "vm/Compartment-inl.h"    // for Compartment::wrap
 #include "vm/GeckoProfiler-inl.h"  // for AutoSuppressProfilerSampling
 #include "vm/JSAtom-inl.h"         // for AtomToId, ValueToId
 #include "vm/JSContext-inl.h"      // for JSContext::check
@@ -676,17 +676,17 @@ bool Debugger::getFrame(JSContext* cx, c
 
       if (!ensureExecutionObservabilityOfFrame(cx, referent)) {
         return false;
       }
     }
 
     if (!frames.add(p, referent, frame)) {
       frame->freeFrameIterData(cx->defaultFreeOp());
-      frame->clearGenerator(cx->runtime()->defaultFreeOp(), this);
+      frame->clearGeneratorInfo(cx->runtime()->defaultFreeOp(), this);
       ReportOutOfMemory(cx);
       return false;
     }
   }
 
   result.set(p->value());
   return true;
 }
@@ -1199,17 +1199,17 @@ bool DebugAPI::slowPathOnNewGenerator(JS
     if (!ok) {
       return;
     }
 
     RootedDebuggerFrame frameObj(cx, frameObjPtr);
     {
       AutoRealm ar(cx, frameObj);
 
-      if (!frameObj->setGenerator(cx, genObj)) {
+      if (!frameObj->setGeneratorInfo(cx, genObj)) {
         ReportOutOfMemory(cx);
 
         // This leaves `genObj` and `frameObj` unassociated. It's OK
         // because we won't pause again with this generator on the stack:
         // the caller will immediately discard `genObj` and unwind `frame`.
         ok = false;
       }
     }
@@ -3828,17 +3828,17 @@ void DebugAPI::sweepAll(JSFreeOp* fop) {
     //
     // Since DebugAPI::sweepAll is called after everything is marked, but before
     // anything has been finalized, this is the perfect place to drop the count.
     if (dbg->zone()->isGCSweeping()) {
       for (Debugger::GeneratorWeakMap::Enum e(dbg->generatorFrames); !e.empty();
            e.popFront()) {
         DebuggerFrame* frameObj = e.front().value();
         if (IsAboutToBeFinalizedUnbarriered(&frameObj)) {
-          frameObj->clearGenerator(fop, dbg, &e);
+          frameObj->clearGeneratorInfo(fop, dbg, &e);
         }
       }
     }
 
     // Detach dying debuggers and debuggees from each other. Since this
     // requires access to both objects it must be done before either
     // object is finalized.
     bool debuggerDying = IsAboutToBeFinalized(&dbg->object);
@@ -4727,17 +4727,17 @@ void Debugger::removeDebuggeeGlobal(JSFr
   // is going away, it doesn't care about the state of its generatorFrames
   // table, and the Debugger.Frame finalizer will fix up the generator observer
   // counts.
   if (fromSweep == FromSweep::No) {
     for (GeneratorWeakMap::Enum e(generatorFrames); !e.empty(); e.popFront()) {
       AbstractGeneratorObject& genObj = *e.front().key();
       DebuggerFrame& frameObj = *e.front().value();
       if (genObj.isClosed() || &genObj.callee().global() == global) {
-        frameObj.clearGenerator(fop, this, &e);
+        frameObj.clearGeneratorInfo(fop, this, &e);
       }
     }
   }
 
   auto& globalDebuggersVector = global->getDebuggers();
 
   // The relation must be removed from up to three places:
   // globalDebuggersVector and debuggees for sure, and possibly the
@@ -5997,17 +5997,17 @@ bool Debugger::CallData::adoptFrame() {
     FrameIter iter(*frameObj->frameIterData());
     if (!dbg->observesFrame(iter)) {
       JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
       return false;
     }
     if (!dbg->getFrame(cx, iter, &adoptedFrame)) {
       return false;
     }
-  } else if (frameObj->hasGenerator()) {
+  } else if (frameObj->isSuspended()) {
     Rooted<AbstractGeneratorObject*> gen(cx, &frameObj->unwrappedGenerator());
     if (!dbg->observesGlobal(&gen->global())) {
       JS_ReportErrorASCII(cx, "Debugger.Frame's global is not a debuggee");
       return false;
     }
 
     if (!dbg->getFrame(cx, gen, &adoptedFrame)) {
       return false;
@@ -6346,29 +6346,29 @@ void Debugger::removeFromFrameMapsAndCle
                                                         bool suspending) {
   forEachDebuggerFrame(frame, [&](DebuggerFrame* frameobj) {
     JSFreeOp* fop = cx->runtime()->defaultFreeOp();
     frameobj->freeFrameIterData(fop);
 
     Debugger* dbg = Debugger::fromChildJSObject(frameobj);
     dbg->frames.remove(frame);
 
-    if (frameobj->hasGenerator()) {
+    if (frameobj->hasGeneratorInfo()) {
       // If this is a generator's final pop, remove its entry from
       // generatorFrames. Such an entry exists if and only if the
       // Debugger.Frame's generator has been set.
       if (!suspending) {
         // Terminally exiting a generator.
 #if DEBUG
         AbstractGeneratorObject& genObj = frameobj->unwrappedGenerator();
         GeneratorWeakMap::Ptr p = dbg->generatorFrames.lookup(&genObj);
         MOZ_ASSERT(p);
         MOZ_ASSERT(p->value() == frameobj);
 #endif
-        frameobj->clearGenerator(fop, dbg);
+        frameobj->clearGeneratorInfo(fop, dbg);
       }
     } else {
       frameobj->maybeDecrementStepperCounter(fop, frame);
     }
   });
 
   // If this is an eval frame, then from the debugger's perspective the
   // script is about to be destroyed. Remove any breakpoints in it.
--- a/js/src/debugger/Frame-inl.h
+++ b/js/src/debugger/Frame-inl.h
@@ -8,20 +8,20 @@
 #define debugger_Frame_inl_h
 
 #include "debugger/Frame.h"  // for DebuggerFrame
 
 #include "mozilla/Assertions.h"  // for AssertionConditionType, MOZ_ASSERT
 
 #include "NamespaceImports.h"  // for Value
 
-inline bool js::DebuggerFrame::hasGenerator() const {
+inline bool js::DebuggerFrame::hasGeneratorInfo() const {
   return !getReservedSlot(GENERATOR_INFO_SLOT).isUndefined();
 }
 
 inline js::DebuggerFrame::GeneratorInfo* js::DebuggerFrame::generatorInfo()
     const {
-  MOZ_ASSERT(hasGenerator());
+  MOZ_ASSERT(hasGeneratorInfo());
   return static_cast<GeneratorInfo*>(
       getReservedSlot(GENERATOR_INFO_SLOT).toPrivate());
 }
 
 #endif /* debugger_Frame_inl_h */
--- a/js/src/debugger/Frame.cpp
+++ b/js/src/debugger/Frame.cpp
@@ -250,17 +250,17 @@ DebuggerFrame* DebuggerFrame::create(
     if (!data) {
       return nullptr;
     }
 
     frame->setFrameIterData(data);
   }
 
   if (maybeGenerator) {
-    if (!frame->setGenerator(cx, maybeGenerator)) {
+    if (!frame->setGeneratorInfo(cx, maybeGenerator)) {
       return nullptr;
     }
   }
 
   return frame;
 }
 
 /**
@@ -328,28 +328,33 @@ class DebuggerFrame::GeneratorInfo {
 
   JSScript* generatorScript() { return generatorScript_; }
 
   bool isGeneratorScriptAboutToBeFinalized() {
     return IsAboutToBeFinalized(&generatorScript_);
   }
 };
 
+bool js::DebuggerFrame::isSuspended() const {
+  return hasGeneratorInfo() &&
+         generatorInfo()->unwrappedGenerator().isSuspended();
+}
+
 js::AbstractGeneratorObject& js::DebuggerFrame::unwrappedGenerator() const {
   return generatorInfo()->unwrappedGenerator();
 }
 
 #ifdef DEBUG
 JSScript* js::DebuggerFrame::generatorScript() const {
   return generatorInfo()->generatorScript();
 }
 #endif
 
-bool DebuggerFrame::setGenerator(JSContext* cx,
-                                 Handle<AbstractGeneratorObject*> genObj) {
+bool DebuggerFrame::setGeneratorInfo(JSContext* cx,
+                                     Handle<AbstractGeneratorObject*> genObj) {
   cx->check(this);
 
   Debugger::GeneratorWeakMap::AddPtr p =
       owner()->generatorFrames.lookupForAdd(genObj);
   if (p) {
     MOZ_ASSERT(p->value() == this);
     MOZ_ASSERT(&unwrappedGenerator() == genObj);
     return true;
@@ -398,18 +403,18 @@ bool DebuggerFrame::setGenerator(JSConte
                    MemoryUse::DebuggerFrameGeneratorInfo);
 
   generatorFramesGuard.release();
   infoGuard.release();
 
   return true;
 }
 
-void DebuggerFrame::clearGenerator(JSFreeOp* fop) {
-  if (!hasGenerator()) {
+void DebuggerFrame::clearGeneratorInfo(JSFreeOp* fop) {
+  if (!hasGeneratorInfo()) {
     return;
   }
 
   GeneratorInfo* info = generatorInfo();
 
   // 3) The generator's script's observer count must be dropped.
   //
   // For ordinary calls, Debugger.Frame objects drop the script's stepper count
@@ -423,46 +428,46 @@ void DebuggerFrame::clearGenerator(JSFre
     maybeDecrementStepperCounter(fop, generatorScript);
   }
 
   // 1) The DebuggerFrame must no longer point to the AbstractGeneratorObject.
   setReservedSlot(GENERATOR_INFO_SLOT, UndefinedValue());
   fop->delete_(this, info, MemoryUse::DebuggerFrameGeneratorInfo);
 }
 
-void DebuggerFrame::clearGenerator(
+void DebuggerFrame::clearGeneratorInfo(
     JSFreeOp* fop, Debugger* owner,
     Debugger::GeneratorWeakMap::Enum* maybeGeneratorFramesEnum) {
-  if (!hasGenerator()) {
+  if (!hasGeneratorInfo()) {
     return;
   }
 
   // 2) generatorFrames must no longer map the AbstractGeneratorObject to the
   // DebuggerFrame.
   GeneratorInfo* info = generatorInfo();
   if (maybeGeneratorFramesEnum) {
     maybeGeneratorFramesEnum->removeFront();
   } else {
     owner->generatorFrames.remove(&info->unwrappedGenerator());
   }
 
-  clearGenerator(fop);
+  clearGeneratorInfo(fop);
 }
 
 /* static */
 bool DebuggerFrame::getCallee(JSContext* cx, HandleDebuggerFrame frame,
                               MutableHandleDebuggerObject result) {
   RootedObject callee(cx);
   if (frame->isOnStack()) {
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
     if (referent.isFunctionFrame()) {
       callee = referent.callee();
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     callee = &frame->generatorInfo()->unwrappedGenerator().callee();
   }
 
   return frame->owner()->wrapNullableDebuggeeObject(cx, callee, result);
 }
 
 /* static */
@@ -472,17 +477,17 @@ bool DebuggerFrame::getIsConstructing(JS
     Maybe<FrameIter> maybeIter;
     if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter)) {
       return false;
     }
     FrameIter& iter = *maybeIter;
 
     result = iter.isFunctionFrame() && iter.isConstructing();
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     // Generators and async functions can't be constructed.
     result = false;
   }
   return true;
 }
 
 static void UpdateFrameIterPc(FrameIter& iter) {
@@ -546,17 +551,17 @@ bool DebuggerFrame::getEnvironment(JSCon
     FrameIter& iter = *maybeIter;
 
     {
       AutoRealm ar(cx, iter.abstractFramePtr().environmentChain());
       UpdateFrameIterPc(iter);
       env = GetDebugEnvironmentForFrame(cx, iter.abstractFramePtr(), iter.pc());
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     AbstractGeneratorObject& genObj =
         frame->generatorInfo()->unwrappedGenerator();
     JSScript* script = frame->generatorInfo()->generatorScript();
 
     {
       AutoRealm ar(cx, &genObj.environmentChain());
       env = GetDebugEnvironmentForSuspendedGenerator(cx, script, genObj);
@@ -586,17 +591,17 @@ bool DebuggerFrame::getOffset(JSContext*
       result = iter.wasmBytecodeOffset();
     } else {
       JSScript* script = iter.script();
       UpdateFrameIterPc(iter);
       jsbytecode* pc = iter.pc();
       result = script->pcToOffset(pc);
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     AbstractGeneratorObject& genObj =
         frame->generatorInfo()->unwrappedGenerator();
     JSScript* script = frame->generatorInfo()->generatorScript();
     result = script->resumeOffsets()[genObj.resumeIndex()];
   }
   return true;
 }
@@ -634,29 +639,33 @@ bool DebuggerFrame::getOlder(JSContext* 
       if (dbg->observesFrame(iter)) {
         if (iter.isIon() && !iter.ensureHasRematerializedFrame(cx)) {
           return false;
         }
         return dbg->getFrame(cx, iter, result);
       }
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     // If the frame is suspended, there is no older frame.
   }
 
   result.set(nullptr);
   return true;
 }
 
 /* static */
 bool DebuggerFrame::getAsyncPromise(JSContext* cx, HandleDebuggerFrame frame,
                                     MutableHandleDebuggerObject result) {
-  if (!frame->hasGenerator()) {
+  MOZ_ASSERT(frame->isOnStack() || frame->isSuspended());
+
+  if (!frame->hasGeneratorInfo()) {
+    // An on-stack frame may not have an associated generator yet when the
+    // frame is initially entered.
     result.set(nullptr);
     return true;
   }
 
   RootedObject resultObject(cx);
   AbstractGeneratorObject& generator = frame->unwrappedGenerator();
   if (generator.is<AsyncFunctionGeneratorObject>()) {
     resultObject = generator.as<AsyncFunctionGeneratorObject>().promise();
@@ -697,17 +706,17 @@ bool DebuggerFrame::getThis(JSContext* c
       UpdateFrameIterPc(iter);
 
       if (!GetThisValueForDebuggerFrameMaybeOptimizedOut(cx, frame, iter.pc(),
                                                          result)) {
         return false;
       }
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     AbstractGeneratorObject& genObj =
         frame->generatorInfo()->unwrappedGenerator();
     JSScript* script = frame->generatorInfo()->generatorScript();
 
     if (!GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut(
             cx, genObj, script, result)) {
       return false;
@@ -739,17 +748,17 @@ DebuggerFrameType DebuggerFrame::getType
     if (referent.isModuleFrame()) {
       return DebuggerFrameType::Module;
     }
 
     if (referent.isWasmDebugFrame()) {
       return DebuggerFrameType::WasmCall;
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
 
     return DebuggerFrameType::Call;
   }
 
   MOZ_CRASH("Unknown frame type");
 }
 
 /* static */
@@ -794,17 +803,17 @@ bool DebuggerFrame::setOnStepHandler(JSC
     if (handler) {
       if (!frame->maybeIncrementStepperCounter(cx, referent)) {
         return false;
       }
     } else {
       frame->maybeDecrementStepperCounter(cx->runtime()->defaultFreeOp(),
                                           referent);
     }
-  } else if (frame->hasGenerator()) {
+  } else if (frame->isSuspended()) {
     RootedScript script(cx, frame->generatorInfo()->generatorScript());
 
     if (handler) {
       if (!frame->maybeIncrementStepperCounter(cx, script)) {
         return false;
       }
     } else {
       frame->maybeDecrementStepperCounter(cx->runtime()->defaultFreeOp(),
@@ -1236,17 +1245,17 @@ void DebuggerFrame::freeFrameIterData(JS
 /* static */
 void DebuggerFrame::finalize(JSFreeOp* fop, JSObject* obj) {
   MOZ_ASSERT(fop->onMainThread());
 
   DebuggerFrame& frameobj = obj->as<DebuggerFrame>();
 
   // Connections between dying Debugger.Frames and their
   // AbstractGeneratorObjects should have been broken in DebugAPI::sweepAll.
-  MOZ_ASSERT(!frameobj.hasGenerator());
+  MOZ_ASSERT(!frameobj.hasGeneratorInfo());
 
   frameobj.freeFrameIterData(fop);
   OnStepHandler* onStepHandler = frameobj.onStepHandler();
   if (onStepHandler) {
     onStepHandler->drop(fop, &frameobj);
   }
   OnPopHandler* onPopHandler = frameobj.onPopHandler();
   if (onPopHandler) {
@@ -1259,17 +1268,17 @@ void DebuggerFrame::trace(JSTracer* trc)
   if (onStepHandler) {
     onStepHandler->trace(trc);
   }
   OnPopHandler* onPopHandler = this->onPopHandler();
   if (onPopHandler) {
     onPopHandler->trace(trc);
   }
 
-  if (hasGenerator()) {
+  if (hasGeneratorInfo()) {
     generatorInfo()->trace(trc, *this);
   }
 }
 
 /* static */
 DebuggerFrame* DebuggerFrame::check(JSContext* cx, HandleValue thisv) {
   JSObject* thisobj = RequireObject(cx, thisv);
   if (!thisobj) {
@@ -1362,17 +1371,17 @@ static bool EnsureOnStack(JSContext* cx,
                               JSMSG_DEBUG_NOT_ON_STACK, "Debugger.Frame");
     return false;
   }
 
   return true;
 }
 static bool EnsureOnStackOrSuspended(JSContext* cx, HandleDebuggerFrame frame) {
   MOZ_ASSERT(frame);
-  if (!frame->isOnStack() && !frame->hasGenerator()) {
+  if (!frame->isOnStack() && !frame->isSuspended()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_DEBUG_NOT_ON_STACK_OR_SUSPENDED,
                               "Debugger.Frame");
     return false;
   }
 
   return true;
 }
@@ -1509,17 +1518,17 @@ bool DebuggerFrame::CallData::asyncPromi
   if (frame->isOnStack()) {
     FrameIter iter(*frame->frameIterData());
     AbstractFramePtr framePtr = iter.abstractFramePtr();
 
     if (!framePtr.isWasmDebugFrame()) {
       script = framePtr.script();
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
     script = frame->generatorInfo()->generatorScript();
   }
   // The async promise value is only provided for async functions and
   // async generator functions.
   if (!script || !script->isAsync()) {
     args.rval().setUndefined();
     return true;
   }
@@ -1586,17 +1595,17 @@ bool DebuggerFrame::getOlderSavedFrame(J
 
       // If we hit another frame that we observe, then there is no saved
       // frame that we'd want to return.
       if (dbg->observesFrame(iter)) {
         break;
       }
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
   }
 
   result.set(nullptr);
   return true;
 }
 
 bool DebuggerFrame::CallData::thisGetter() {
   if (!ensureOnStackOrSuspended()) {
@@ -1762,17 +1771,17 @@ bool DebuggerFrame::CallData::getScript(
     if (framePtr.isWasmDebugFrame()) {
       RootedWasmInstanceObject instance(cx, framePtr.wasmInstance()->object());
       scriptObject = debug->wrapWasmScript(cx, instance);
     } else {
       RootedScript script(cx, framePtr.script());
       scriptObject = debug->wrapScript(cx, script);
     }
   } else {
-    MOZ_ASSERT(frame->hasGenerator());
+    MOZ_ASSERT(frame->isSuspended());
     RootedScript script(cx, frame->generatorInfo()->generatorScript());
     scriptObject = debug->wrapScript(cx, script);
   }
   if (!scriptObject) {
     return false;
   }
 
   args.rval().setObject(*scriptObject);
@@ -1800,17 +1809,17 @@ bool DebuggerFrame::CallData::liveGetter
 }
 
 bool DebuggerFrame::CallData::onStackGetter() {
   args.rval().setBoolean(frame->isOnStack());
   return true;
 }
 
 bool DebuggerFrame::CallData::terminatedGetter() {
-  args.rval().setBoolean(!frame->isOnStack() && !frame->hasGenerator());
+  args.rval().setBoolean(!frame->isOnStack() && !frame->isSuspended());
   return true;
 }
 
 static bool IsValidHook(const Value& v) {
   return v.isUndefined() || (v.isObject() && v.toObject().isCallable());
 }
 
 bool DebuggerFrame::CallData::onStepGetter() {
--- a/js/src/debugger/Frame.h
+++ b/js/src/debugger/Frame.h
@@ -190,23 +190,25 @@ class DebuggerFrame : public NativeObjec
 
   static MOZ_MUST_USE DebuggerFrame* check(JSContext* cx, HandleValue thisv);
 
   bool isOnStack() const;
 
   // Like isOnStack, but works even in the midst of a relocating GC.
   bool isOnStackMaybeForwarded() const;
 
+  bool isSuspended() const;
+
   OnStepHandler* onStepHandler() const;
   OnPopHandler* onPopHandler() const;
   void setOnPopHandler(JSContext* cx, OnPopHandler* handler);
 
-  inline bool hasGenerator() const;
+  inline bool hasGeneratorInfo() const;
 
-  // If hasGenerator(), return an direct cross-compartment reference to this
+  // If hasGeneratorInfo(), return an direct cross-compartment reference to this
   // Debugger.Frame's generator object.
   AbstractGeneratorObject& unwrappedGenerator() const;
 
 #ifdef DEBUG
   JSScript* generatorScript() const;
 #endif
 
   /*
@@ -221,36 +223,36 @@ class DebuggerFrame : public NativeObjec
    * Technically, the generator activation need not actually be on the stack
    * right now; it's okay to call this method on a Debugger.Frame that has no
    * ScriptFrameIter::Data at present. However, this function has no way to
    * verify that genObj really is the generator associated with the call for
    * which this Debugger.Frame was originally created, so it's best to make the
    * association while the call is on the stack, and the relationships are easy
    * to discern.
    */
-  MOZ_MUST_USE bool setGenerator(JSContext* cx,
-                                 Handle<AbstractGeneratorObject*> genObj);
+  MOZ_MUST_USE bool setGeneratorInfo(JSContext* cx,
+                                     Handle<AbstractGeneratorObject*> genObj);
 
   /*
    * Undo the effects of a prior call to setGenerator.
    *
    * If provided, owner must be the Debugger to which this Debugger.Frame
    * belongs; remove this frame's entry from its generatorFrames map, and clean
    * up its cross-compartment wrapper table entry. The owner must be passed
    * unless this method is being called from the Debugger.Frame's finalizer. (In
    * that case, the owner is not reliably available, and is not actually
    * necessary.)
    *
    * If maybeGeneratorFramesEnum is non-null, use it to remove this frame's
    * entry from the Debugger's generatorFrames weak map. In this case, this
    * function will not otherwise disturb generatorFrames. Passing the enum
    * allows this function to be used while iterating over generatorFrames.
    */
-  void clearGenerator(JSFreeOp* fop);
-  void clearGenerator(
+  void clearGeneratorInfo(JSFreeOp* fop);
+  void clearGeneratorInfo(
       JSFreeOp* fop, Debugger* owner,
       Debugger::GeneratorWeakMap::Enum* maybeGeneratorFramesEnum = nullptr);
 
   /*
    * Called after a generator/async frame is resumed, before exposing this
    * Debugger.Frame object to any hooks.
    */
   bool resume(const FrameIter& iter);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1644699-terminated-generator.js
@@ -0,0 +1,79 @@
+// |jit-test| exitstatus:6
+// Ensure that a frame terminated due to an interrupt in the generator
+// builtin will properly be treated as terminated.
+
+const g = newGlobal({ newCompartment: true });
+const dbg = new Debugger(g);
+g.eval(`
+var done = false;
+async function* f() {
+  await null;
+  await null;
+  await null;
+  await null;
+  done = true;
+}
+`);
+
+dbg.onEnterFrame = f => {
+  frame = f;
+  dbg.onEnterFrame = undefined;
+};
+
+setInterruptCallback(function() {
+  const stack = saveStack();
+
+  // We want to explicitly terminate inside the AsyncGeneratorNext builtin
+  // when it tries to resume execution at the 'await' in the async generator.
+  // Terminating inside AsyncGeneratorNext causes the generator to be closed,
+  // and for this test case we specifically need that to happen without
+  // entering the async generator frame because we're aiming to trigger the
+  // case where DebugAPI::onLeaveFrame does not having the opportunity to
+  // clean up the generator information associated with the Debugger.Frame.
+  if (
+    stack.parent &&
+    stack.parent.source === "self-hosted" &&
+    stack.parent.functionDisplayName === "AsyncGeneratorNext" &&
+    stack.parent.parent &&
+    stack.parent.parent.source === stack.source &&
+    stack.parent.parent.line === DRAIN_QUEUE_LINE
+  ) {
+    return false;
+  }
+
+  // Keep interrupting until we find the right place.
+  interruptIf(true);
+  return true;
+});
+
+// Run the async generator and suspend at the first await.
+const it = g.f();
+let promise = it.next();
+
+// Queue the interrupt so that it will start trying to terminate inside the
+// generator builtin.
+interruptIf(true);
+
+// Explicitly drain the queue to run the async generator to completion.
+const DRAIN_QUEUE_LINE = saveStack().line + 1;
+drainJobQueue();
+
+let threw = false;
+try {
+  // In the original testcase for this bug, this call would cause
+  // an assertion failure because the generator was closed.
+  frame.environment;
+} catch (err) {
+  threw = true;
+}
+assertEq(threw, true);
+
+// The frame here still has a GeneratorInfo datastructure because the
+// termination interrupt will cause the generator to be closed without
+// clearing that data. The frame must still be treated as terminated in
+// this case in order for the Debugger API to behave consistently.
+assertEq(frame.terminated, true);
+
+// We should never reach the end of the async generator because it will
+// have been terminated.
+assertEq(g.done, false);