php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #38018 __destruct called if __wakeup threw an exception
Submitted: 2006-07-05 20:31 UTC Modified: 2006-07-06 20:36 UTC
From: php dot bugs at login dot hedim dot ch Assigned:
Status: Not a bug Package: Class/Object related
PHP Version: 5.1.4 OS: irrelevant
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 you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: php dot bugs at login dot hedim dot ch
New email:
PHP Version: OS:

 

 [2006-07-05 20:31 UTC] php dot bugs at login dot hedim dot ch
Description:
------------
If __wakeup threw an exception when unserializing an object, __destruct is called on that object. For the same reasons as __destruct is not called if __construct threw an exception (see bug #29368), it should not be called here either.

Reproduce code:
---------------
class Foo
{
    function __wakeup()
    {
        throw new Exception;
    }

    function __destruct()
    {
        echo "hello from destructor\n";
    }
}

unserialize(serialize(new Foo));


Expected result:
----------------
hello from destructor (from the destructor of the object to be serialized)

Actual result:
--------------
hello from destructor (from the destructor of the object to be serialized)
hello from destructor (from the destructor of the object that fails to unserialize)

Patches

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2006-07-05 21:36 UTC] tony2001@php.net
Change your code to this:

$var = new Foo;
$str = serialize($var);
unserialize($str);

and voila - no second destructor call.
The first destructor is called for the temporary var.
 [2006-07-06 00:40 UTC] php dot bugs at login dot hedim dot ch
Thank you for your response.

While I admit that the code example I gave was confusing, I believe it is not bogus. It is true that there is only one call to __destruct with the changed code, but it's the wrong one. The correct call to __destruct has disappeared for a different reason.

This is what happens with the changed code: Because the Foo instance #1 is assigned to $var, it would only be destructed when $var goes out of scope or (as in this case) when the script terminates (normally).

When unserializing $str, __wakeup is called on the unserialized Foo instance #2 and throws an exception. Then, __destruct is called on Foo instance #2. This is imho wrong because Foo instance #2 doesn't really exist. The exception indicates it could not be reconstructed, just like an exception from __construct does. If Foo instance #2 doesn't really exists, there is nothing to destruct and __destruct should not be called.

The correct call to __destruct on Foo instance #1 doesn't happen anymore because the above exception terminates the script with a fatal error. Wrapping a try { } around the unserialize would bring the call to Foo #1's __destruct back.

To prove this, I have extended the reproduce code (see below) to also print out which object is destructed.

The point is this: If __destruct is not called for objects that throw an exception from __construct, then it should not be called either for those that throw an exception from __wakeup. In both cases, the object "could not be (re-) constructed", therefore there is nothing to destruct.

I hope this makes things clearer and I apologize for the confusing description and code example before.

Thank you for looking into this matter again.

Extended reproduce code:
------------------------
class Foo
{
    function __wakeup()
    {
        throw new Exception;
    }

    function __destruct()
    {
        echo "hello from $this's destructor\n";
    }
}

$var = new Foo;
echo "$var\n";
$str = serialize($var);
unserialize($str);

Expected result:
----------------
Object id #1
Fatal error: Uncaught exception 'Exception' [...]
No output from any destructor. The exception terminates the script before any object can be destructed.

Actual result:
--------------
Object id #1
hello from Object id #2's destructor
Fatal error: Uncaught exception 'Exception' [...]
 [2006-07-06 20:36 UTC] tony2001@php.net
Ok, let me explain how it works:
1) we got a serialized object.
2) unserialize() creates an object #2.
3) unserialize() looks for __wakeup() function and calls it.
4) exception is thrown.
5) unserialize() fails and destroys it's result value (to prevent memory leak) - here is where __destruct() for object #2 is called.
6) engine detects an unhandled exception and exit()'s.
All of this is expected and I don't see any bugs here.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Sep 07 14:01:28 2024 UTC