Bug #78032 [Opn->Nab]: Circular references from unset objects keep references to array elements

From: Date: Sat, 18 May 2019 23:43:45 +0000
Subject: Bug #78032 [Opn->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-220893@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 Updated by: requinix@php.net Reported by: tutano2004 at gmail dot com Summary: Circular references from unset objects keep references to array elements -Status: Open +Status: Not a bug Type: Bug Package: Scripting Engine problem PHP Version: Irrelevant Block user comment: N Private report: N New Comment: This is by design. https://www.php.net/manual/en/features.gc.collecting-cycles.php Previous Comments: ------------------------------------------------------------------------ [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

« previous php.bugs (#220893) next »