parse_query_lenient does not terminate on `a:(^` (unclosed group with a boost)
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
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.
Source: quickwit-oss/tantivy