Skip to content

Commit 49c891d

Browse files
committed
Fix volatile INDICECT with .=
Neither STATIC_PROP nor VAR are susceptible. Static vars cannot be unset. Unsetting vars only sets them to IS_UNDEF, including dynamic properties (`zend_hash_del_ind()` in the `ZEND_UNSET_VAR` handler). Fixes GH-15938
1 parent 090b53b commit 49c891d

File tree

7 files changed

+171
-2
lines changed

7 files changed

+171
-2
lines changed

Zend/tests/gh15938_001.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
GH-15938
3+
--FILE--
4+
<?php
5+
6+
#[AllowDynamicProperties]
7+
class C {}
8+
9+
$obj = new C();
10+
// $obj->a = str_repeat('a', 10);
11+
$obj->a .= new class {
12+
function __toString() {
13+
global $obj;
14+
unset($obj->a);
15+
return str_repeat('c', 10);
16+
}
17+
};
18+
19+
var_dump($obj->a);
20+
21+
?>
22+
--EXPECTF--
23+
Warning: Undefined property: C::$a in %s on line %d
24+
string(10) "cccccccccc"

Zend/tests/gh15938_002.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
GH-15938
3+
--FILE--
4+
<?php
5+
6+
#[AllowDynamicProperties]
7+
class C {}
8+
9+
$obj = new C();
10+
$obj->a = '';
11+
$obj->a .= new class {
12+
function __toString() {
13+
global $obj;
14+
for ($i = 0; $i < 8; $i++) {
15+
$obj->{$i} = 0;
16+
}
17+
return 'str';
18+
}
19+
};
20+
21+
var_dump($obj);
22+
23+
?>
24+
--EXPECTF--
25+
object(C)#%d (9) {
26+
["a"]=>
27+
string(3) "str"
28+
["0"]=>
29+
int(0)
30+
["1"]=>
31+
int(0)
32+
["2"]=>
33+
int(0)
34+
["3"]=>
35+
int(0)
36+
["4"]=>
37+
int(0)
38+
["5"]=>
39+
int(0)
40+
["6"]=>
41+
int(0)
42+
["7"]=>
43+
int(0)
44+
}

Zend/tests/gh15938_003.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
GH-15938
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public $a;
8+
}
9+
10+
$obj = new C();
11+
$obj->a = '';
12+
$obj->a .= new class {
13+
function __toString() {
14+
global $obj;
15+
$obj = null;
16+
return 'str';
17+
}
18+
};
19+
20+
?>
21+
--EXPECTF--
22+
Fatal error: Uncaught Error: Attempt to assign property "a" on null in %s:%d
23+
Stack trace:
24+
#0 {main}
25+
thrown in %s on line %d

Zend/tests/gh15938_004.phpt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
GH-15938
3+
--FILE--
4+
<?php
5+
6+
$a = [];
7+
$a[0] = '';
8+
$a[0] .= new class {
9+
function __toString() {
10+
global $a;
11+
for ($i = 0; $i < 8; $i++) {
12+
$a[] = 0;
13+
}
14+
return 'str';
15+
}
16+
};
17+
18+
var_dump($a);
19+
20+
?>
21+
--EXPECT--
22+
array(9) {
23+
[0]=>
24+
string(3) "str"
25+
[1]=>
26+
int(0)
27+
[2]=>
28+
int(0)
29+
[3]=>
30+
int(0)
31+
[4]=>
32+
int(0)
33+
[5]=>
34+
int(0)
35+
[6]=>
36+
int(0)
37+
[7]=>
38+
int(0)
39+
[8]=>
40+
int(0)
41+
}

Zend/tests/gh15938_005.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-15938
3+
--FILE--
4+
<?php
5+
6+
$dyn = 'a';
7+
$$dyn = str_repeat('a', 10);
8+
try {
9+
$$dyn .= new class {
10+
function __toString() {
11+
global $dyn;
12+
unset($$dyn);
13+
return str_repeat('c', 10);
14+
}
15+
};
16+
} catch (Exception $e) {}
17+
18+
var_dump(get_defined_vars()['a']);
19+
20+
?>
21+
--EXPECT--
22+
string(20) "aaaaaaaaaacccccccccc"

Zend/zend_compile.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3627,6 +3627,17 @@ static inline void zend_emit_assign_ref_znode(zend_ast *var_ast, znode *value_no
36273627
}
36283628
/* }}} */
36293629

3630+
static void zend_cast_to_string_if_concat(uint32_t opcode, zend_ast *expr, znode *result)
3631+
{
3632+
/* For .=, if the expression may contain objects, cast to a string before
3633+
* compiling the lhs W fetches. Otherwise, __toString() may cause the
3634+
* volatile zval storage to go away. */
3635+
if (opcode == ZEND_CONCAT && expr->kind != ZEND_AST_ZVAL) {
3636+
zend_op *cast_opline = zend_emit_op_tmp(result, ZEND_CAST, result, NULL);
3637+
cast_opline->extended_value = IS_STRING;
3638+
}
3639+
}
3640+
36303641
static void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
36313642
{
36323643
zend_ast *var_ast = ast->child[0];
@@ -3669,6 +3680,7 @@ static void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
36693680
offset = zend_delayed_compile_begin();
36703681
zend_delayed_compile_dim(result, var_ast, BP_VAR_RW, /* by_ref */ false);
36713682
zend_compile_expr_with_potential_assign_to_self(&expr_node, expr_ast, var_ast);
3683+
zend_cast_to_string_if_concat(opcode, expr_ast, &expr_node);
36723684

36733685
opline = zend_delayed_compile_end(offset);
36743686
opline->opcode = ZEND_ASSIGN_DIM_OP;
@@ -3683,6 +3695,7 @@ static void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
36833695
offset = zend_delayed_compile_begin();
36843696
zend_delayed_compile_prop(result, var_ast, BP_VAR_RW);
36853697
zend_compile_expr(&expr_node, expr_ast);
3698+
zend_cast_to_string_if_concat(opcode, expr_ast, &expr_node);
36863699

36873700
opline = zend_delayed_compile_end(offset);
36883701
cache_slot = opline->extended_value;

ext/opcache/tests/jit/assign_obj_op_001.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ $test = new Test;
1313
(function(){$this->y.=[];})->call($test);
1414
?>
1515
--EXPECTF--
16+
Warning: Array to string conversion in %sassign_obj_op_001.php on line 6
17+
1618
Deprecated: Creation of dynamic property Test::$y is deprecated in %sassign_obj_op_001.php on line 6
1719

1820
Warning: Undefined property: Test::$y in %sassign_obj_op_001.php on line 6
19-
20-
Warning: Array to string conversion in %sassign_obj_op_001.php on line 6

0 commit comments

Comments
 (0)