|  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #74020 Foreach-by-reference + assignment may recreate unexpected key references
Submitted: 2017-01-31 03:08 UTC Modified: 2019-02-17 12:37 UTC
Avg. Score:4.7 ± 0.5
Reproduced:3 of 3 (100.0%)
Same Version:1 (33.3%)
Same OS:1 (33.3%)
From: icarpenter at leadid dot com Assigned: requinix (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: 7.0.15 OS:
Private report: No CVE-ID: None
 [2017-01-31 03:08 UTC] icarpenter at leadid dot com
After a foreach-by-reference, an array may create references to keys if you create a copy of the original variable (either directly or indirectly via foreach) and then on that new variable, create a copy of a key to another key. The original key in that variable will then become a reference to the same key in the original array.

In PHP 5.6 and 7.1, this can be avoided by either having the foreach run in a different scope, or unsetting the reference variable after the loop. PHP 7.0 exhibits this behavior regardless.

Test script:
--------------- (Triggers the bug in PHP 7.0) (Triggers the bug in PHP 5.6, 7.0, and 7.1)

Expected result:
string(3) "foo"

Actual result:
string(3) "bar"


Add a Patch

Pull Requests

Add a Pull Request


AllCommentsChangesGit/SVN commitsRelated reports
 [2017-01-31 08:57 UTC]
-Summary: Foreach-by-reference may create unexpected key references +Summary: Foreach-by-reference + assignment may recreate unexpected key references -Status: Open +Status: Verified -Package: Arrays related +Package: Scripting Engine problem
 [2017-01-31 08:57 UTC]
Note that var_dump on the array will show references.

The first one looks like a bug as the reference was (supposedly) destroyed, however it comes back with the foo_copy=foo assignment.

The second is not because $item is still a reference after the loop and when $mockCopy copies $mock it gets foo as a copy *of the reference*. Adding unset($item) is the recommended solution.
 [2017-01-31 15:28 UTC] icarpenter at leadid dot com
While I agree that unsetting the $item is a best practice, the docs only really say you'll run into issues if you happen to do something with the reference variable. Since I'm not doing that here, one wouldn't expect references to get created when copying the original variable. Maybe the docs can be updated to say that foreach by reference will not only leave a variable with a reference to the last key in the array hanging around, but also the original array may contain references as well unless you unset the variable.
 [2018-10-23 20:25 UTC] fra dot martin at free dot fr
This is probably the same issue, much more direct and very disturbing for me. These two simple foreach loops do nothing, they just use the same variable name, once by reference, once by value: the last array element always becomes a copy of the penultimate element!

$items = [1,2,3];
foreach($items as &$item);
foreach($items as $item);
echo array_pop($items); 

Expected result: 3
Actual result: 2

Expected result:
 [2018-10-23 21:30 UTC] spam2 at rhsoft dot net
your expectation is imply wrong

foreach($items as &$item);
foreach($items as $item);

you miss the unset!
either understand what references are or don't use them
 [2018-10-23 22:54 UTC] fra dot martin at free dot fr
I know what a reference is... 
But you are right, my expectation is false. Indeed I did not take time to understand step by step what happened here: 
- the first foreach leaves $item referencing the last array's element
- the second foreach loop assigns successively $item with the 1st value (which also modifies the last element since $item is a pointer on it, its value is now 1), then the 2nd one (which re-assigns $item and the last element, its value becomes 2), then the 3rd one, which re-assings $item and itself with its current value '2'.
Sorry for my previous useless comment.

In my previous example, the first foreach loop creates a reference variable and makes it reference successively the 1st, 2nd and 3rd element in the array. After the first foreach loop, I understand that $item still references the last array's element (int(3)), that's what would give a 'var_dump($item);'

The second foreach loop uses the same variable (still a reference since created as such in the first foreach loop) and iterates over each element, assigning
 [2018-10-23 22:59 UTC] fra dot martin at free dot fr
Previous comment was supposed to end at «...Sorry for my previous useless comment.»
 [2019-02-17 12:29 UTC] glueball at gmail dot com
I've found another instance of this same issue (I think it's the same):

While it's true that it gets fixed easily by unsetting the variable after the loop, this behavior is very unexpected, and does not seem right. I'd expect $item to be a reference to the last array item after the first loop, but apparently the last item in the array is also a reference to "something" (as can be seen by the "&" sign in the intermediate var_dump)
 [2019-02-17 12:37 UTC]
-Status: Verified +Status: Closed -Assigned To: +Assigned To: requinix
 [2019-02-17 12:37 UTC]
They're both references to the same thing, because

> I'd expect $item to be a reference to the last array item after the first loop
that's not what is actually happening. A reference is not technically to another variable but to a shared value in memory. $item does not "point" to the last item in the array, rather it and the array item both use the same shared value. Thus why they are both references. unset()ing $item destroys one of the two references, and since there's only one left it does not need to be a reference anymore.

Closing because as far as I know the originally-reported bug only affects PHP 7.0 and that is no longer supported.
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Wed Aug 21 23:01:26 2019 UTC