Skip to content

Commit 8d3cc92

Browse files
alibeklfcmeta-codesync[bot]
authored andcommitted
Fix clone_index null return for IndexRowwiseMinMax (#5220)
Summary: Pull Request resolved: #5220 ## TL;DR `clone_index` returns `nullptr` for any `IndexRowwiseMinMax` or `IndexRowwiseMinMaxFP16` instance — a silent failure that leaks the allocated clone and its deep-copied inner index. We add the missing `return res;` and a regression test covering both subtypes. ## Bug `clone_Index` in `clone_index.cpp` is a dispatch table of `else if` branches, one per index type. Every branch ends with `return res;` except one: the `IndexRowwiseMinMaxBase` branch allocates `res`, sets `own_fields = true`, deep-copies the inner index into `res->index`, and then falls through to the bottom of the function. The fallthrough hits `return nullptr;`. The result: callers receive `nullptr` with no exception or warning, and the `res` pointer plus its inner clone are leaked. ## Impact `clone_index` has 15+ callsites across the faiss codebase and is part of the public C++ and Python API. None of the callers we surveyed guard against `nullptr` — the contract is "clone returns a usable index or throws." With this bug: - Python users see `clone_index(codec)` return `None`. The next attribute access on the result raises `AttributeError`, often deep in unrelated code where the connection to the clone call is not obvious. - C++ users dereference the null pointer on the next method call, producing a segfault with no stack frame pointing at `clone_index`. - Memory leaks: every failed clone leaks `res` (the `IndexRowwiseMinMax` shell) and `res->index` (the inner `IndexScalarQuantizer` deep copy). The bug affects any pipeline that wraps a codec with `MinMax,*` or `MinMaxFP16,*` factory strings and then clones it — common when cloning a trained codec for parallel encoding, snapshot persistence, or sharded deployment. ## Fix One line added: ``` } else if ( const IndexRowwiseMinMaxBase* irmmb = dynamic_cast<const IndexRowwiseMinMaxBase*>(index)) { IndexRowwiseMinMaxBase* res = clone_IndexRowwiseMinMax(irmmb); res->own_fields = true; res->index = clone_Index(irmmb->index); return res; // added } ``` The fix restores symmetry with every other branch in the function. No new abstractions, no related cleanup — the missing return is purely an oversight from when the branch was added. The `clone_IndexRowwiseMinMax` helper dispatches via `TRYCLONE` to both `IndexRowwiseMinMaxFP16` and `IndexRowwiseMinMax`, so both concrete subtypes are restored by this one line. Reviewed By: mnorris11 Differential Revision: D105157714 fbshipit-source-id: ed29db91b51716133ef8ceb426bc92760a222fef
1 parent ff1d543 commit 8d3cc92

2 files changed

Lines changed: 20 additions & 0 deletions

File tree

faiss/clone_index.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ Index* Cloner::clone_Index(const Index* index) {
377377
IndexRowwiseMinMaxBase* res = clone_IndexRowwiseMinMax(irmmb);
378378
res->own_fields = true;
379379
res->index = clone_Index(irmmb->index);
380+
return res;
380381
} else if (
381382
dynamic_cast<const IndexAdditiveQuantizerFastScan*>(index) ||
382383
dynamic_cast<const IndexAdditiveQuantizer*>(index) ||

tests/test_clone.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,22 @@ def test_AdditiveQuantizerFastScan(self):
8686
self.do_test_clone("RQ3x4fs_32_Nlsq2x4")
8787
self.do_test_clone("PLSQ2x3x4fs_Nlsq2x4")
8888
self.do_test_clone("PRQ2x3x4fs_Nrq2x4")
89+
90+
def test_RowwiseMinMax(self):
91+
# clone_Index for IndexRowwiseMinMaxBase was missing return res, so
92+
# clone_index returned nullptr and leaked the clone allocation plus
93+
# the deep-copied inner index.
94+
d = 32
95+
n = 200
96+
rng = np.random.default_rng(42)
97+
x = rng.standard_normal((n, d)).astype(np.float32)
98+
99+
for factory in ("MinMax,SQ8", "MinMaxFP16,SQ8"):
100+
codec = faiss.index_factory(d, factory)
101+
codec.train(x)
102+
codes = codec.sa_encode(x)
103+
104+
codec2 = faiss.clone_index(codec)
105+
self.assertEqual(type(codec), type(codec2))
106+
codes2 = codec2.sa_encode(x)
107+
np.testing.assert_array_equal(codes, codes2)

0 commit comments

Comments
 (0)