Zoekt mishandles valid regexp alternation below query.Symbol.
sym:(abc|def) fails during match-tree construction with found *index.orMatchTree inside query.Symbol.
sym:(éa|êb) builds without an error but uses only êb as the symbol verifier, which causes a false negative for éa.
I reproduced both cases on 6fa876bf5395a70bc2249128d0e3734d591047be and on a0f5789d25cb, used by v0.0.0-20260325124901-a0f5789d25cb.
Reproduction
Add this test to index/matchtree_test.go:
func TestSymbolRegexpAlternationMatchTree(t *testing.T) {
q, err := query.Parse("sym:(abc|def)")
if err != nil {
t.Fatal(err)
}
if _, err := (&indexData{}).newMatchTree(q, matchTreeOpt{}); err != nil {
t.Fatal(err)
}
}
Run:
go test ./index -run '^TestSymbolRegexpAlternationMatchTree$' -count=1
Result:
found *index.orMatchTree inside query.Symbol
The false-negative case uses content := []byte("éa êb éa êb") and []DocumentSection{{8, 11}, {12, 15}}. For sym:(éa|êb), the expected match offsets are 8 and 12; Zoekt returns only 12.
Cause
- The regexp optimizer makes exact branches and returns an exact
orMatchTree for alternation (optimizer, substring conversion).
newMatchTree returns that tree directly when isEq is true (source).
query.Symbol accepts a direct substrMatchTree; otherwise, it searches the child tree for a regexpMatchTree (source). No regexp leaf causes the type error. Multiple regexp leaves cause the last leaf to become the verifier for the complete expression.
The existing sym:(ab|cd) test does not cover the defect because its two-rune branches are below ngramSize == 3 (test).
Fix
When query.Symbol.Expr is a *query.Regexp, build the symbol verifier from that original query node. Keep the optimized child tree as the document prefilter and retain the direct symbolSubstrMatchTree fast path.
A local prototype fixes both cases and passes go test ./index -count=1. Regression tests should cover ASCII and multibyte alternatives, text inside and outside symbol sections, and line-match and chunk-match modes.
Workaround
Repeat sym: on each query-level OR branch:
Related
Sourcegraph issue #49892 reported the same failure class with an andMatchTree. Zoekt PR #571 fixed it by disabling word-match optimization below query.Symbol.
Zoekt mishandles valid regexp alternation below
query.Symbol.sym:(abc|def)fails during match-tree construction withfound *index.orMatchTree inside query.Symbol.sym:(éa|êb)builds without an error but uses onlyêbas the symbol verifier, which causes a false negative foréa.I reproduced both cases on
6fa876bf5395a70bc2249128d0e3734d591047beand ona0f5789d25cb, used byv0.0.0-20260325124901-a0f5789d25cb.Reproduction
Add this test to
index/matchtree_test.go:Run:
Result:
The false-negative case uses
content := []byte("éa êb éa êb")and[]DocumentSection{{8, 11}, {12, 15}}. Forsym:(éa|êb), the expected match offsets are8and12; Zoekt returns only12.Cause
orMatchTreefor alternation (optimizer, substring conversion).newMatchTreereturns that tree directly whenisEqis true (source).query.Symbolaccepts a directsubstrMatchTree; otherwise, it searches the child tree for aregexpMatchTree(source). No regexp leaf causes the type error. Multiple regexp leaves cause the last leaf to become the verifier for the complete expression.The existing
sym:(ab|cd)test does not cover the defect because its two-rune branches are belowngramSize == 3(test).Fix
When
query.Symbol.Expris a*query.Regexp, build the symbol verifier from that original query node. Keep the optimized child tree as the document prefilter and retain the directsymbolSubstrMatchTreefast path.A local prototype fixes both cases and passes
go test ./index -count=1. Regression tests should cover ASCII and multibyte alternatives, text inside and outside symbol sections, and line-match and chunk-match modes.Workaround
Repeat
sym:on each query-level OR branch:Related
Sourcegraph issue #49892 reported the same failure class with an
andMatchTree. Zoekt PR #571 fixed it by disabling word-match optimization belowquery.Symbol.