[php-src] master: Fix GH-22490: NULL deref for a pipe on the lhs of ??= (#22494)
Ilia Alshanetsky via GitHub <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-06-28T10:17:11-04:00
Commit: https://github.com/php/php-src/commit/2db458f44470b16222d9a8f7ad17ce368969cb80
Raw diff: https://github.com/php/php-src/commit/2db458f44470b16222d9a8f7ad17ce368969cb80.diff
Fix GH-22490: NULL deref for a pipe on the lhs of ??= (#22494)
zend_compile_var_inner did not route ZEND_AST_PIPE through
zend_compile_memoized_expr, so a pipe on the left-hand side of ??= was
recompiled during the FETCH pass. zend_compile_pipe synthesizes a fresh
call node on each pass, so the memoized-result lookup keyed by that node
returned NULL and was dereferenced. Memoize the pipe like the other call
kinds zend_is_call() already recognizes.
Fixes GH-22490
Changed paths:
A Zend/tests/pipe_operator/gh22490.phpt
M Zend/zend_compile.c
Diff:
diff --git a/Zend/tests/pipe_operator/gh22490.phpt b/Zend/tests/pipe_operator/gh22490.phpt
new file mode 100644
index 000000000000..c801d938c275
--- /dev/null
+++ b/Zend/tests/pipe_operator/gh22490.phpt
@@ -0,0 +1,60 @@
+--TEST--
+GH-22490: Null pointer dereference compiling a pipe on the lhs of ??=
+--FILE--
+<?php
+$calls = 0;
+function unset_prop($x) {
+ global $calls;
+ $calls++;
+ return new stdClass;
+}
+function set_prop($x) {
+ global $calls;
+ $calls++;
+ $o = new stdClass;
+ $o->p = 7;
+ return $o;
+}
+
+echo "property unset, assigns and returns default: ";
+var_dump((1 |> unset_prop(...))->p ??= 99);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+$calls = 0;
+echo "property set, returns existing value: ";
+var_dump((1 |> set_prop(...))->p ??= 99);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+function new_array($x) {
+ global $calls;
+ $calls++;
+ return [];
+}
+
+$calls = 0;
+echo "dim target on a pipe result: ";
+var_dump((1 |> new_array(...))[0] ??= 42);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+function identity($x) {
+ return $x;
+}
+
+$calls = 0;
+echo "nested pipe on the lhs: ";
+var_dump((1 |> identity(...) |> unset_prop(...))->p ??= 5);
+echo "pipe evaluated once: ";
+var_dump($calls);
+?>
+--EXPECT--
+property unset, assigns and returns default: int(99)
+pipe evaluated once: int(1)
+property set, returns existing value: int(7)
+pipe evaluated once: int(1)
+dim target on a pipe result: int(42)
+pipe evaluated once: int(1)
+nested pipe on the lhs: int(5)
+pipe evaluated once: int(1)
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 7e9f7ceac8db..9f8ebad8ab29 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -12270,6 +12270,7 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
case ZEND_AST_METHOD_CALL:
case ZEND_AST_NULLSAFE_METHOD_CALL:
case ZEND_AST_STATIC_CALL:
+ case ZEND_AST_PIPE:
zend_compile_memoized_expr(result, ast, BP_VAR_W);
/* This might not actually produce an opcode, e.g. for expressions evaluated at comptime. */
return NULL;