Skip to content
Draft
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
38 changes: 38 additions & 0 deletions Zend/tests/gh15938-005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
GH-15938 005: ASSIGN_OBJ on typed 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==
207 changes: 207 additions & 0 deletions Zend/tests/gh15938-006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
--TEST--
GH-15938 006: 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 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',
'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"
# remove_current_source_in_list:
object(class@anonymous)#1 (1) {
["a"]=>
uninitialized(string)
["b"]=>
&string(10) "aaaaaaaaaa"
}
string(10) "aaaaaaaaaa"
# remove_next_source_in_list:
object(class@anonymous)#1 (1) {
["a"]=>
&string(10) "aaaaaaaaaa"
["b"]=>
uninitialized(string)
}
string(10) "aaaaaaaaaa"
# remove_all_sources_in_list:
object(class@anonymous)#1 (0) {
["a"]=>
uninitialized(string)
["b"]=>
uninitialized(string)
}
string(10) "aaaaaaaaaa"
# cleanup_shrink:
object(class@anonymous)#1 (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==
Loading