From:             csaba at alum dot mit dot edu
Operating system: Win XP Pro
PHP version:      5CVS-2005-11-28 (snap)
PHP Bug Type:     Scripting Engine problem
Bug description:  array_splice resetting loop index when in function

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.

-- 
Edit bug report at http://bugs.php.net/?id=35438&edit=1
-- 
Try a CVS snapshot (php4):   http://bugs.php.net/fix.php?id=35438&r=trysnapshot4
Try a CVS snapshot (php5.0): 
http://bugs.php.net/fix.php?id=35438&r=trysnapshot50
Try a CVS snapshot (php5.1): 
http://bugs.php.net/fix.php?id=35438&r=trysnapshot51
Fixed in CVS:                http://bugs.php.net/fix.php?id=35438&r=fixedcvs
Fixed in release:            http://bugs.php.net/fix.php?id=35438&r=alreadyfixed
Need backtrace:              http://bugs.php.net/fix.php?id=35438&r=needtrace
Need Reproduce Script:       http://bugs.php.net/fix.php?id=35438&r=needscript
Try newer version:           http://bugs.php.net/fix.php?id=35438&r=oldversion
Not developer issue:         http://bugs.php.net/fix.php?id=35438&r=support
Expected behavior:           http://bugs.php.net/fix.php?id=35438&r=notwrong
Not enough info:             
http://bugs.php.net/fix.php?id=35438&r=notenoughinfo
Submitted twice:             
http://bugs.php.net/fix.php?id=35438&r=submittedtwice
register_globals:            http://bugs.php.net/fix.php?id=35438&r=globals
PHP 3 support discontinued:  http://bugs.php.net/fix.php?id=35438&r=php3
Daylight Savings:            http://bugs.php.net/fix.php?id=35438&r=dst
IIS Stability:               http://bugs.php.net/fix.php?id=35438&r=isapi
Install GNU Sed:             http://bugs.php.net/fix.php?id=35438&r=gnused
Floating point limitations:  http://bugs.php.net/fix.php?id=35438&r=float
No Zend Extensions:          http://bugs.php.net/fix.php?id=35438&r=nozend
MySQL Configuration Error:   http://bugs.php.net/fix.php?id=35438&r=mysqlcfg

Reply via email to