Skip to content

Query filters silently ignore malformed structure instead of rejecting it #795

Description

@b1tw153

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions