php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #35438 array_splice resetting loop index when in function
Submitted: 2005-11-28 04:33 UTC Modified: 2005-11-28 20:54 UTC
From: csaba at alum dot mit dot edu Assigned:
Status: Not a bug Package: Arrays related
PHP Version: 5CVS-2005-11-28 (snap) OS: Win XP Pro
Private report: No CVE-ID: None
 [2005-11-28 04:33 UTC] csaba at alum dot mit dot edu
Description:
------------
I apologize in advance if this is not a bug, but I've narrowed it down as far as I can and can't see the problem, so here goes...

I have an array that essentially contains the results of a (BFS) breadth first search, a recursive directory listing.  I want to represent the heirarchical nature of the listing which means that I want the results of the BFS converted to a DFS (depth first search) format.  I accomplish this by means of a double loop.  The actual transpositioning of the array elements happens by means of a double array_splice: Use an inner array_splice to cut out a single entry (which returns that entry) and then splice that in using the outer array_splice.  In fact, this works when the code is implemented at the top level.

However, if I encapsulate the code in a function which passes $aDir by reference, and then call the function, I wind up in an infinite loop.  The double splice is performed just fine, but then outer loop counter ($i) resets upon the next pass through the foreach, whereas this does not happen if the code is not encapsulated within a function.

After spending some time on this, I can't spot the reason.  Now I know all bets are off you are modifying the array (which is the reason for the advance apology), but the behaviour difference of top level vs. function seemed interesting enough to warrant a report.  I can additionally say that this is specific to foreach and pass by reference.  If I replace the foreach with a
for ($i=0;$i<sizeof($aDir);++$i) {
    $file = $aDir[$i];
then the code works as expected.

Also, if I keep the foreach but declare the bfs2dfs with
function bfs2dfs ($aDir) {
then the code also works as expected.

It is only the pass by reference, foreach version that goes into the infinite loop.

Sincerely,
Csaba Gabor from Vienna

Reproduce code:
---------------
$aDir = array("B/", "file", "B/C/");
var_dump($aDir); print "<br>\n";
bfs2dfs($aDir);

function bfs2dfs(&$aDir) {
  foreach ($aDir as $i => $file) {
print "$i: $file<br>\n";
    $slashPos = strlen($file)-1;  // final slash pos
    while (($slashPos = strrpos($file, "/",
        $slashPos-strlen($file)-1))!==false)
      if (($key=array_search($common=substr($file,
          0,$slashPos+1),$aDir))!==false) {
        for ($j=$key+1;$j<$i;++$j)
            if ($common!=substr($aDir[$j],0,
                $slashPos+1))
              break;
        array_splice($aDir, $j, 0,
            array_splice($aDir, $i, 1));			print "after splice with (i, j, file) " .
      "as ($i, $j, $file)<br>\n";
var_dump($aDir);
print "<br>\n";
        continue 2; }}
}


The idea behind this code is that we march through the array and each time we encounter a file or dir, we move it to be the last child (of already encountered children) of the most immediate (already encountered) ancestor.

We do this by peeling off lower level subdirectories one after another (that's what the while does) and see if what remains has already been encountered (that's what the first if tests), hence processed.  If so, we march forward from that point (the for loop), checking to see (the second if) when we are no longer a descendent of this ancestor.  That gives the position to insert the current file, which is done by means of the double array_splice.

Again, if I comment out the function definition line and its closing '}' and the bfs2dfs($aDir) call, the code works as expected.  If the foreach is replace by a for, the code works as expected, and if the function declares $aDir by value instead of &$aDir by ref, the code also works as expected.

Expected result:
----------------
array(3) { [0]=>  string(2) "B/" [1]=>  string(4) "file" [2]=>  string(4) "B/C/" }
0: B/
1: file
2: B/C/
after splice with (i, j, file) as (2, 1, B/C/)
array(3) { [0]=> string(2) "B/" [1]=> string(4) "B/C/" [2]=> string(4) "file" }

Actual result:
--------------
exactly the same as with the expected result, but then it continues looping infinitely with the following:

0: B/
1: B/C/
after splice with (i, j, file) as (1, 1, B/C/)
array(3) { [0]=> string(2) "B/" [1]=> string(4) "B/C/" [2]=> string(4) "file" }


The interesting thing here is not the infinite loop, but the fact that the loop counter ($i) reset to 0.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2005-11-28 15:46 UTC] csaba at alum dot mit dot edu
OK, this is my last posting of a simplified example:
<?php
$ar = array("a", "b");
myfunc($ar);

function myfunc(&$ar) {
  foreach ($ar as $i => $entry) {
print "<br>$i: $entry, {$ar[$i]}\n";
    array_splice($ar, $i, 0, array_splice($ar, sizeof($ar)-1, 1));
var_dump($ar);
}}
?>

The above ACTUAL results in an infinte loop:
0: a, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
0: b, b array(2) { [0]=> string(1) "a" [1]=> string(1) "b" }
0: a, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
0: b, b array(2) { [0]=> string(1) "a" [1]=> string(1) "b" }
0: a, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
0: b, b array(2) { [0]=> string(1) "a" [1]=> string(1) "b" } 
...


The EXPECTED is what you get if you declare myfunc without the &:
0: a, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
1: b, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
Note that $entry and $ar[$i] differ on the second line
 [2005-11-28 19:12 UTC] tony2001@php.net
Yes, array_splice() resets internal pointer of the array and this is expected, because is being modified in this function.
Looking at the code, I should say you're definitely doing something wrong and I don't see a reason why you're expecting to get something different from the actual result.
 [2005-11-28 19:42 UTC] csaba at alum dot mit dot edu
OK, let's suppose that it is true that the array's pointer gets reset.

In that case, why does the version where myfunc is declared without the '&' not produce an infinite loop, and why does the inline version (below) not produce an infinite loop?  If the array's pointer is simply reset then all of the three versions should fail (go into an infinite loop) in the same way.  But only the '&' version fails according to the model you just presented.  There's clearly another piece to this puzzle.

<?php
$ar = array("a", "b");
//myfunc($ar);

//function myfunc($ar) {
  foreach ($ar as $i => $entry) {
print "<br>$i: $entry, {$ar[$i]}\n";
    array_splice($ar, $i, 0, array_splice($ar, sizeof($ar)-1, 1));
var_dump($ar);
}
//}
?>

The ACTUAL results of this inline version produce the same result as the non '&' myfunc version (which you can get by uncommenting the three commented out lines above):
0: a, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
1: b, a array(2) { [0]=> string(1) "b" [1]=> string(1) "a" }
 [2005-11-28 20:54 UTC] tony2001@php.net
I can't understand what are you trying to do with this code, but it still looks wrong.
DO NOT modify the array in the foreach body.
In your case array_splice() modifies array only in case it's already a reference, this is expected.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat May 18 22:01:31 2024 UTC