Skip to content

jq: bare/unpiped path-context builtins (parent, parent(n), key, file_index) silently no-op outside Expr::Pipe #1663

Description

@newhoggy

Severity: Low-Medium (silent wrong output, no crash; requires an uncommon-but-real query shape)

Summary

Found during #1487's code review (multiple independent review passes converged on this). Builtin::Parent/Builtin::ParentN/Builtin::Key/Builtin::FileIndex all require path-context tracking to resolve meaningfully, which only eval_pipe_with_path_context_internal provides. That evaluator is only reached when needs_path_context recognizes the surrounding expression shape and routes through it via Expr::Pipe's dispatcher.

needs_path_context (src/jq/eval.rs, ~line 561) is missing arms for several common expression shapes, so a path-context builtin used inside any of them falls through to eval_single's plain dispatch → eval_builtin, whose arms for these builtins don't have path context available and return a fixed, wrong fallback (e.g. ParentN's arm does let _ = n_expr; and unconditionally returns {}, without even evaluating n_expr -- so a parent(error("boom")) in one of these positions silently swallows the error instead of raising it).

Confirmed-missing shapes (live-verified against a --release --features cli build):

  • Expr::Object (e.g. {x: parent(-1)})
  • Expr::AsPattern (EXPR as $x | ... -- the ordinary variable-binding idiom, very commonly used)
  • Expr::Reduce
  • Expr::Foreach
  • Expr::Limit

Repro

$ echo '{"a":1}' | succinctly jq 'parent(-1)'              # {} instead of erroring (bare/unpiped)
$ echo '{"a":1}' | succinctly jq 'parent(error("boom"))'   # {} -- error is never raised
$ echo '{"a":{"b":1}}' | succinctly jq '.a.b | . as $x | parent(1)'   # {} instead of {"b":1} (WRONG, not just unvalidated)
$ echo '{"a":{"b":1}}' | succinctly jq '.a.b | {x: parent(1)}'        # {"x":{}} instead of {"x":{"b":1}}

The Expr::AsPattern case is the most consequential: it's not merely a missed validation opportunity (like the bare/no-pipe cases), it's an outright wrong value for an extremely common jq idiom (EXPR as $x | ...).

Scope note

This is a pre-existing gap in needs_path_context's coverage, not something #1487 introduced -- the same builtins already returned the same wrong fallback in these shapes before that fix. #1487 was scoped narrowly to parent(n)'s numeric-argument classification (the Int/Float disagreement, and yq-mode's negative-n wraparound) and deliberately did not expand into fixing needs_path_context's coverage, since that affects every path-context builtin (not just parent(n)) and carries a much larger blast radius than a single-builtin argument-validation fix.

Suggested approach

Audit every Expr variant needs_path_context currently returns false/falls through to the catch-all for, and either:

  • add explicit path-context-aware handling for key/parent/parent(n)/file_index inside each of the missing shapes (mirroring how Expr::Array already does this), or
  • find a way to make the fallback in eval_builtin at least fail loudly (evaluate n_expr for its side effects and error rather than silently returning {}) when it can't provide real path context, so the failure mode changes from "silently wrong" to "clearly unsupported" until full support lands.

Related: #1409 ("isolate-then-continue_rest_with_context loses per-output path threading") is adjacent path-context-threading work but not the same gap -- worth checking for overlap when scoping the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SonnetSuitable for a Sonnet-class model to implement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions