Skip to content

fix(adi): the branch descent compares only the bytes of the seek key - #162

Merged
FiveTechSoft merged 1 commit into
FiveTechSoft:mainfrom
russimicro:fix/adi-branch-descent-prefix
Aug 5, 2026
Merged

fix(adi): the branch descent compares only the bytes of the seek key#162
FiveTechSoft merged 1 commit into
FiveTechSoft:mainfrom
russimicro:fix/adi-branch-descent-prefix

Conversation

@russimicro

Copy link
Copy Markdown
Collaborator

Descending a multi-level ADI bag compared the full index key length even
when the caller supplied a shorter (partial) key. Past the end of the search
string the comparison read whatever followed it in memory, so a partial seek
whose prefix equalled a separator's could take the wrong child and miss a key
that exists.

The dense leaf already applies the right rule — compare only the bytes the
caller gave — so this makes the branch descent agree with it:

const std::size_t cmp_len = std::min<std::size_t>(fnkey.size(), key_total_len_);

Found on a Harbour/FiveWin ERP doing dbSeek(partial_code) over an ADT company
whose article bag is deep enough to have a branch level; the same seek over
DBFCDX found the row.

Tests

New abi_adi_prefix_seek_multilevel_test: builds a bag with enough keys to grow
a branch level and seeks with partial keys of several lengths, including one
whose prefix collides with the separator.

Full suite green on this branch (1292 cases).

AdiIndex::compare_keys_ compares min(a, b, key_total_len_) bytes, so the
dense-leaf scan honours a seek key shorter than the index key -- the prefix
rule the CDX side already follows. The char-key BRANCH descent did not: it
memcmp'd key_total_len_ bytes against the separator while the folded search
key held only what the caller supplied, reading past the end of that buffer
whenever the separator's own prefix equalled the search prefix.

Honesty about the impact: I could not turn this into a wrong answer. The
bytes following a std::string compare low, which selects the same child the
prefix rule wants, and that held both inside the small-buffer optimisation
and with a key long enough to force a heap allocation. So this is an
out-of-bounds read removed and the rule stated once instead of twice, not a
reproduced defect.

The tests are worth more than the fix here: the ADT / ADI side of the
partial-seek rule had no coverage at all, while the CDX side has had it
since the seek and scope fixes. Both cases sweep every key rather than
probing one, because a single wrong branch decision shows up as one miss in
1500 and any single probe is likely to land mid-page and pass regardless.
The tree has to be multi-level for the branch descent to run at all, hence
the row counts.

Test: abi_adi_prefix_seek_multilevel_test -- 6000 rows on a 16-byte key
seeking a 10-byte prefix (the shape of the ERP's
`SEEK cCodigoCon + cDocumeTra` over a con+doc+seq tag), and 3000 rows on a
36-byte key seeking a 16-byte prefix so the folded key lands on the heap
instead of inside std::string's inline buffer. The key is a single wide
character field, not a compound expression, so a plain v1 ADI tag suffices.

(cherry picked from commit ae4994a)
@FiveTechSoft
FiveTechSoft merged commit 6d5ff59 into FiveTechSoft:main Aug 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants