High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionJIT miscompilation in the JavaScript Engine: JIT component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2052207
Fix commitdc03ed6e0053 (firefox) +107/-0
CISA KEVNot listed
CreditedAmy Burnett of OpenAI
Disclosed2026-07-21

Changed Functions

FunctionChangeNotes
for
js/src/jit/IonAnalysis.cpp
modified
if
js/src/jit/IonAnalysis.cpp
modified

Files Changed

  • js/src/jit/IonAnalysis.cpp
  • js/src/jit/MacroAssembler.cpp
diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp
index a2c88692e66..36597cc8752 100644
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -14,6 +14,7 @@
 #include "jit/DominatorTree.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
+#include "js/HashTable.h"
 
 #include "vm/BytecodeUtil-inl.h"
 
@@ -2485,6 +2486,85 @@ static MObjectToIterator* FindObjectToIteratorUse(MDefinition* ins) {
   return nullptr;
 }
 
+using IteratorMoreSet =
+    InlineSet<MIteratorMore*, 8, DefaultHasher<MIteratorMore*>,
+              BackgroundSystemAllocPolicy>;
+
+static bool FindSafeIteratorMoreInstructions(MIRGraph& graph,
+                                             IteratorMoreSet& safeIterMores) {
+  // Fill |safeIterMores| with MIteratorMore instructions where no instruction
+  // use is dominated by an MIteratorEnd for the same iterator.
+
+  using InstructionVector =
+      Vector<MInstruction*, 8, BackgroundSystemAllocPolicy>;
+
+  auto hasDominatingIteratorEnd = [](const InstructionVector& iteratorEnds,
+                                     MInstruction* access) {
+    for (MInstruction* iteratorEnd : iteratorEnds) {
+      if (iteratorEnd->dominates(access)) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  for (MBasicBlockIterator block(graph.begin()); block != graph.end();
+       block++) {
+    for (MInstructionIterator ins(block->begin()); ins != block->end(); ins++) {
+      if (!ins->isObjectToIterator()) {
+        continue;
+      }
+
+      InstructionVector iteratorMores;
+      InstructionVector iteratorEnds;
+      bool hasPhiUse = false;
+
+      for (MUseDefIterator uses(*ins); uses; uses++) {
+        MDefinition* def = uses.def();
+        if (def->isIteratorMore()) {
+          if (!iteratorMores.append(def->toInstruction())) {
+            return false;
+          }
+        } else if (def->isIteratorEnd()) {
+          if (!iteratorEnds.append(def->toInstruction())) {
+            return false;
+          }
+        } else if (def->isLoadIteratorElement() ||
+                   def->isObjectKeysFromIterator() || def->isIteratorLength() ||
+                   def->isPostWriteBarrier() || def->isStoreElement()) {
+          continue;
+        } else if (def->isPhi()) {
+          hasPhiUse = true;
+          break;
+        } else {
+          MOZ_CRASH("Unexpected ObjectToIterator use");
+        }
+      }
+      if (hasPhiUse) {
+        continue;
+      }
+
+      for (MInstruction* iterMore : iteratorMores) {
+        bool hasUnsafeUse = false;
+        for (MUseDefIterator iterMoreUses(iterMore); iterMoreUses;
+             iterMoreUses++) {
+          MDefinition* def = iterMoreUses.def();
+          if (def->isInstruction() &&
+              hasDominatingIteratorEnd(iteratorEnds, def->toInstruction())) {
+            hasUnsafeUse = true;
+            break;
+          }
+        }
+        if (!hasUnsafeUse && !safeIterMores.put(iterMore->toIteratorMore())) {
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) {
   bool changed = false;
 
@@ -2493,6 +2573,11 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) {
     return block->id() >= numInitialBlocks;
   };
 
+  IteratorMoreSet safeIteratorMores;
+  if (!FindSafeIteratorMoreInstructions(graph, safeIteratorMores)) {
+    return false;
+  }
+
   for (ReversePostorderIterator blockIter = graph.rpoBegin();
        blockIter != graph.rpoEnd();) {
     MBasicBlock* block = *blockIter++;
@@ -2594,6 +2679,9 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) {
             SkipIterObjectUnbox(receiver)) {
           continue;
         }
+        if (!safeIteratorMores.has(iterNext)) {
+          continue;
+        }
       } else if (supportObjectKeys && SkipBox(idVal)->isLoadIteratorElement()) {
         auto* iterLoad = SkipBox(idVal)->toLoadIteratorElement();
 
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
index 32ee888f311..7afb8570506 100644
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3160,9 +3160,28 @@ void MacroAssembler::extractCurrentIndexAndKindFromIterator(Register iterator,
                          PropertyIteratorObject::offsetOfIteratorSlot());
   loadPrivate(nativeIterAddr, outIndex);
 
+#ifdef DEBUG
+  // Assert the Active flag is set.
+  Label iterActive;
+  branchTest32(Assembler::NonZero,
+               Address(outIndex, NativeIterator::offsetOfFlags()),
+               Imm32(NativeIterator::Flags::Active), &iterActive);
+  assumeUnreachable("iterator-index fast path on an inactive iterator");
+  bind(&iterActive);
+#endif
+
   // Load the property count into outKind.
   load32(Address(outIndex, NativeIterator::offsetOfPropertyCount()), outKind);
 
+  // The cursor must not be 0 because then we would access indices[cursor - 1]
+  // below.
+  Label cursorOk;
+  branch32(Assembler::NotEqual,
+           Address(outIndex, NativeIterator::offsetOfPropertyCursor()),
+           Imm32(0), &cursorOk);
+  assumeUnreachable("iterator-index fast path on a closed iterator");
+  bind(&cursorOk);
+
   // We need two bits of wiggle room in a u32 here for the logic below.
   static_assert(NativeIterator::PropCountLimit <= 1 << 30);
 
Loading diff…