php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #38315 Constructing in the destructor causes weird behaviour
Submitted: 2006-08-03 15:55 UTC Modified: 2006-08-24 10:10 UTC
From: sean@php.net Assigned: dmitry
Status: Closed Package: Scripting Engine problem
PHP Version: 5.1.4 OS: Linux (N/A)
Private report: No CVE-ID:
 [2006-08-03 15:55 UTC] sean@php.net
Description:
------------
Hi,

I already discussed this with Gopal, so he'll probably have something more enlightening to say about it.

When calling a class' constructor from one of its object's destructors, the engine behaves strangely.

I expected it to recurse forever and segfault like PHP normally does on infinite recursion.

Yes, I know I /shouldn't/ be doing this. I just want to make sure something bad isn't happening internally (and document this behaviour).

S

Reproduce code:
---------------
class Foo {
  function __construct() { echo "constructing\n"; }
  function __destruct() { $GLOBALS['foo'] = new Foo(); }
}
$foo = new Foo();

Expected result:
----------------
constructing
constructing
... (some large number of times)
constructing
constructing
[[Segmentation Fault]]


Actual result:
--------------
constructing
constructing
constructing
constructing


(yes, only 4 times. 4 is a strange number here, no?)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2006-08-03 18:03 UTC] gopalv@php.net
This happens because of the following code segment in function zend_objects_store_call_destructors:

for (i = 1; i < objects->top ; i++) {
   if (objects->object_buckets[i].valid) {
   ....
       obj->dtor(obj->object, i TSRMLS_CC);

The problem being that the destructor allocates a new object, which due to chance puts the new object in objects->object_buckets[2] 

This dtor call originated from the 

zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_CC);

for the element in the global scope.

Then after object#2 has been inserted into the objects store, by the dtor of #1

zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);

again starts to walk the objects->object_buckets to dtor the objects. It discovers object#2 and calls the dtor, which yet again constructs a new object, which yet again occupies bucket 1 of the objects_store (call it object#1a). This bucket is passed over already in the loop and the current call_destructors will never destroy this object. 

Then, zend_hash_graceful_reverse_destroy(&EG(symbol_table));
in zend_execute_API.c:235 again hits the symbol tables, to discover object#1a. Calls the dtor, which allocates object#3.

zend_objects_store_free_object_storage is called during shutdown_executor and which free(object#3) without calling its destructor. 

Now, there are two problems here. One, the object bucket loop does not handle self-modifying destructors. Maybe a linking the buckets, like the usual hashes maintain order in php, might help. Now, I don't know how erealloc works, but the self-modifying nature might have further problems if EG(objects_store).object_buckets is moved while the following "local" value (obj) isn't, in function zend_objects_store_del_ref():

 obj = &EG(objects_store).object_buckets[handle].bucket.obj;
.... 
  obj->dtor(obj->object, handle TSRMLS_CC);
....
  if (obj->refcount == 1) {
     if (obj->free_storage) {


Second, the free_storage code does not check if the bucket destructor was called.

And finally, why the hell do you call them buckets when they are just arrays indexed by integer offsets ? (confused me into thinking they were chained hashes for a while).

I hope I understood everything right. 
 [2006-08-03 18:47 UTC] tony2001@php.net
Dmitry, could you plz check it out?
 [2006-08-24 10:10 UTC] dmitry@php.net
The bug is fixed in CVS HEAD and PHP_5_2.
 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Wed Apr 23 17:01:58 2014 UTC