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.
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::FileIndexall require path-context tracking to resolve meaningfully, which onlyeval_pipe_with_path_context_internalprovides. That evaluator is only reached whenneeds_path_contextrecognizes the surrounding expression shape and routes through it viaExpr::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 toeval_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 doeslet _ = n_expr;and unconditionally returns{}, without even evaluatingn_expr-- so aparent(error("boom"))in one of these positions silently swallows the error instead of raising it).Confirmed-missing shapes (live-verified against a
--release --features clibuild):Expr::Object(e.g.{x: parent(-1)})Expr::AsPattern(EXPR as $x | ...-- the ordinary variable-binding idiom, very commonly used)Expr::ReduceExpr::ForeachExpr::LimitRepro
The
Expr::AsPatterncase 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 toparent(n)'s numeric-argument classification (the Int/Float disagreement, and yq-mode's negative-n wraparound) and deliberately did not expand into fixingneeds_path_context's coverage, since that affects every path-context builtin (not justparent(n)) and carries a much larger blast radius than a single-builtin argument-validation fix.Suggested approach
Audit every
Exprvariantneeds_path_contextcurrently returnsfalse/falls through to the catch-all for, and either:key/parent/parent(n)/file_indexinside each of the missing shapes (mirroring howExpr::Arrayalready does this), oreval_builtinat least fail loudly (evaluaten_exprfor 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.