Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionJIT miscompilation in the JavaScript Engine component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2013560
Fix commit1bd7ef2aa4bd (firefox) +46/-14
CISA KEVNot listed
CreditedEvyatar Ben Asher, Keane Lucas, Nicholas Carlini, Newton Cheng, Daniel Freeman, Alex Gaynor, and Joel Weinberger using Claude from Anthropic
Disclosed2026-03-24

Changed Functions

FunctionChangeNotes
if
js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
modified
while
js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
modified

Files Changed

  • js/src/debugger/Debugger.cpp
  • js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
diff --git a/js/src/debugger/Debugger.cpp b/js/src/debugger/Debugger.cpp
index 7ff7dd00450..616b8e956cc 100644
--- a/js/src/debugger/Debugger.cpp
+++ b/js/src/debugger/Debugger.cpp
@@ -2033,26 +2033,27 @@ Completion Completion::fromJSFramePop(JSContext* cx, AbstractFramePtr frame,
   //
   // GetGeneratorObjectForFrame can return nullptr even when a generator
   // object does exist, if the frame is paused between the Generator and
-  // SetAliasedVar opcodes. But by checking the opcode first we eliminate that
-  // possibility, so it's fine to call genObj->isClosed().
+  // SetAliasedVar opcodes.
   Rooted<AbstractGeneratorObject*> generatorObj(
       cx, GetGeneratorObjectForFrame(cx, frame));
-  switch (JSOp(*pc)) {
-    case JSOp::InitialYield:
-      MOZ_ASSERT(!generatorObj->isClosed());
-      return Completion(InitialYield(generatorObj));
 
-    case JSOp::Yield:
-      MOZ_ASSERT(!generatorObj->isClosed());
-      return Completion(Yield(generatorObj, frame.returnValue()));
+  if (generatorObj && !generatorObj->isClosed()) {
+    switch (JSOp(*pc)) {
+      case JSOp::InitialYield:
+        return Completion(InitialYield(generatorObj));
 
-    case JSOp::Await:
-      MOZ_ASSERT(!generatorObj->isClosed());
-      return Completion(Await(generatorObj, frame.returnValue()));
+      case JSOp::Yield:
+        return Completion(Yield(generatorObj, frame.returnValue()));
 
-    default:
-      return Completion(Return(frame.returnValue()));
+      case JSOp::Await:
+        return Completion(Await(generatorObj, frame.returnValue()));
+
+      default:
+        break;
+    }
   }
+
+  return Completion(Return(frame.returnValue()));
 }
 
 void Completion::trace(JSTracer* trc) {
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
new file mode 100644
index 00000000000..79ddb611f03
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
@@ -0,0 +1,31 @@
+// Don't crash on {return:} from onStep in a generator at a Yield.
+
+// This test force-returns from each bytecode instruction in a generator.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+function* gen() {
+  yield 1;
+}
+`)
+
+let dbg = new Debugger(g);
+
+let targetSteps = 0;
+let found = true;
+dbg.onEnterFrame = (frame) => {
+  let steps = 0;
+  frame.onStep = () => {
+    if (steps++ == targetSteps) {
+      found = true;
+      return { return: 0xdead };
+    }
+  }
+}
+dbg.uncaughtExceptionHook = () => undefined
+
+while (found) {
+  found = false;
+  targetSteps++;
+  for (var y of g.gen()) {}
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
new file mode 100644
index 00000000000..79ddb611f03
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-generator-resumption-04.js
@@ -0,0 +1,31 @@
+// Don't crash on {return:} from onStep in a generator at a Yield.
+
+// This test force-returns from each bytecode instruction in a generator.
+
+let g = newGlobal({ newCompartment: true });
+g.eval(`
+function* gen() {
+  yield 1;
+}
+`)
+
+let dbg = new Debugger(g);
+
+let targetSteps = 0;
+let found = true;
+dbg.onEnterFrame = (frame) => {
+  let steps = 0;
+  frame.onStep = () => {
+    if (steps++ == targetSteps) {
+      found = true;
+      return { return: 0xdead };
+    }
+  }
+}
+dbg.uncaughtExceptionHook = () => undefined
+
+while (found) {
+  found = false;
+  targetSteps++;
+  for (var y of g.gen()) {}
+}
Loading diff…