Inside (...) query filters, malformed input is not rejected — it is silently discarded or misread, silently producing incorrect results. This is a systemic gap in how criterion makers consume the parsed token tree, affecting every (...) filter except if.
Steps to Reproduce
Case 1 — set reference silently dropped (wrong results)
The set-ref is matched only as a .-rooted node, so wrapping it in brackets leaves a [/(/{ node the maker doesn't recognize, and the input set silently defaults to _:
way[highway]({{bbox}})->.roads;
node(around.roads:30); out count; // correct: N nodes near highways
node(around[.roads]:30); out count; // accepted; silently runs on `_` -> 0
node(around(.roads):30); out count; // same
node(w[.roads]); out count; // same drop in recurse
No syntax error is raised for any of the bracketed forms.
Case 2 — literal collapses to a bracket, ballooning a bbox (resource impact)
A value is read as the root token of whatever subtree sits there. A wrapped bbox coordinate collapses to ( -> parsed as 0:
node[highway](32.730,-116.001,32.747,-115.987); out count; // ~12, 1 sq mi
node[highway](32.730,-116.001,32.747,(-115.987)); out count; // east -> 0; scans to lon 0
A tiny bounded query silently becomes an enormous one.
Root cause
A (...) filter body is parsed by the generic operator-precedence token tree (the same one used for evaluators), which intentionally accepts far more than the QL grammar. Each Criterion_Maker::create_criterion is supposed to narrow it back to valid QL, but the implementations do a partial positive walk: dispatch on the leftmost leaf, match a few anchor nodes (:, ,, .), and read values via a raw ->token. They never (a) assert the whole tree was consumed, or (b) assert a value node is a leaf. So surplus structure is dropped, and non-leaf value subtrees collapse to their root operator token.
Scope
There are silent failure modes across all set-consuming / literal-consuming filters (around, area, recurse w/r/bn/bw/br, way_cnt, way_link, pivot, id, bbox, user/uid, poly, and newer/changed):
- wrapped set refs are dropped
- non-leaf literals collapse to a root token
Some non-leaf literal collapses are caught late by a downstream check (unparseable id, degenerate bbox, zero timestamp) — an error, but never a parse error.
if is the exception: it lowers its subtree via the evaluator builder and is unaffected.
Not affected / already rejected
- bare juxtaposition ("Operator expected")
- unbalanced brackets
;/-> inside a filter
- prefix operator before the keyword (dispatch fails)
All correctly error.
Suggested fix
This is conceptually a single fix applied uniformly. In the criterion makers, reject leftover/unconsumed nodes and require value slots to be leaves, rather than silently ignoring them. The evaluator makers already follow this pattern by recursing/validating the full subtree, validating arity/type, and calling the existing assert_* helpers -- which is why the if filter is not affected by this issue.
Tested on v0.7.62.11
Inside
(...)query filters, malformed input is not rejected — it is silently discarded or misread, silently producing incorrect results. This is a systemic gap in how criterion makers consume the parsed token tree, affecting every(...)filter exceptif.Steps to Reproduce
Case 1 — set reference silently dropped (wrong results)
The set-ref is matched only as a
.-rooted node, so wrapping it in brackets leaves a[/(/{node the maker doesn't recognize, and the input set silently defaults to_:No syntax error is raised for any of the bracketed forms.
Case 2 — literal collapses to a bracket, ballooning a bbox (resource impact)
A value is read as the root token of whatever subtree sits there. A wrapped bbox coordinate collapses to
(-> parsed as0:A tiny bounded query silently becomes an enormous one.
Root cause
A
(...)filter body is parsed by the generic operator-precedence token tree (the same one used for evaluators), which intentionally accepts far more than the QL grammar. EachCriterion_Maker::create_criterionis supposed to narrow it back to valid QL, but the implementations do a partial positive walk: dispatch on the leftmost leaf, match a few anchor nodes (:,,,.), and read values via a raw->token. They never (a) assert the whole tree was consumed, or (b) assert a value node is a leaf. So surplus structure is dropped, and non-leaf value subtrees collapse to their root operator token.Scope
There are silent failure modes across all set-consuming / literal-consuming filters (
around,area,recursew/r/bn/bw/br,way_cnt,way_link,pivot,id,bbox,user/uid,poly, andnewer/changed):Some non-leaf literal collapses are caught late by a downstream check (unparseable id, degenerate bbox, zero timestamp) — an error, but never a parse error.
ifis the exception: it lowers its subtree via the evaluator builder and is unaffected.Not affected / already rejected
;/->inside a filterAll correctly error.
Suggested fix
This is conceptually a single fix applied uniformly. In the criterion makers, reject leftover/unconsumed nodes and require value slots to be leaves, rather than silently ignoring them. The evaluator makers already follow this pattern by recursing/validating the full subtree, validating arity/type, and calling the existing
assert_*helpers -- which is why theiffilter is not affected by this issue.Tested on
v0.7.62.11