#3032·tantivy

parse_query_lenient does not terminate on `a:(^` (unclosed group with a boost)

Author: coconutbirdCreated Aug 5, 2026Updated Aug 23, 2026

Describe the bug

parse_query_lenient does not terminate on some short malformed queries. Minimal input: "a:(^" — a field-qualified, unclosed group containing a boost.

The call is still running after 45 s with no sign of progress. In longer randomised runs the same shape sometimes surfaces instead as an enormous allocation request before aborting:

memory allocation of 68719476736 bytes failed    (64 GiB)
memory allocation of 137438953472 bytes failed   (128 GiB)

Different inputs produced different sizes, so the requested size appears to be derived from the input rather than being a fixed overshoot. Both symptoms look like the same underlying unbounded work in the lenient error-recovery path.

Which version are you using?

tantivy-query-grammar 0.22.0 (currently the latest published version).

To Reproduce

rust
fn main() {
    // never returns
    let _ = tantivy_query_grammar::parse_query_lenient("a:(^");
    println!("unreachable in practice");
}

Expected behavior

Termination — returning the AST plus whatever LenientErrors were recovered, as the lenient parser does for other malformed inputs.

Minimisation

Each input run in its own process with a timeout:

Input Result
a:(^ does not terminate
a:(a^ does not terminate
a:([^ does not terminate
a:((^ does not terminate
a:()^ ok — closing the group fixes it
a:(~ ok — ~ in place of ^ is fine
a:[^ ok — bracket instead of paren is fine
a:( ok
(^, a(^, :(^ ok — the field: prefix is required

So the trigger needs all three of: a field: prefix, an unclosed ( group, and a ^ boost inside it.

Notes

parse_query (strict) returns normally on "a:(^" — this is specific to the lenient entry point.

This is separate from the expect panic on "- *" reported in #3031; that one is strict-path and fails fast, this one is lenient-path and hangs. They are independent, and a fix for one does not address the other.

Relevant for anyone reaching for parse_query_lenient to handle untrusted query strings gracefully: a 4-character input wedges the calling thread. We found it while fuzzing the grammar after hitting #3031 in production.