Subject: [PATCH v12 13/13] fsmonitor: fix split-index bitmap bounds in tweak_fsmonitor()

From: "Paul Tarjan via GitGitGadget" <[email-masked]>
Date: Tue, 31 Mar 2026 06:19:18 +0000

From: Paul Tarjan <[email-masked]>

When GIT_TEST_SPLIT_INDEX=yes is set and the fsmonitor daemon is
active, tweak_fsmonitor() can hit a BUG() assertion:

  BUG: fsmonitor.c:27: fsmonitor_dirty has more entries than the index (2 > 0)

The fsmonitor_dirty EWAH bitmap may reference positions from a
previous index state.  With split-index, cache_nr can be smaller
than the bitmap expects because entries have not been merged yet.

This is related to the issue that 05f28e4b3c (scalar: use
index.skipHash=true for performance, 2025-06-04) worked around by
disabling GIT_TEST_SPLIT_INDEX in t9210, noting "the issue should
be resolved in a series focused on the split index."  This fixes
the fsmonitor bitmap side; the index.skipHash interaction remains.

Two places hit this:

  - tweak_fsmonitor() calls assert_index_minimum() without the
    !istate->split_index guard that the read path (line 98) and
    write path (line 128) already have.  Add the same guard.

  - fsmonitor_ewah_callback() unconditionally asserts and then
    accesses istate->cache[pos], which is out of bounds with
    split-index.  Replace the assertion with a bounds check that
    silently skips positions beyond cache_nr.

Signed-off-by: Paul Tarjan <[email-masked]>
---
 fsmonitor.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/fsmonitor.c b/fsmonitor.c
index d07dc18967..5e5a4fadea 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -33,7 +33,8 @@ static void fsmonitor_ewah_callback(size_t pos, void *is)
 	struct index_state *istate = (struct index_state *)is;
 	struct cache_entry *ce;
 
-	assert_index_minimum(istate, pos + 1);
+	if (pos >= istate->cache_nr)
+		return;
 
 	ce = istate->cache[pos];
 	ce->ce_flags &= ~CE_FSMONITOR_VALID;
@@ -805,7 +806,8 @@ void tweak_fsmonitor(struct index_state *istate)
 			}
 
 			/* Mark all previously saved entries as dirty */
-			assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size);
+			if (!istate->split_index)
+				assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size);
 			ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate);
 
 			refresh_fsmonitor(istate);
-- 
gitgitgadget