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
37 changes: 37 additions & 0 deletions Zend/tests/gh20316-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
GH-20316 001: Assign to ref: non-ref rvalue may be turned into a ref
--FILE--
<?php

class C {
public string $a = '';
public $b;
function __toString() {
global $c; // turns rvalue into a ref
return '';
}
}

for ($i = 0; $i < 2; $i++) {
$c = new C;
$c->b = &$c->a;
$c->b = $c;

var_dump($c);
unset($c);
}

?>
--EXPECTF--
object(C)#%d (2) {
["a"]=>
&string(0) ""
["b"]=>
&string(0) ""
}
object(C)#%d (2) {
["a"]=>
&string(0) ""
["b"]=>
&string(0) ""
}
28 changes: 28 additions & 0 deletions Zend/tests/gh20316-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
GH-20316 002: Assign to ref: ref rvalue may be unset
--FILE--
<?php

class C {
public string $a = '';
public $b;
function __toString() {
unset($GLOBALS['c']);
unset($GLOBALS['r']);
return '';
}
}

for ($i = 0; $i < 2; $i++) {
$c = new C;
$r = &$c;
$c->b = &$c->a;
$c->b = $c;

var_dump(isset($c));
}

?>
--EXPECT--
bool(false)
bool(false)
26 changes: 26 additions & 0 deletions Zend/tests/gh20316-003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
GH-20316 003: Assign to ref: rvalue may be unset
--FILE--
<?php

class C {
public string $a = '';
public $b;
function __toString() {
unset($GLOBALS['c']);
return '';
}
}

for ($i = 0; $i < 2; $i++) {
$c = new C;
$c->b = &$c->a;
$c->b = $c;

var_dump(isset($c));
}

?>
--EXPECT--
bool(false)
bool(false)
38 changes: 38 additions & 0 deletions Zend/tests/gh20318-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
GH-20318 001: Assign to ref: Ref may be freed by __toString()
--CREDITS--
iluuu1994
--FILE--
<?php

class C {
public mixed $prop1;
public ?string $prop2;

public function __toString() {
unset($this->prop1);
unset($this->prop2);
return 'bar';
}
}

function test() {
$c = new C();
$c->prop1 = 'foo';
$c->prop1 = &$c->prop2;
$c->prop1 = $c;
var_dump($c);
}

test();

?>
==DONE==
--EXPECTF--
object(C)#%d (0) {
["prop1"]=>
uninitialized(mixed)
["prop2"]=>
uninitialized(?string)
}
==DONE==
242 changes: 242 additions & 0 deletions Zend/tests/gh20318-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
--TEST--
GH-20318 002: Assign to ref: Concurrent reference source list mutations variations
--ENV--
LEN=10
--FILE--
<?php

function remove_single_ptr_source() {
$obj = new class {
public string $a = '';
function __toString() {
unset($this->a);
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$r = $obj;

var_dump($obj, $r);
}

function replace_single_ptr_source_incompatible() {
$obj = new class {
public int|string $a = 0;
public int $b = 0;
public $c;
function __toString() {
unset($this->a);
$this->b = &$this->c;
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$obj->c = &$r;
try {
$r = $obj;
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}

var_dump($obj, $r);
}

function remove_current_source_in_list() {
$obj = new class {
public string $a = '';
public string $b = '';
function __toString() {
unset($this->a);
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$obj->b = &$obj->a;
$r = $obj;

var_dump($obj, $r);
}

function remove_next_source_in_list() {
$obj = new class {
public string $a = '';
public string $b = '';
function __toString() {
unset($this->b);
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$obj->b = &$obj->a;
$r = $obj;

var_dump($obj, $r);
}

function remove_all_sources_in_list() {
$obj = new class {
public string $a = '';
public string $b = '';
function __toString() {
unset($this->a);
unset($this->b);
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$obj->b = &$obj->a;
$r = $obj;

var_dump($obj, $r);
}

function add_sources() {
$obj = new class {
public string $a = '';
public string $b = '';
public string $c = '';
public string $d = '';
public string $e = '';
public string $f = '';
public string $g = '';
public string $h = '';
function __toString() {
var_dump(__METHOD__);
$this->b = &$this->a;
$this->c = &$this->a;
$this->d = &$this->a;
$this->e = &$this->a;
$this->f = &$this->a;
$this->g = &$this->a;
$this->h = &$this->a;
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
$r = $obj;

var_dump($obj, $r);
}

function cleanup_shrink() {
$obj = new class {
public string $a = '';
};

$r = &$obj->a;

$objs = [];
for ($i = 0; $i < 100; $i++) {
$objs[] = clone $obj;
}

$r = new class($objs) {
function __construct(public mixed &$objs) {}
function __toString() {
$this->objs = array_slice($this->objs, 0, 2);
return str_repeat('a', getenv('LEN'));
}
};

var_dump($obj, $r);
}

function add_incompatible() {
$obj = new class {
public int|string $a = 1;
public int $b = 2;
function __toString() {
$this->b = &$this->a;
return str_repeat('a', getenv('LEN'));
}
};

$r = &$obj->a;
try {
$r = $obj;
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}

var_dump($obj, $r);
}

foreach ([
'remove_single_ptr_source',
'replace_single_ptr_source_incompatible',
'remove_current_source_in_list',
'remove_next_source_in_list',
'remove_all_sources_in_list',
'cleanup_shrink',
'add_incompatible',
] as $func) {
echo "# ", $func, ":\n";
$func();
}

?>
==DONE==
--EXPECT--
# remove_single_ptr_source:
object(class@anonymous)#1 (0) {
["a"]=>
uninitialized(string)
}
string(10) "aaaaaaaaaa"
# replace_single_ptr_source_incompatible:
TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int
object(class@anonymous)#1 (2) {
["a"]=>
uninitialized(string|int)
["b"]=>
&int(0)
["c"]=>
&int(0)
}
int(0)
# remove_current_source_in_list:
object(class@anonymous)#2 (1) {
["a"]=>
uninitialized(string)
["b"]=>
&string(10) "aaaaaaaaaa"
}
string(10) "aaaaaaaaaa"
# remove_next_source_in_list:
object(class@anonymous)#2 (1) {
["a"]=>
&string(10) "aaaaaaaaaa"
["b"]=>
uninitialized(string)
}
string(10) "aaaaaaaaaa"
# remove_all_sources_in_list:
object(class@anonymous)#2 (0) {
["a"]=>
uninitialized(string)
["b"]=>
uninitialized(string)
}
string(10) "aaaaaaaaaa"
# cleanup_shrink:
object(class@anonymous)#2 (1) {
["a"]=>
&string(10) "aaaaaaaaaa"
}
string(10) "aaaaaaaaaa"
# add_incompatible:
TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int
object(class@anonymous)#3 (2) {
["a"]=>
&int(1)
["b"]=>
&int(1)
}
int(1)
==DONE==
8 changes: 3 additions & 5 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -5068,17 +5068,15 @@ ZEND_API zend_result zend_update_static_property_ex(zend_class_entry *scope, zen
}

ZEND_ASSERT(!Z_ISREF_P(value));
Z_TRY_ADDREF_P(value);
ZVAL_COPY(&tmp, value);
if (ZEND_TYPE_IS_SET(prop_info->type)) {
ZVAL_COPY_VALUE(&tmp, value);
if (!zend_verify_property_type(prop_info, &tmp, /* strict */ 0)) {
Z_TRY_DELREF_P(value);
zval_ptr_dtor(&tmp);
return FAILURE;
}
value = &tmp;
}

zend_assign_to_variable(property, value, IS_TMP_VAR, /* strict */ 0);
zend_assign_to_variable(property, &tmp, IS_TMP_VAR, /* strict */ 0);
return SUCCESS;
}
/* }}} */
Expand Down
Loading