php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #36424 Keeping reference info through recursive calls to serialize()
Submitted: 2006-02-17 06:27 UTC Modified: 2011-09-30 16:05 UTC
Votes:19
Avg. Score:4.7 ± 0.7
Reproduced:17 of 17 (100.0%)
Same Version:4 (23.5%)
Same OS:6 (35.3%)
From: mastabog at hotmail dot com Assigned: mike
Status: Closed Package: Unknown/Other Function
PHP Version: 5.3.8 OS: *
Private report: No CVE-ID:
 [2006-02-17 06:27 UTC] mastabog at hotmail dot com
Description:
------------
First of all, I know this is very new and undocumented.

The Serializable interface serialize() method breaks reference of objects that are properties of the serialized object and that they themselves implement the Serializable interface. See the reproduceable code below.

an echo over $ser yields:

C:1:"C":85:{a:2:{s:1:"A";C:1:"A":6:{a:0:{}}s:1:"B";C:1:"B":32:{a:1:{s:1:"A";C:1:"A":6:{a:0:{}}}}}}

It's visible that the last A is not a reference but a new class instance.

I know that Serializable::unserialize() acts as a constructor, but shouldn't object references be honored by Serializable::serialize() the same way unserialize() does when the class does not implement the Serializable interface.

If we remove the Serializable interface from class A and leave it like this:

class A {}

then $ser looks like the following:

O:1:"C":2:{s:1:"A";O:1:"A":0:{}s:1:"B";O:1:"B":1:{s:1:"A";r:2;}}

And it's visible that the last A is a reference.

If this is all intended behavior for the Serializable interface to break object references then you can ignore this bug report. I hope it's not though, because it would have provided a better alternative to the __sleep() and __wakeup() (e.g. classes extending the PDO class cannot be serialized using __sleep() and __wakeup(), neither by overloading nor by default)

Reproduce code:
---------------
class A implements Serializable
{
	public function serialize ()
	{
		$serialized = array();
		foreach($this as $prop => $val) {
			$serialized[$prop] = $val;
		}
		return serialize($serialized);
		
		//return serialize(get_object_vars($this));
	}

	function unserialize($serialized)
	{
		foreach(unserialize($serialized) as $prop => $val) {
			$this->$prop = $val;
		}
		return true;
	}
}

class B extends A
{
	public $A;
}

class C extends A
{
	public $A;
	public $B;
}

$oC = new C();
$oC->A = new A();
$oC->B = new B();
$oC->B->A = $oC->A;

echo $oC->A === $oC->B->A ? "yes" : "no", "\n"; 
$ser = serialize($oC);
$new_oC = unserialize($ser);
echo $new_oC->A === $new_oC->B->A ? "yes" : "no", "\n"; 

Expected result:
----------------
yes
yes


Actual result:
--------------
yes
no


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2006-03-18 10:29 UTC] nohn@php.net
When playing around with this bug, I discovered this:

While

    var_dump($new_oC->A === $new_oC->B->A);

Results into bool(false)

    $this->assertEquals($new_oC->A, $new_oC->B->A);

Does not fail!
 [2006-03-18 10:30 UTC] nohn@php.net
To make this fail, it needs to be

    $this->assertEquals(true, $new_oC->A === $new_oC->B->A);

 [2006-04-11 11:43 UTC] sniper@php.net
Assigned to the SPL maintainer.
 [2006-06-06 18:51 UTC] hos dot endre at cafecsoport dot hu
Crashing also reproducable.

Win2000Prof / PHP514

My related problem is that serializing a Serializable object, thats some object property holds a reference to the serialized root object, PHP crashes.

The magic function __serialize have too much sideFx for me - making the object unworkable,
but I cant figure out how nested serialize function calls could work properly.

Probably when the native serialize function is handles a nested call then could hook up the root calls pointer map?

And then could this work for nested unserialization?

At this time I have no vision about the sideFx of this mention.

Right now I dont have a prepared C development environment to work out, and dont remember how the related part of the code workx. As I remember nested call test was implemented in the __autoload() function.

Since it can cause crashes please take more care on this bug.

Just uncomment 'implements Serializable' to let it crash.

class A // implements Serializable
{
	public $dontSerialize;
	public $that;

	function __construct($that = null)
	{
		$this->that = ($that === null) ? new A($this) : $that;
	}

	function serialize()
	{
		return serialize($this->that);
	}

	function unserialize($serialized)
	{
		$this->that = serialize($serialized);
	}
}
 [2006-06-06 19:02 UTC] tony2001@php.net
Yeah, calling a function in an endless loop usually ends up with stack overflow. It's expected and has nothing to do with this report.
 [2006-06-06 21:04 UTC] hos dot endre at cafecsoport dot hu
Yes its expected, but IMHO the problem need a featureful fix, not a limitation.

Im not sure my problem is SPL related.

Here is my workaround:

The native serialize must start counting variables from 1. Outer references may indexed negative, in case of nested call. Nested native calls can only called in a native calls context in case of handling a __serialize() or Serializable::serialize() call. The nested call can reference as -1 to the topmost calls serialized root object. In case of a negative reference is unresolvable on unserialization (multiplying by -1 and querying the topmost pointer remapping), the value is not ready, and a null value replaces temporarily.

What about a nested call already serialized a variable
that the topmost is willing to serialize? we have that pointer in the map, but the reference no. might be greater
than the number of locally serialized variables.

If every call starts numbering variables from 1, then the topmost serialize call also could reference on those hidden
variables by a negative index from the nesting wide accessible map - not the calls local map. Backwards
if the unserialization process makes it possible, the negative reference is also resolvable at the time the function processes it.

In case of wrong unserialization order, the negative reference's zval is created with null, and the time the unserialization reaches that index, da value's pointer is ready to be casted properly.

I think above mentioned way doesnt violates the syntax or the result of serialization, this could work for a lot of cases, just like mine.

This way, the following code could produce this:

C:1:"A":93:{a:2:{s:4:"that";C:1:"A":43:{a:2:{s:4:"that";R:-1;s:9:"thatsThat";R:-2;}}s:9:"thatsThat";R:-1;}

class A implements Serializable
{
	public $dontSerialize;
	public $that;
	public $thatsThat;

	function __construct($that = null)
	{
		$this->that = ($that === null) ? new A($this) : $that;
		$this->thatsThat = &$this->that->that;
	}

	function serialize()
	{
		return serialize(array($this->that, $this->thatsThat));
	}

	function unserialize($serialized)
	{
		list($this->that, $this->thatsThat) = unserialize($serialized);
	}
}
echo serialize(new A());

Some more without any class relation, the following script serialises 3 arrays instead of 2 since we are passing a copy of $arr1, but the _deprecated_ syntax serialize(&$arr1) still works fine.

Documentation says: "recent versions of PHP you will get a warning saying that "Call-time pass-by-reference" is deprecated when you use a & in foo(&$a);"

References' syntax are not consequent.

$arr2 = array();
$arr1['that'] = &$arr2;
$arr2['that'] = &$arr1;
$arr1['thatsThat'] = &$arr2['that'];
$arr2['thatsThat'] = &$arr1['that'];
echo serialize($arr1);

Produces:

a:2:{s:4:"that";a:2:{s:4:"that";a:2:{s:4:"that";R:2;s:9:"thatsThat";R:3;}s:9:"thatsThat";R:2;}s:9:"thatsThat";R:3;}

instead of

a:2:{s:4:"that";a:2:{s:4:"that";R:1;s:9:"thatsThat";R:2;}s:9:"thatsThat";R:1;}
 [2006-06-06 21:18 UTC] helly@php.net
The problem has nothing to do with SPL.
 [2006-06-06 21:21 UTC] hos dot endre at cafecsoport dot hu
"It's expected and has nothing to do with this report."

I disagree with it, and Im sure Helly will make it work as expected which means without crash at least.
 [2006-06-06 21:27 UTC] helly@php.net
It's still assigned to me.
 [2006-07-22 22:11 UTC] mastabog at hotmail dot com
I'm not sure if we drifted away from the original bug report I made in the first post. Has there been any progress on the original bug or plans to fix it anytime soon?

I see the bug is still open so i take it the maintainer regards it as a bug that needs fixing.

The latest snapshot I tried of php 5.2 from July 21 2006 still experiences the same problem.
 [2008-11-02 12:31 UTC] jani@php.net
Please try using this CVS snapshot:

  http://snaps.php.net/php5.2-latest.tar.gz
 
For Windows:

  http://windows.php.net/snapshots/


 [2008-11-10 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 [2010-02-27 02:30 UTC] mastabog at hotmail dot com
I know it's been over 1 year since you asked me to try the latest CVS snapshot but ...

... the bug is still there in 5.2.12 (Windows)! Yuu can try the reproduce code from the first post.

Can this be fixed please?
 [2010-02-28 15:36 UTC] jani@php.net
Please try using this snapshot:

  http://snaps.php.net/php5.2-latest.tar.gz
 
For Windows:

  http://windows.php.net/snapshots/


 [2010-03-02 00:31 UTC] mastabog at hotmail dot com
Hi,

I tested with the latest Linux source snapshot (php5.2-201003011530) and the bug is still there. I also tested the recently released 5.2.13 release and got the same result. Both Linux and Windows.

Did you run the reproduce code from the first post above and got the expected result? The bug is definitely there in the snapshot I tested.

I've been avoiding the Serializable interface ever since it got introduced due to this nasty bug. Could it please be fixed? It's growing old and strong :)

Cheers
 [2010-05-20 16:09 UTC] mike@php.net
-Status: Open +Status: Bogus
 [2010-05-20 16:09 UTC] mike@php.net
The reproduce code is wrong.

Your code serializes everything on it's own, as you can see if you replace

        return $serialized;

with
        $serialized = serialize($serialized);
        printf("serialized %s as '%s'\n", get_class($this), $serialized);
        return $serialized;


Output:

yes
serialized A as 'a:0:{}'
serialized A as 'a:0:{}'
serialized B as 'a:1:{s:1:"A";C:1:"A":6:{a:0:{}}}'
serialized C as 'a:2:{s:1:"A";C:1:"A":6:{a:0:{}}s:1:"B";C:1:"B":32:{a:1:{s:1:"A";C:1:"A":6:{a:0:{}}}}}'
no
 [2010-05-20 18:31 UTC] mastabog at hotmail dot com
If you remove "implements Serializable" then it works as expected and the reproduce code shows that. Revealing the bug was what my report was about.

It's a bug. It surprises me that nobody wants to fix it after more than 4 years ... apparently it's just me that considers this bug to be important which is why I still avoid using the Serializable interface altogether. It's unreliable.
 [2010-05-20 23:21 UTC] mastabog at hotmail dot com
Why has the status of this been changed to "bogus"?

Are we to understand that the devs regard this as being intended behaviour, i.e. the Serializable interface breaks object references of objects implementing it?

The initial reproduce code shows that what was an object reference before is no longer a reference after unserialization ... this is a bug. There may be other (better) reproduce codes but the bug is still there. Why not try and fix it?

Well, I tried but I think I will give up ... it's been 4+ years!
 [2010-05-21 11:19 UTC] mike@php.net
-Summary: Serializable interface breaks object references +Summary: Keeping reference info through recursive calls to serialize() -Status: Bogus +Status: Assigned -Type: Bug +Type: Feature/Change Request -Package: Scripting Engine problem +Package: Unknown/Other Function -Assigned To: helly +Assigned To: mike
 [2010-05-21 11:19 UTC] mike@php.net
Reclassified as Change Request.

JFYI: http://news.php.net/php.internals/48369
 [2010-05-21 13:08 UTC] mastabog at hotmail dot com
I still don't understand why this is not seen as a bug. My example shows that without "implements Serializable" object references are honoured (as expected) while with "implements Serializable" object references are broken (which is unexpected).

Seeing you classified this as "feature request" makes me think that breaking object references was actually intended behaviour or that there is a way to maintain object references when implementing Serializable. Can you then please provide the body of the serialize() and unserialize() methods in class A in the example below that will maintain object references as expected, i.e. $new_oC->A === $new_oC->B->A?

class A implements Serializable
{
	public function serialize ()
	{
		[...]
	}

	function unserialize($serialized)
	{
		[...]
	}
}

class B extends A
{
	public $A;
}

class C extends A
{
	public $A;
	public $B;
}

$oC = new C();
$oC->A = new A();
$oC->B = new B();
$oC->B->A = $oC->A;

echo $oC->A === $oC->B->A ? "yes" : "no", "\n"; 
$ser = serialize($oC);
$new_oC = unserialize($ser);
echo $new_oC->A === $new_oC->B->A ? "yes" : "no", "\n";
 [2010-05-26 09:24 UTC] mike@php.net
Automatic comment from SVN on behalf of mike
Revision: http://svn.php.net/viewvc/?view=revision&revision=299770
Log: Added support for object references in recursive serialize() calls. FR #36424
 [2010-12-10 12:40 UTC] peter at desk dot nl
I've just been bitten by this bug (in php 5.3.3), cost me half a day to figure it out... it's highly annoying because I can't go back to using __sleep and __wakeup without much rewriting, and while working around it is feasible, it's pretty hacky in my situation.

what has happened to the patch mentioned in may ? was it ever committed ?
 [2011-09-30 06:49 UTC] mike@php.net
-Status: Assigned +Status: Closed
 [2011-09-30 06:49 UTC] mike@php.net
The patch is contained in upcoming PHP-5.4
 [2011-09-30 15:01 UTC] mastabog at hotmail dot com
-PHP Version: 5.2.14-dev +PHP Version: 5.3.8
 [2011-09-30 15:01 UTC] mastabog at hotmail dot com
Wow! More than 5 and a half years since I discovered, reproduced and submitted this bug ...

To mike:

Why wait until 5.4? There are many web hosting providers that will delay the update to 5.4 for years but will instantly update to the next 5.3.x release. Most providers only just updated from 5.2 to 5.3 (i.e. this or last month) even though 5.3 has been out since 2009.

Couldn't you apply the patch to the next 5.3.x release instead?
 [2011-09-30 16:05 UTC] johannes@php.net
mastabog, no this can't go in 5.3 it changes internal structures and APIs which we can't change in bug fix releases without breaking API compatibility which we guarantee.
 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Thu Apr 24 02:02:10 2014 UTC