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
 [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

Add a Patch

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: Thu Apr 18 23:01:27 2024 UTC