Bug #78032 [Nab]: Circular references from unset objects keep references to array elements
| From: | tutano2004 at gmail dot com | Date: | Sun, 19 May 2019 10:48:56 +0000 |
| Subject: | Bug #78032 [Nab]: Circular references from unset objects keep references to array elements | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-220899@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=78032&edit=1
ID: 78032
User updated by: tutano2004 at gmail dot com
Reported by: tutano2004 at gmail dot com
Summary: Circular references from unset objects keep
references to array elements
Status: Not a bug
Type: Bug
Package: Scripting Engine problem
PHP Version: Irrelevant
Block user comment: N
Private report: N
New Comment:
I am sorry, but I am having a bit of a hard time wrapping my head around the way this is explained
in that particular page (the diagram used is very confusing, at least for me), although the paper
it's based off (linked in that page) seems to explain it a bit more clearly, so if I understood
the paper correctly (although I didn't read it entirely), do you mean this has to do with the
root buffer?
As in, if there are no circular references ever, the refcount is decreased and always hits zero, so
the zval is immediately released, however if the reference is circular, then the only thing which
can free it is the actual GC cycle, by checking these zvals starting from the root buffer itself,
since the refcount will never be zero for the zvals with circular references, is that it?
If that's so, then it makes sense.
Sorry for the bug report, and thank you for the link. :)
Previous Comments:
------------------------------------------------------------------------
[2019-05-18 23:43:45] requinix@php.net
This is by design. https://www.php.net/manual/en/features.gc.collecting-cycles.php
------------------------------------------------------------------------
[2019-05-18 20:25:29] 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) {
}
}
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=78032&edit=1