php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #29493 extract(array, EXTR_REFS) fails if array has more than one referring symbol
Submitted: 2004-08-02 17:38 UTC Modified: 2004-08-10 08:06 UTC
From: msm at manley dot org Assigned: moriyoshi
Status: Closed Package: Unknown/Other Function
PHP Version: 5.0.0 OS: Linux, FreeBSD
Private report: No CVE-ID:
 [2004-08-02 17:38 UTC] msm at manley dot org
Description:
------------
Is there an issue with extract() using EXTR_REFS when more than one symbol points to an array variable?

EXTR_REF "extracts variables as references. This effectively means that the values of the imported variables are still referencing the values of the var_array parameter." (from http://us2.php.net/manual/en/function.extract.php)

From some experiments, I think that this function has issues when you pass it an array that has been given a reference or pass in a reference to an array itself. 

It three of the four testcases shown below, it would appear that the extracted variable $foo is NOT actually a reference into $array['foo'] after the extract() call.

PHP does lazy copies on arrays. Does modifying an array via a reference to one of the array's members not trigger the lazy copy? This is not the bug (and makes sense to me), though it certainly added to the confusion trying to figure out what was happenning here.

The testcases return the same results under PHP 4.3.4, PHP 4.3.8 and PHP 5.0.0 on all the FreeBSD, Linux and Windows systems I have access to.

Reproduce code:
---------------
I describe four testcases and their output at http://www.sitepoint.com/forums/showthread.php?p=1329965

Sorry, the testcases are longer than 20 lines, all total. In summary:

1) Copy array by reference  -- $array_b =& $array_a -- and then extract(array, EXTR_REFS) from each.

2) Copy array by value -- $array_b = $array_a -- and then extract(array, EXTR_REFS) from each.

3) Copy array by reference, then modify a value in the array, then extract(array, EXTR_REFS) from each.

4) Copy array by value, then modify a value in the array (triggering the lazy copy), then extract(array, EXTR_REFS) from each.

Expected result:
----------------
In each testcase, given an array ('foo' => 'value') I expect to see a variable $foo that is a reference to $array['foo'] and when I change $foo's value, the value of $array['foo'] should change accordingly.

Actual result:
--------------
Is the first three testcases $foo appears not to be a reference to $array_a['foo'] or $array_b['foo']. 

In the fourth testcase, extract(array, EXTR_REFS) performs as expected. 

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2004-08-03 18:18 UTC] msm at manley dot org
Ignore testcase #2. The behavior there is correct. PHP's lazy copy behavior, in combination with modifying the array via the ref to an entry not triggering the copy, threw me.

So it would appear that there is only an issue with extract(array, EXTR_REFS) when there are multiple entries in the symbol table for that array, which would be the first and third testcases.

I've looked at ext/standard/array.c and the extract() code, but I'm not familar enough with the code in there to figure out what's wrong.
 [2004-08-03 20:04 UTC] msm at manley dot org
I shortened my testcases down to the following:

<?php
rc1(); rc2(); rc3();

function rc1()
{
  echo "\n\n1: array copy by value\n";
  $a = array( 'foo' => 'bar');
  $b = $a;
  $b['foo'] = 'diff';
  print_r($a);
  echo "\n-----\nnow extract from a with EXTR_REFS\n";
  extract($a,EXTR_REFS);
  echo "post extract, foo = $foo\n";
  $foo = 'noo';
  echo "post reassign, foo = $foo\n";
  print_r($a);
  print_r($b);
}

function rc2()
{
  echo "\n\n2: array copy by reference\n";
  $a = array( 'foo' => 'bar');
  $b =& $a;
  print_r($a);
  echo "\n-----\nnow extract from a with EXTR_REFS\n";
  extract($a,EXTR_REFS);
  echo "post extract, foo = $foo\n";
  $foo = 'noo';
  echo "post reassign, foo = $foo\n";
  print_r($a);
}

function rc3()
{
  echo "\n\n3: array copy by reference then unset copy\n";
  $a = array( 'foo' => 'bar');
  $b =& $a;
  unset($b);
  print_r($a);
  echo "\n-----\nnow extract from a with EXTR_REFS\n";
  extract($a,EXTR_REFS);
  echo "post extract, foo = $foo\n";
  $foo = 'noo';
  echo "post reassign, foo = $foo\n";
  print_r($a);
}
?>

Testcases 1 and 3 pass - $foo is a ref to $a['foo']. Testcase 2 fails -- $foo does not appear to be a ref to $a['foo'].
 [2004-08-03 22:59 UTC] msm at manley dot org
Realizing I'm mostly conversing with myself here:

I'm not completely certain, but I think the issue is on line 1375 of ext/standard/array.c

SEPARATE_ZVAL_TO_MAKE_IS_REF(entry);

If I follow the logic back through the twisty little maze of zend.h macros, it would appear that when that macro is called and the refcount on the original entry is > 1, SEPARATE_ZVAL ends up copying the entry entirely.

But that would mean the individual entries in a array/hash have the same refcount as the array in general. Perhaps that's true? 

At this point I am in way deeper than I can figure, having never even looked at the PHP source before today.
 [2004-08-04 23:46 UTC] msm at manley dot org
The problem lies in extract()'s use of the SEPARATE_ZVAL macro in ext/standard/array.c. That macro actually makes a full copy of a ZVAL if the ZVAL's refcount is > 1. 

I assume that's used when doing lazy copies, normally.

The existing extract() function uses SEPARATE_ZVAL_TO_MAKE_IS_REF to set is_ref = 1 in the ZVAL. When the refcount on the extract()ed array is 1, no copy is made and the extracted variables are refs to the array member. When the refcount is > 1, a copy of the array entry gets made by SEPARATE_ZVAL and the extracted variable end up as refs to the copy.

Here is a patch that I believe fixes the problem. So far a modified version of PHP 5.0.0 has passed all the testcases with this patch in place.



--- array.c     Wed Aug  4 15:54:40 2004
+++ array.c.msm Wed Aug  4 16:42:01 2004
@@ -1372,7 +1372,7 @@
                                if (extract_refs) {
                                        zval **orig_var;

-                                       SEPARATE_ZVAL_TO_MAKE_IS_REF(entry);
+                                       (*(entry))->is_ref = 1;
                                        zval_add_ref(entry);

                                        if (zend_hash_find(EG(active_symbol_table), final_name.c, final_name.len+1, (void **) &orig_var) == SUCCESS) {
 [2004-08-05 00:44 UTC] msm at manley dot org
That patch is no good. It causes the testcase for bug 25708 to fail.
 [2004-08-06 16:35 UTC] msm at manley dot org
Sent patch to moriyoshi@php.net that passes all of the PHP distribution testcases for bug 25708 and all of the testcases above.
 [2004-08-09 00:37 UTC] moriyoshi@php.net
Seems a side-effect of the fix I did for bug #25708.

 [2004-08-10 08:06 UTC] moriyoshi@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Wed Apr 16 18:01:53 2014 UTC