Skip to content

Symbol regex alternation fails with orMatchTree inside query.Symbol #1147

Description

@clemlesne

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

  1. The regexp optimizer makes exact branches and returns an exact orMatchTree for alternation (optimizer, substring conversion).
  2. newMatchTree returns that tree directly when isEq is true (source).
  3. 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:

sym:abc or sym:def

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions