Skip to content

Dereferencing an array in foreach unwraps the reference #11665

Closed as not planned

Description

@bwoebi

Description

The following code (https://3v4l.org/8QBeN):

<?php

$a = [1]; 
foreach ((isset($a["key"]) ? [&$a["key"]] : [&$a])[0] as &$v) {
    var_dump($a);
    $v++;
}
var_dump($a);

Resulted in this output:

array(1) {
  [0]=>
  int(1)
}
array(1) {
  [0]=>
  int(1)
}

But I expected this output instead:

array(1) {
  [0]=>
  &int(1)
}
array(1) {
  [0]=>
  &int(2)
}

In PHP 7.2 and before, the output was as expected. Today it's required to use an explicit variable to preserve the reference.

PHP Version

PHP 7.3.0 - master

Operating System

No response

Activity

  1. ndossche commented on Jul 10, 2023

    @ndossche
    Member

    Bisect lead me to 61d00a6 as the first bad commit, although looking at the generated opcodes makes me wonder why there is no W fetch so the root cause is probably different EDIT: I'm blind

  2. usarise commented on Jul 10, 2023

    @usarise
    Contributor

    Is not it natural effect of backward incompatible change?

  3. bwoebi commented on Jul 10, 2023

    @bwoebi
    MemberAuthor

    @usarise Not quite - maybe that's a side-effect of the change, but that change refers to the sequencing when unwrapping happens, when a read as a temporary value and a write of the same reference happen to be arguments for the same operation.

  4. kamil-tekiela commented on Nov 7, 2026

    @kamil-tekiela
    Member

    Simplified:

    <?php
    $a = [1]; 
    foreach ([&$a][0] as &$v) { $v++; }
    var_dump($a);
    

    I don't think we should fix anything here. For me this behaviour is as expected. The expression is a temporary, and foreach by reference over a temporary iterates a copy. There is also a very simple workaround, just bind a variable first before the loop or encapsulate it in a function.

    This is similar to:

    <?php
    $a = [1]; $b = [4];
    foreach ((true ? $a : $b) as &$v) { $v++; }
    var_dump($a);
    

    Fixing this edge case would have a negative effect on OPCache. See also 61d00a6cf3d
    If anything, it looks to me like PHP 7.3 fixed a reference leak on read operation. A temporary should not leak a reference. This is exactly the change that usarise referred to.

    Compare it to:

    <?php
    $a = 1; 
    foreach ([&$a] as &$v) { $v++; }
    var_dump($a);
    
    • foreach ([&$a] as &$v): The temporary is the array [&$a] itself. Its element 0 is a reference to $a. When an array is copied, references inside it stay references. Foreach binds $v to element 0, and that element is already a reference to $a, so $v++ changes $a. No read fetch is involved, so nothing unwraps the reference.
    • foreach ([&$a][0] as &$v): The temporary is the result of [0]. The read fetch unwraps the reference, so the temporary holds the value of $a, the array [1]. Its elements are plain values, not references. $v binds to an element of that copy, so $v++ does not reach $a.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions