php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #27336 inconsistency of unserializing references
Submitted: 2004-02-20 16:22 UTC Modified: 2004-02-21 11:55 UTC
Votes:3
Avg. Score:5.0 ± 0.0
Reproduced:3 of 3 (100.0%)
Same Version:1 (33.3%)
Same OS:1 (33.3%)
From: hawcue at yahoo dot com Assigned:
Status: Wont fix Package: Class/Object related
PHP Version: 4.3.4 OS: Windows XP
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If this is not your bug, you can add a comment by following this link.
If this is your bug, but you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: hawcue at yahoo dot com
New email:
PHP Version: OS:

 

 [2004-02-20 16:22 UTC] hawcue at yahoo dot com
Description:
------------
Please read the codes first.

The actual output shows two copies of $parentNode are 
generated by unserialize(). However, this is not
what I expect because I am using reference and
$newParent->child->parent and $newParent should
refer to the same storage if serialize/unserialize
works correctly. 

The problem can be partially solved by using 
the following line to do serialize:

$str = serialize(&$parentNode); // an ampersand is used

This will generate a compiling warning (depreciation
of using ampersand in function parameters).


Reproduce code:
---------------
class Node {
  var $value;
  var $child;
  var $parent;
  function Node($value) {
    $this->value = $value;
  }
  function setParent(&$parent) {
    $this->parent = &$parent;
  }
  function setChild(&$child) {
    $this->child = &$child;
  }
}

$parentNode = new Node('parent');
$childNode = new Node('child');
$parentNode->setChild($childNode);
$childNode->setParent($parentNode);

$str = serialize($parentNode);
$newParent = unserialize($str);
$newParent->value = 'new parent';
echo $newParent->child->parent->value;
echo $newParent->value;


Expected result:
----------------
new parent
new parent

Actual result:
--------------
parent
new parent

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2004-02-21 03:12 UTC] sniper@php.net
Don't mess with references and serializing. That doesn't work and it won't work.

 [2004-02-21 11:55 UTC] hawcue at yahoo dot com
I believe this is a bug. It is related with 
how PHP determines recursion levels. From
what we have observed, I think PHP determines
the recursion level by comparing the physical
addresses of two references. This is why
serialize(&$parentNode) works as expected
because the address is passed in.
 [2004-10-05 10:34 UTC] jan at bestbytes dot de
<?php

// this example is for php 4

class Node {
  var $value;
  var $child;
  var $parent;
  var $load;
  var $__destroyed = false;
  function Node($value) 
  {
    $this->value = $value;
    global $load;
    $this->load = $load;
  }
  function setParent(&$parent)
  {
    $this->parent = &$parent;
  }
  function setChild(&$child)
  {
    $this->child = &$child;
  }
  /**
  * @return void
  * @desc fake __destroy from a php 4 point of view
  */
  function __destroy()
  {
    $props = get_object_vars($this);
    foreach($props as $propName => $propValue) {
      unset($propValue);
      if(is_object($this->$propName)) {
        if(method_exists($this->$propName, '__destroy') AND $this->$propName->__destroyed !== false) {
          $this->$propName->__destroy();
        }
      }
      if(isset($this->$propName)) {
        unset($this->$propName);
      }
    }
  }
}
header('Content-type: text/plain');

// small load to check the serialized string
$loadSize = 4;

// big load to check leaking
//$loadSize = 32 * 1024;

$load     = str_repeat('.', $loadSize);

for($i = 0;$i<10;$i++) {
  echo '-------------------- '.(memory_get_usage()/1024).' -------------------------'.chr(10);
  
  $serMe = array();
  $serMe['parent'] =& new Node('parent');
  $serMe['child']  =& new Node('child');
  
  /*
   * this will work too !!
   *   $serMe['parent'] = new Node('parent');
   *   $serMe['child']  = new Node('child');
   */

  $serMe['parent']->setChild($serMe['child']);
  $serMe['child']->setParent($serMe['parent']);
  
  
  $str = serialize($serMe);
  
  /*
   * 1.Problem:
   * 
   * You have 2 or more objects with circular references to one another.
   * Serializing these objects will result in unexpected behaviour when
   * unserializing them:
   *
   * Former references will become copies.
   * 
   * This is because the php serialize handler can not handle references,
   * unless all involved objects, variables ... are within an array(),
   * that will be serialized.
   *
   * This should be selfexplaining (just take a look at the length ...):
   * echo serialize($serMe['parent']);
   * echo serialize($serMe);
   * results in:
   *                   O:4:"node":5:{s:5:"value";s:6:"parent";s:5:"child";O:4:"node":5:{s:5:"value";s:5:"child";s:5:"child";N;s:6:"parent";O:4:"node":5:{s:5:"value";s:6:"parent";s:5:"child";R:3;s:6:"parent";N;s:4:"load";s:4:"....";s:11:"__destroyed";b:0;}s:4:"load";s:4:"....";s:11:"__destroyed";b:0;}s:6:"parent";N;s:4:"load";s:4:"....";s:11:"__destroyed";b:0;}
   * a:2:{s:6:"parent";O:4:"node":5:{s:5:"value";s:6:"parent";s:5:"child";O:4:"node":5:{s:5:"value";s:5:"child";s:5:"child";N;s:6:"parent";R:2;s:4:"load";s:4:"....";s:11:"__destroyed";b:0;}s:6:"parent";N;s:4:"load";s:4:"....";s:11:"__destroyed";b:0;}s:5:"child";R:4;}
   *
   * PLEASE GIVE THE WORLD PROPER DOCUMENTATION ON THIS (or prove us wrong)
   *
   * 2.Problem:
   *
   * The loop below will result in extensive leaking (use the big $loadsize ...),
   * unless you help yourself and unset all the properties manually
   * see Node::__destroy()
   *
   */
 
  $serMe['parent']->__destroy();
  $serMe['child']->__destroy();
  $serMe = unserialize($str);
  unset($str);
  $serMe['parent']->value = 'new parent';
  echo '-->'.$serMe['parent']->child->parent->value.chr(10);
  echo '-->'.$serMe['parent']->value.chr(10);
  
  $serMe['parent']->__destroy();
  $serMe['child']->__destroy();
  unset($serMe);
}

?>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Apr 26 06:01:32 2024 UTC