Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Zend/tests/gh15938_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
GH-15938
--FILE--
<?php

#[AllowDynamicProperties]
class C {}

$obj = new C();
// $obj->a = str_repeat('a', 10);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This line isn't even necessary to trigger the leak. $this->a .= prepares the index in the property table and stores the pointer in the VAR passed to ASSIGN_OP. unset($obj->a) will then remove the key and NULL value that were just created. The actual store will then occur in a dead zval.

$obj->a .= new class {
function __toString() {
global $obj;
unset($obj->a);
return str_repeat('c', 10);
}
};

var_dump($obj->a);

?>
--EXPECTF--
Warning: Undefined property: C::$a in %s on line %d
string(10) "cccccccccc"
44 changes: 44 additions & 0 deletions Zend/tests/gh15938_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--TEST--
GH-15938
--FILE--
<?php

#[AllowDynamicProperties]
class C {}

$obj = new C();
$obj->a = '';
$obj->a .= new class {
function __toString() {
global $obj;
for ($i = 0; $i < 8; $i++) {
$obj->{$i} = 0;
}
return 'str';
}
};

var_dump($obj);

?>
--EXPECTF--
object(C)#%d (9) {
["a"]=>
string(3) "str"
["0"]=>
int(0)
["1"]=>
int(0)
["2"]=>
int(0)
["3"]=>
int(0)
["4"]=>
int(0)
["5"]=>
int(0)
["6"]=>
int(0)
["7"]=>
int(0)
}
25 changes: 25 additions & 0 deletions Zend/tests/gh15938_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
GH-15938
--FILE--
<?php

class C {
public $a;
}

$obj = new C();
$obj->a = '';
$obj->a .= new class {
function __toString() {
global $obj;
$obj = null;
return 'str';
}
};

?>
--EXPECTF--
Fatal error: Uncaught Error: Attempt to assign property "a" on null in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
41 changes: 41 additions & 0 deletions Zend/tests/gh15938_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
GH-15938
--FILE--
<?php

$a = [];
$a[0] = '';
$a[0] .= new class {
function __toString() {
global $a;
for ($i = 0; $i < 8; $i++) {
$a[] = 0;
}
return 'str';
}
};

var_dump($a);

?>
--EXPECT--
array(9) {
[0]=>
string(3) "str"
[1]=>
int(0)
[2]=>
int(0)
[3]=>
int(0)
[4]=>
int(0)
[5]=>
int(0)
[6]=>
int(0)
[7]=>
int(0)
[8]=>
int(0)
}
22 changes: 22 additions & 0 deletions Zend/tests/gh15938_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
GH-15938
--FILE--
<?php

$dyn = 'a';
$$dyn = str_repeat('a', 10);
try {
$$dyn .= new class {
function __toString() {
global $dyn;
unset($$dyn);
return str_repeat('c', 10);
}
};
} catch (Exception $e) {}

var_dump(get_defined_vars()['a']);

?>
--EXPECT--
string(20) "aaaaaaaaaacccccccccc"
13 changes: 13 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3627,6 +3627,17 @@ static inline void zend_emit_assign_ref_znode(zend_ast *var_ast, znode *value_no
}
/* }}} */

static void zend_cast_to_string_if_concat(uint32_t opcode, zend_ast *expr, znode *result)
{
/* For .=, if the expression may contain objects, cast to a string before
* compiling the lhs W fetches. Otherwise, __toString() may cause the
* volatile zval storage to go away. */
if (opcode == ZEND_CONCAT && expr->kind != ZEND_AST_ZVAL) {
zend_op *cast_opline = zend_emit_op_tmp(result, ZEND_CAST, result, NULL);
cast_opline->extended_value = IS_STRING;
}
}

static void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
{
zend_ast *var_ast = ast->child[0];
Expand Down Expand Up @@ -3669,6 +3680,7 @@ static void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
offset = zend_delayed_compile_begin();
zend_delayed_compile_dim(result, var_ast, BP_VAR_RW, /* by_ref */ false);
zend_compile_expr_with_potential_assign_to_self(&expr_node, expr_ast, var_ast);
zend_cast_to_string_if_concat(opcode, expr_ast, &expr_node);

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

opline = zend_delayed_compile_end(offset);
cache_slot = opline->extended_value;
Expand Down
4 changes: 2 additions & 2 deletions ext/opcache/tests/jit/assign_obj_op_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ $test = new Test;
(function(){$this->y.=[];})->call($test);
?>
--EXPECTF--
Warning: Array to string conversion in %sassign_obj_op_001.php on line 6

Deprecated: Creation of dynamic property Test::$y is deprecated in %sassign_obj_op_001.php on line 6

Warning: Undefined property: Test::$y in %sassign_obj_op_001.php on line 6

Warning: Array to string conversion in %sassign_obj_op_001.php on line 6
Loading