php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #39868 Destructor is called allthought constructor is never called
Submitted: 2006-12-18 14:31 UTC Modified: 2008-11-10 01:00 UTC
Votes:7
Avg. Score:3.9 ± 0.8
Reproduced:6 of 6 (100.0%)
Same Version:4 (66.7%)
Same OS:2 (33.3%)
From: jb at ez dot no Assigned: derick (profile)
Status: No Feedback Package: Scripting Engine problem
PHP Version: 5.2.0 OS: Kubuntu 6.10
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2006-12-18 14:31 UTC] jb at ez dot no
Description:
------------
It is possible for PHP to call the __destruct() on an 
object allthough __construct() was never called. This 
seems to happen when having nested object creations and 
the inner one throws and exception in the constructor.

The attached reproducable code shows the problem:
Child::__construct() [Child]
Master::__destruct() [Master]

If the last line is changed to:
$b = new Child(); $a = new Master( $b );

it works of course.


In general destructors should never be called if the 
constructor was not called or even if it did not finish.

Reproduce code:
---------------
class Master {
    public function __construct( $child ) {
        echo __CLASS__, "::__construct() [", get_class( $this ), "]\n";
    }
    public function __destruct() {
        echo __CLASS__, "::__destruct() [", get_class( $this ), "]\n";
    }
}
class Child {
    public function __construct() {
        echo __CLASS__, "::__construct() [", get_class( $this ), "]\n";
        throw new Exception( "Child failure" );
    }
    public function __destruct() {
        echo __CLASS__, "::__destruct() [", get_class( $this ), "]\n";
    }
}
$a = new Master( new Child() );


Expected result:
----------------
Child::__construct() [Child]


Actual result:
--------------
Child::__construct() [Child]
Master::__destruct() [Master]


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2006-12-18 14:46 UTC] tony2001@php.net
I don't see anything wrong here.
With "$b = new Child(); $a = new Master( $b );" the second line does not get executed, of course you won't see any of Master methods called.
But "$a = new Master( new Child() );" is totally different case - Master::__construct() is called, you don't see the output because the exception is thrown just before you print it.
 [2006-12-19 08:28 UTC] jb at ez dot no
You don't think it is a problem that the destructor (for 
Master) is called while the constructor (for Master) is 
never called?

In this example neither the constructor or the destructor 
should be called since an exception is thrown in the Child 
class, ie. the new Master() expression should never be 
processed.

The problem seems to be that PHP actually allocates the 
object for Master and then delays the call to construct 
until the parameters are prepared (which makes sense, 
except the allocation). Then when the exception is thrown 
PHP will cleanup all variables/objects, it then sees the 
allocated object and tries the destructor,

In a good object oriented language you would never call 
the destructor unless the constructor was called and was 
completed 100%.
 [2006-12-19 09:32 UTC] tony2001@php.net
>You don't think it is a problem that the destructor (for 
>Master) is called while the constructor (for Master) is 
>never called?

You didn't read my reply, did you? 
"Master::__construct() is called, you don't see the output because the exception is thrown just before you print it."
 [2006-12-19 09:59 UTC] jb at ez dot no
> You didn't read my reply, did you? 
> "Master::__construct() is called, you don't see the
> output because the
> exception is thrown just before you print it."

I did read it, and I just think you misunderstood my 
explanation.
If you see in Master::__construct() there is no exception 
throwing, hence this constructor cannot throw any 
exceptions. The throwing is done in the 
Child::__construct() (which does not inherit the Master 
class) and so it will not interfere with the constructor 
of the Master class.

I don't see why PHP would call the constructor then 
immediately stop it before it gets to the first line of 
the constructor code.

Did you try to run this through a debugger (GDB) to verify 
that the constructor is actually run?
 [2006-12-19 10:38 UTC] jb at ez dot no
I'm sorry but this is definitely not Bogus, changing back 
to Open.
 [2006-12-19 10:56 UTC] tony2001@php.net
Your arguments are based on wrong assumption that object is created in __construct(). This is wrong, because __construct() is supposed to initialize it, the object itself is created by "new" and already exists in __construct().
No bug here.
 [2006-12-19 11:52 UTC] jb at ez dot no
> Your arguments are based on wrong assumption that object
> is created in
> __construct(). This is wrong, because __construct() is
> supposed to
> initialize it, the object itself is created by "new" and
> already exists
> in __construct().
> No bug here.

I'm very aware that the constructor does not 
create/allocate the object and that the constructor is 
used for initialization. And I never claimed that it did 
so.
What I'm saying is that the constructor (ie. 
initialization) is never called (which is correct since 
there is an exception) and therefore the destructor (which 
is uninitialization) should not be called.

ie. this expression:
$a = new Master( new Child() );

would become this (using fake opcodes).

1. allocate instance of Master as $0
2. allocate instance of Child as $1
3. call $1->__construct()
4. call $0->__construct( $1 );

the end of the script would then do:
5. if not initialized $1 goto 7
6. call $1->__destruct()
7. if not initialized $0 goto 9
8. call $0->__destruct()
9. free instance $1
10. free instance $0

Since line #3 gives an exception it will never do #4, 
however the script ends and it performs the freeing of the 
objects but not the destructor of Master since it never 
called __construct.


An alternative would also be:
1. allocate instance of Child as $1
2. call $1->__construct()
3. allocate instance of Master as $0
4. call $0->__construct( $1 );

To avoid the Master object being allocated at all. 
Whichever way is chosen the __destruct() should not be 
called in any case.


If PHP is meant to work this way there is something 
seriously wrong with its design and should be fixed.


And also to demonstrate the problem with a real-life 
example, consider this PHP code:
class Master {
    public function __construct( $child ) {
        echo __CLASS__, "::__construct() [", get_class( 
$this ), "]\n";
        $this->child = $child;
    }
    public function __destruct() {
        echo __CLASS__, "::__destruct() [", get_class( 
$this ), "]\n";
        $this->child->callByMaster();
    }
}
class Child {
    public function __construct() {
        echo __CLASS__, "::__construct() [", get_class( 
$this ), "]\n";
        throw new Exception( "Child failure" );
    }
    public function __destruct() {
        echo __CLASS__, "::__destruct() [", get_class( 
$this ), "]\n";
    }
    public function callByMaster() {
        echo __CLASS__, "::__callByMaster() is called [", 
get_class( $this ), "]\n";
    }
}
$a = new Master( new Child() );


You will now get a fatal error in the destruct:
Child::__construct() [Child]
Master::__destruct() [Master]

Notice: Undefined property:  Master::$child in 
construct2.php on line 9

Call Stack:
    0.0003   1. {main}() construct2.php:0
    0.0005   2. Master->__destruct() construct2.php:0

Fatal error: Call to a member function callByMaster() on a 
non-object in construct2.php on line 9

Call Stack:
    0.0003   1. {main}() construct2.php:0
    0.0005   2. Master->__destruct() construct2.php:0



Now the only way to protect against this problem is to use 
a boolean to set if __construct() was called. However that 
means that programmer must take care of issues which PHP 
itself should solve for him.
 [2008-11-02 12:39 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".
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Apr 24 22:01:30 2024 UTC