Bug #66040 [Opn->Nab]: Cloning an object while having reference to members results incorrect clone
| From: | cmb@php.net | Date: | Wed, 03 Oct 2018 21:17:51 +0000 |
| Subject: | Bug #66040 [Opn->Nab]: Cloning an object while having reference to members results incorrect clone | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-217397@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=66040&edit=1
ID: 66040
Updated by: cmb@php.net
Reported by: bawolff+wn at gmail dot com
Summary: Cloning an object while having reference to members
results incorrect clone
-Status: Open
+Status: Not a bug
Type: Bug
Package: Scripting Engine problem
Operating System: linux
PHP Version: 5.5Git-2013-11-07 (snap)
-Assigned To:
+Assigned To: cmb
Block user comment: N
Private report: N
New Comment:
> [â¦] has a reference to [â¦]
To quote @requinix[1]:
| That is the fundamental misunderstanding here: references do not
| go "to" other variables. A reference means that two or more values
| refer to the same underlying data in PHP's internals. [â¦]
And the docs say[2]:
| Any properties that are references to other variables will
| remain references.
So yes, this is expected behavior.
[1] <https://bugs.php.net/bug.php?id=76863>
[2] <http://php.net/manual/en/language.oop5.cloning.php>
Previous Comments:
------------------------------------------------------------------------
[2018-06-04 18:02:55] david at frankieandshadow dot com
$list["{$obj->field}"] = clone $obj
also makes it work.
------------------------------------------------------------------------
[2018-06-04 17:59:29] david at frankieandshadow dot com
By the way...
(a) it's definitely $list[$obj->field] that's provoking the problem. If I change it to
$list[$obj->anotherfield], then 'anotherfield' is the one that gets overwritten.
(b) it's wrong on return from f, i.e. if I print the list before the clone line on the sedcond
iteration, then the first element of the list has already been overwritten by f. It's not the
assignment in the loop that is doing the overwriting.
(c) if I change list to be an object instead of array, and have
$list{$obj->field} = clone $obj
it still fails
------------------------------------------------------------------------
[2018-06-04 17:49:53] david at frankieandshadow dot com
I think I am encountering this bug, in a slightly different arrangement, and in PHP7.0.27 (the most
up-to-date Debian release of PHP), so if it is the same, as seems likely, it's a rather nasty
core bug that's been around for a long time.
I also verified that @bawolff's example still fails in 7.0.27.
I have
while ($q->f($obj) > 0) {
$list[$obj->field] = clone $obj;
}
where f sets fields in $obj. The list ends up with all 'field' members being the same as
the final one. I don't think there is any PHP I could write in f that would do this
accidentally as it doesn't have access to $list and clone should make the objects be distinct
from each other.
If I change it to:
while ($q->f($obj) > 0) {
$k = obj->field;
$list[$k] = clone $obj;
}
it works. Whether this is because the code generated has been perturbed slightly, or it really is
changing the example enough to not be hitting the bug I can't tell. But my guess is the while
code block is equivalent to @bawolff's function and the $list[$k] is the reference that is
still live when it calls f again causing it to overwrite - which is why I think my bug is
essentially the same.
This isn't the case for all f. In my case f is the result of a database query defined by $q,
and f results in a MySQL select, returning the number of results remaining. I simplified it down to
mimicking that returning objects in turn from a short array instead, but that doesn't fail.
------------------------------------------------------------------------
[2013-11-07 02:54:13] bawolff+wn at gmail dot com
Description:
------------
The problem is easiest understood by looking at the test script.
Basically, if you clone an object in a function that as one of its arguments (or one of its calling
functions) has a reference to a member variable of this object, the clone of the object is not
correct for the field that the reference is to. The fields of the two objects behave as if they are
references to each other (To clarify I do not mean fields point to the same object as is expected, I
mean the field itself is a reference to the other field.). If you assign something to that member
variable, the change is reflected in both the cloned object and the original, where it should only
be reflected in the object that the assignment was for.
Tested on snapshot php5.5-201311070030
Also tested on earlier versions like 5.3.3-7
Originally discovered while investigating a bug in MediaWiki: https://bugzilla.wikimedia.org/show_bug.cgi?id=56226
Test script:
---------------
<?php
$originalObj = new A;
class A {
var $foo = 'default value';
}
// Important note: If you remove the &, the bug disappears.
function changeFooWithUnusedReference( &$unusedReferenceToFoo, $newValue ) {
global $originalObj;
$newObj = clone $originalObj;
echo $originalObj->foo . "\n";
echo $newObj->foo . "\n";
$newObj->foo = $newValue;
echo $originalObj->foo . "\n";
echo $newObj->foo . "\n";
}
changeFooWithUnusedReference( $originalObj->foo, 'Some other value' );
Expected result:
----------------
I expect the following output:
default value
default value
default value
Some other value
Actual result:
--------------
Following output:
default value
default value
Some other value
Some other value
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=66040&edit=1