php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #66040 Cloning an object while having reference to members results incorrect clone
Submitted: 2013-11-07 02:54 UTC Modified: 2018-10-03 21:17 UTC
Votes:5
Avg. Score:4.6 ± 0.5
Reproduced:5 of 5 (100.0%)
Same Version:2 (40.0%)
Same OS:4 (80.0%)
From: bawolff+wn at gmail dot com Assigned: cmb (profile)
Status: Not a bug Package: Scripting Engine problem
PHP Version: 5.5Git-2013-11-07 (snap) OS: linux
Private report: No CVE-ID: None
 [2013-11-07 02:54 UTC] 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


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-06-04 17:49 UTC] 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.
 [2018-06-04 17:59 UTC] 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 18:02 UTC] david at frankieandshadow dot com
$list["{$obj->field}"] = clone $obj
also makes it work.
 [2018-10-03 21:17 UTC] cmb@php.net
-Status: Open +Status: Not a bug -Assigned To: +Assigned To: cmb
 [2018-10-03 21:17 UTC] cmb@php.net
> […] 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>
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Tue Nov 24 12:01:28 2020 UTC