Fix reduce to preserve its input across init backtracking (#3633)

`reduce f as $x (init; update)` evaluated `f` against a stack slot
that forkpoints created while evaluating `init` restore. Both the
`DUPN` opening the reduction loop and the `LOADVN` retrieving the
result pop that slot destructively, nulling it out, so an `init`
that backtracks (e.g. `0, 0`) made `f` see `null` and fail with
"Cannot iterate over null".

Duplicate the input before the `FORK` instead of inside the loop,
and drop the leftover copy with `POP` once the loop is exhausted.
The slot left live at the `FORK` is then a block pushed after
`init` completed, which no earlier forkpoint can reach, so nulling
it is harmless. This is the same approach `gen_foreach` has used
since c70ae1a.

`DUPN` had no other user, so remove the opcode.
This commit is contained in:
itchyny authored and GitHub committed 2026-09-19 15:21:29 +09:00
1 parent 4beed4e01a
commit df24981307
4 files changed
+10 -11

No files matched your search

+3 -2
View File
@@ -832,8 +832,7 @@ static block bind_alternation_matchers(block matchers, block body) {
block gen_reduce(block source, block matcher, block init, block body) {
block res_var = gen_op_var_fresh(STOREV, "reduce");
block loop = BLOCK(gen_op_simple(DUPN),
source,
block loop = BLOCK(source,
bind_alternation_matchers(matcher,
BLOCK(gen_op_bound(LOADVN, res_var),
body,
@@ -842,8 +841,10 @@ block gen_reduce(block source, block matcher, block init, block body) {
return BLOCK(gen_op_simple(DUP),
init,
res_var,
gen_op_simple(DUP),
gen_op_target(FORK, loop),
loop,
gen_op_simple(POP),
gen_op_bound(LOADVN, res_var));
}
-7
View File
@@ -428,13 +428,6 @@ jv jq_next(jq_state *jq) {
break;
}
case DUPN: {
jv v = stack_popn(jq);
stack_push(jq, jv_copy(v));
stack_push(jq, v);
break;
}
case DUP2: {
jv keep = stack_pop(jq);
jv v = stack_pop(jq);
-1
View File
@@ -1,6 +1,5 @@
OP(LOADK, CONSTANT, 1, 1)
OP(DUP, NONE, 1, 2)
OP(DUPN, NONE, 1, 2)
OP(DUP2, NONE, 2, 3)
OP(PUSHK_UNDER, CONSTANT, 1, 2)
OP(POP, NONE, 1, 0)
+7 -1
View File
@@ -2571,7 +2571,13 @@ try ["OK", setpath([[1]]; 1)] catch ["KO", .]
[]
["KO","Cannot update field at array index of array"]
# regression test for #3227
# regression test for #3227 (reduce)
[] | reduce .[] as $x (0, 1; .)
null
0
1
# regression test for #3227 (foreach)
foreach .[] as $x (0, 1; . + $x)
[1, 2]
1