Repository navigation
Fix divideTree assertion failure with KDTreeSingleIndexDynamicAdaptor removePoint/addPoints - #283
Merged
Conversation
… removePoint/addPoints
The assertion `assert(left < obj.dataset_.kdtree_get_point_count())`
added in commit
549a5e3 - Fix middleSplit_ for same points
(PR jlblancoc#250, released in v1.6.2) incorrectly fires when using
`KDTreeSingleIndexDynamicAdaptor` with repeated `removePoint`/`addPoints`
cycles on the same point indices.
Root cause: `addPoints` increments the internal `pointCount_` unconditionally,
even when re-adding a previously removed point. This causes sub-tree `vAcc_`
vectors to accumulate more entries than `dataset_.kdtree_get_point_count()`.
When `buildIndex()` is called on such a sub-tree, it sets
`size_ = vAcc_.size()` and calls `divideTree(*this, 0, size_, ...)`.
In recursive calls, `left` (an offset into `vAcc_`) can exceed
`kdtree_get_point_count()`, triggering the assertion.
However, the actual point *indices* stored in `vAcc_` are always valid
(they are always < N). The assertion conflates the array offset with the
dataset size, which is only a valid invariant for the static adaptor where
`vAcc_` is a permutation of `[0, N)`.
Fix: Change the assertion to check what actually matters -- that the point
index at `vAcc_[left]` is within bounds of the dataset.
Add a repro test that exercises repeated `removePoint`/`addPoints` cycles.
That fails without the fix.
LLM: Found, repro'd, and fixed using Claude Opus 4.6, human review.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #283 +/- ##
==========================================
+ Coverage 83.85% 84.29% +0.44%
==========================================
Files 1 1
Lines 675 675
Branches 127 128 +1
==========================================
+ Hits 566 569 +3
+ Misses 109 106 -3
🚀 New features to boost your workflow:
|
pull Bot
pushed a commit
to daoran/nanoflann
that referenced
this pull request
May 25, 2026
Re-adding a previously-removed point now reactivates the existing entry in place instead of inserting a duplicate. removePoint is a lazy deletion (treeIndex_[idx]=-1) that leaves the point physically present in its sub-tree's vAcc_. Re-adding it via addPoints re-inserted it: incrementing pointCount_, growing treeIndex_, and pushing the index into vAcc_ again. Repeated remove/add cycles therefore caused unbounded growth of the internal vectors, duplicate results in radius/knn searches, and could eventually overflow treeCount_ (the assertion fixed in PR jlblancoc#283 was an earlier casualty of the same growth). treeIndex_'s numeric value is only ever compared to -1, so reactivation only needs to restore a valid tree mark. removedPoints_ becomes a map from index to the sub-tree that still holds it; removePoint records it (and now guards double-removal); addPoints reactivates in place; treeIndex_ is resized incrementally instead of by the batch count. Adds test kdtree.dynamic_readd_no_duplicates (fails before, passes after). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
social4hyq
pushed a commit
to social4hyq/homebrew-core
that referenced
this pull request
Sep 20, 2026
nanoflann 1.10.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>## What's Changed * Fix an invalid read. by @drwells in jlblancoc/nanoflann#282 * Fix divideTree assertion failure with KDTreeSingleIndexDynamicAdaptor removePoint/addPoints by @nh2 in jlblancoc/nanoflann#283 * Fix duplicate-entry growth on removePoint/addPoints re-add cycles by @jlblancoc in jlblancoc/nanoflann#285 * Fix loadIndex reading past EOF for empty indices (#286) by @jlblancoc in jlblancoc/nanoflann#287 * fix flaky kdtree.L1_vs_bruteforce_rknn test by @jschueller in jlblancoc/nanoflann#288 * Fix KDTreeSingleIndexDynamicAdaptor_ saveIndex/loadIndex infinite recursion by @jlblancoc in jlblancoc/nanoflann#290 * Remove dead worst_dist branch from L1/L2_Adaptor::evalMetric by @jlblancoc in jlblancoc/nanoflann#291 * Fix three type/correctness bugs (operator=, First0Bit, findWithinBox) by @jlblancoc in jlblancoc/nanoflann#292 * Polish docs warnings attrs by @jlblancoc in jlblancoc/nanoflann#293 * Efficiency: remove middleSplit_ heap alloc (§6.2) + else-if in computeInitialDistances (§6.7) by @jlblancoc in jlblancoc/nanoflann#294 * saveIndex/loadIndex: add magic+version+type-size header, throw on mismatch by @jlblancoc in jlblancoc/nanoflann#295 * CI: extend tested C++ standards, and increase warning levels by @jlblancoc in jlblancoc/nanoflann#296 * Deduplicate addPoint, computeBoundingBox, and searchLevel by @jlblancoc in jlblancoc/nanoflann#297 * Minor internal cleanups: allocateBytes, has_flag(), noexcept by @jlblancoc in jlblancoc/nanoflann#298 * Centralize DIM dispatch in veclen() with if constexpr; widen nodiscard by @jlblancoc in jlblancoc/nanoflann#299 * Refactor tree builders and harden Eigen adaptor ownership by @jlblancoc in jlblancoc/nanoflann#300 * Hoist point-length computation out of search inner loops by @jlblancoc in jlblancoc/nanoflann#301 * Incremental self-balancing dynamic k-d tree index (sync + multithreaded) by @jlblancoc in jlblancoc/nanoflann#302 ## New Contributors * @drwells made their first contribution in jlblancoc/nanoflann#282 * @nh2 made their first contribution in jlblancoc/nanoflann#283 **Full Changelog**: https://github.com/jlblancoc/nanoflann/compare/v1.9.0...v1.10.0</pre> <p>View the full release notes at <a href="https://github.com/jlblancoc/nanoflann/releases/tag/v1.10.0">https://github.com/jlblancoc/nanoflann/releases/tag/v1.10.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!10859
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Disclosure: This is the outcome of me debugging an assertion failure (I encountered from 1.5.5 to 1.7.1) with the Claude Opus 4.6 LLM.
As far as I can tell, the minimal repro it generated for me makes sense.
I am not 100% confident about the fix to the assertion, so please do review that with extra scrutiny @yzabalotski and @jlblancoc (from #250).
LLM output below.
The assertion
assert(left < obj.dataset_.kdtree_get_point_count())added in commit(PR #250, released in v1.6.2) incorrectly fires when using
KDTreeSingleIndexDynamicAdaptorwith repeatedremovePoint/addPointscycles on the same point indices.Root cause:
addPointsincrements the internalpointCount_unconditionally, even when re-adding a previously removed point. This causes sub-treevAcc_vectors to accumulate more entries thandataset_.kdtree_get_point_count(). WhenbuildIndex()is called on such a sub-tree, it setssize_ = vAcc_.size()and callsdivideTree(*this, 0, size_, ...). In recursive calls,left(an offset intovAcc_) can exceedkdtree_get_point_count(), triggering the assertion.However, the actual point indices stored in
vAcc_are always valid (they are always < N). The assertion conflates the array offset with the dataset size, which is only a valid invariant for the static adaptor wherevAcc_is a permutation of[0, N).Fix: Change the assertion to check what actually matters -- that the point index at
vAcc_[left]is within bounds of the dataset.Add a repro test that exercises repeated
removePoint/addPointscycles. That fails without the fix.LLM: Found, repro'd, and fixed using Claude Opus 4.6, human review.