Skip to content

Commit 2db458f

Browse files
authored
Fix phpGH-22490: NULL deref for a pipe on the lhs of ??= (php#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 phpGH-22490
1 parent 44b11b7 commit 2db458f

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
GH-22490: Null pointer dereference compiling a pipe on the lhs of ??=
3+
--FILE--
4+
<?php
5+
$calls = 0;
6+
function unset_prop($x) {
7+
global $calls;
8+
$calls++;
9+
return new stdClass;
10+
}
11+
function set_prop($x) {
12+
global $calls;
13+
$calls++;
14+
$o = new stdClass;
15+
$o->p = 7;
16+
return $o;
17+
}
18+
19+
echo "property unset, assigns and returns default: ";
20+
var_dump((1 |> unset_prop(...))->p ??= 99);
21+
echo "pipe evaluated once: ";
22+
var_dump($calls);
23+
24+
$calls = 0;
25+
echo "property set, returns existing value: ";
26+
var_dump((1 |> set_prop(...))->p ??= 99);
27+
echo "pipe evaluated once: ";
28+
var_dump($calls);
29+
30+
function new_array($x) {
31+
global $calls;
32+
$calls++;
33+
return [];
34+
}
35+
36+
$calls = 0;
37+
echo "dim target on a pipe result: ";
38+
var_dump((1 |> new_array(...))[0] ??= 42);
39+
echo "pipe evaluated once: ";
40+
var_dump($calls);
41+
42+
function identity($x) {
43+
return $x;
44+
}
45+
46+
$calls = 0;
47+
echo "nested pipe on the lhs: ";
48+
var_dump((1 |> identity(...) |> unset_prop(...))->p ??= 5);
49+
echo "pipe evaluated once: ";
50+
var_dump($calls);
51+
?>
52+
--EXPECT--
53+
property unset, assigns and returns default: int(99)
54+
pipe evaluated once: int(1)
55+
property set, returns existing value: int(7)
56+
pipe evaluated once: int(1)
57+
dim target on a pipe result: int(42)
58+
pipe evaluated once: int(1)
59+
nested pipe on the lhs: int(5)
60+
pipe evaluated once: int(1)

Zend/zend_compile.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12270,6 +12270,7 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
1227012270
case ZEND_AST_METHOD_CALL:
1227112271
case ZEND_AST_NULLSAFE_METHOD_CALL:
1227212272
case ZEND_AST_STATIC_CALL:
12273+
case ZEND_AST_PIPE:
1227312274
zend_compile_memoized_expr(result, ast, BP_VAR_W);
1227412275
/* This might not actually produce an opcode, e.g. for expressions evaluated at comptime. */
1227512276
return NULL;

0 commit comments

Comments
 (0)