|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2019-05-18 20:25 UTC] tutano2004 at gmail dot com
Description:
------------
When an array is transversed with the value passed by reference, in a "foreach" loop, with the value then being passed by reference to another function (in this case "doReferenceStuff"), if the function internally instantiates 2 objects, each one with a reference to the other (circular reference), and with at least 1 of them with a reference to the value passed by reference, despite the fact that these objects are implicitly unset at the end of function (and thus "seize to exist"), the array still becomes an array of references in the end (by the time "var_dump" is called) rather than an array of values.
As noted in the test script, calling gc_collect_cycles prevents this bug from happening, meaning that something is not cleaned up well in these cases before the garbage collection kicks in.
If the circular reference is not made between both objects, this doesn't happen at all either.
The consequence of this later on is, in cases like this:
$array2 = $array;
$array2[1] = 'bar';
both $array and $array2 would have the element with the index 1 set as 'bar', rather than only the element in $array2 being set as such, given that this element would be a reference rather than a value, incorrectly so.
From my testing, this happens in every PHP version all the way back to PHP 5.0 at the very least, and all the way forward up to the latest stable version PHP 7.3.5.
I spent about 30min checking if there were any other bug reports of this kind given the number of PHP versions affected, and I found none thus far, hence creating this report, but finding the right words to summarize this problem is hard, so there's a good chance this was already reported but described differently.
None of the suggestions upon the submission of this report seem to point out to this problem having been reported either.
On another note, what led me to find this bug is a use case that I have in my own code, namely my own custom Exception class in a small library/framework I have been developing over the years, extending from the PHP Exception class, in which I instantiate with properties rather than a message, and internally I have a properties manager class/object that has a back reference to the Exception itself, so when an Exception is thrown, it also has a reference to this manager, and the manager has a reference to the Exception (creating a circular reference), and whenever an Exception is thrown, the PHP engine itself stores a stack trace within the Exception, and it's this stack trace which ends up holding the reference (&$v), since in the stack one of the functions receives the value by reference.
Test script:
---------------
function doReferenceStuff(&$v)
{
$c1 = new stdClass;
$c2 = new stdClass;
$c1->c2 = $c2;
$c2->c1 = $c1;
$c1->v = &$v;
}
$array = [1, 'foo', new stdClass];
foreach ($array as &$v) {
doReferenceStuff($v);
}
unset($v);
//gc_collect_cycles(); --> adding this here prevents the bug
var_dump($array);
Expected result:
----------------
array(3) {
[0]=>
int(1)
[1]=>
string(3) "foo"
[2]=>
object(stdClass)#7 (0) {
}
}
Actual result:
--------------
array(3) {
[0]=>
&int(1)
[1]=>
&string(3) "foo"
[2]=>
&object(stdClass)#7 (0) {
}
}
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Dec 20 07:00:01 2025 UTC |
Indeed, even something simple like this: $a = []; for ($i = 0; $i < 10e3; $i++) { $c = new stdClass; $a[] = $c; unset($c); } before the var_dump was enough for the cycle to be triggered and for references to be cleaned up. Thank you for your insights. :)