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 (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: 5.1.4 OS: Linux (N/A)
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: sean@php.net
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Tue Dec 03 17:01:29 2024 UTC