Edit report at http://bugs.php.net/bug.php?id=52774&edit=1
ID: 52774 Updated by: fel...@php.net Reported by: cataphr...@php.net Summary: Proxy object's store free callback calls zval_ptor_dtor on already freed data -Status: Open +Status: Assigned Type: Bug Package: Scripting Engine problem PHP Version: trunk-SVN-2010-09-04 (SVN) -Assigned To: +Assigned To: dmitry Block user comment: N Previous Comments: ------------------------------------------------------------------------ [2010-09-04 03:43:10] cataphr...@php.net Description: ------------ Proxy objects have this structure: typedef struct _zend_proxy_object { zval *object; zval *property; } zend_proxy_object; zend_object_create_proxy does this: ZEND_API zval *zend_object_create_proxy(zval *object, zval *member TSRMLS_DC) { zend_proxy_object *pobj = emalloc(sizeof(zend_proxy_object)); /* ... */ pobj->property = member; zval_add_ref(&pobj->property); /* ... */ } The property field is used to store a zval that, in the get and set handlers can the passed to zend_proxy_object.object's read_property and write_property callbacks, so that the reads and writes in the proxy object can be proxied. The store free callback for proxy objects calls zval_ptr_tor on &pobj->property: ZEND_API void zend_objects_proxy_free_storage(zend_proxy_object *object TSRMLS_DC) { zval_ptr_dtor(&object->object); zval_ptr_dtor(&object->property); efree(object); } However, on script cleanup, the destruction order is wrong. The zval stored in object->property is destroyed *before* the proxy object is. Test script: --------------- /* Extension */ typedef struct _proxy_test { zend_object std; long value; } proxy_test; static zend_class_entry *pt_ce_ptr; static zend_object_handlers p_obj_handlers; static zend_object_value p_ce_create_object(zend_class_entry *class_type TSRMLS_DC) { zend_object_value zov; proxy_test *pobj; pobj = emalloc(sizeof *pobj); zend_object_std_init((zend_object *) pobj, class_type TSRMLS_CC); pobj->value = 7; object_properties_init(&pobj->std, class_type); zov.handle = zend_objects_store_put(pobj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); zov.handlers = &p_obj_handlers; return zov; } zval *p_read_property(zval *object, zval *member, int type, const struct _zend_literal *key TSRMLS_DC) { proxy_test *iobj = zend_object_store_get_object(object TSRMLS_CC); if (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) { zval *ret = zend_object_create_proxy(object, member TSRMLS_CC); Z_DELREF_P(ret); return ret; } else { zval *ret; MAKE_STD_ZVAL(ret); ZVAL_LONG(ret, iobj->value); Z_DELREF_P(ret); return ret; } } void p_write_property(zval *object, zval *member, zval *value, const struct _zend_literal *key TSRMLS_DC) { proxy_test *iobj = zend_object_store_get_object(object TSRMLS_CC); if (Z_TYPE_P(value) == IS_LONG) { iobj->value = Z_LVAL_P(value); } } zval **p_get_property_ptr_ptr(zval *object, zval *member, const struct _zend_literal *key TSRMLS_DC) { return NULL; } ZEND_MODULE_STARTUP_D(testext) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "ProxyTestClass", NULL); pt_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); pt_ce_ptr->create_object = p_ce_create_object; memcpy(&p_obj_handlers, zend_get_std_object_handlers(), sizeof p_obj_handlers); /* could be NULL, but an empty impl is better (see bug #51768) */ p_obj_handlers.get_property_ptr_ptr = p_get_property_ptr_ptr; p_obj_handlers.read_property = p_read_property; p_obj_handlers.write_property = p_write_property; } /* Script */ <?php $n = new ProxyTestClass(); $h =& $n->whatever; Expected result: ---------------- The proxy object would be destroyed before the property zval encapsulated in it. Actual result: -------------- The property zval encapsulated in the proxy object is destroyed prematurely. Breakpoint on zend_object_create_proxy. Set data breakpoint on &member->refcount__gc Continue. Data breakpoint is hit (zval_add_ref(&pobj->property);). The refcount is now 3. Continue. Data breakpoint is hit. Call stack: > msvcr100d.dll!memset(unsigned char * dst=0x0000005a, unsigned char value='`', unsigned long count=11071032) Line 127 Asm php5ts_debug.dll!_zend_mm_free_int(_zend_mm_heap * heap=0x02426d50, void * p=0x028101a0, const char * __zend_filename=0x5d637b70, const unsigned int __zend_lineno=397, const char * __zend_orig_filename=0x00000000, const unsigned int __zend_orig_lineno=0) Line 2019 + 0x15 bytes C php5ts_debug.dll!_efree(void * ptr=0x028101a0, const char * __zend_filename=0x5d637b70, const unsigned int __zend_lineno=397, const char * __zend_orig_filename=0x00000000, const unsigned int __zend_orig_lineno=0) Line 2378 + 0x2b bytes C php5ts_debug.dll!destroy_op_array(_zend_op_array * op_array=0x0280f470, void * * * tsrm_ls=0x024115f8) Line 397 + 0x21 bytes C php5ts_debug.dll!zend_execute_scripts(int type=8, void * * * tsrm_ls=0x024115f8, _zval_struct * * retval=0x00000000, int file_count=3, ...) Line 1220 + 0x1e bytes C php5ts_debug.dll!php_execute_script(_zend_file_handle * primary_file=0x00a8f72c, void * * * tsrm_ls=0x024115f8) Line 2330 + 0x1b bytes C php.exe!main(int argc=2, char * * argv=0x024114c0) Line 1252 + 0x13 bytes C php.exe!__tmainCRTStartup() Line 555 + 0x19 bytes C php.exe!mainCRTStartup() Line 371 C efree() is being called with the refcount still being 3. Breakpoint on zend_objects_proxy_free_storage. The call zval_ptr_dtor(&object->property); will operate on already freed data: object 0x028102c0 {object=0x0280db38 property=0x028101d0 } object: 0x0280db38 {value={...} refcount__gc=2 type='' ...} property: 0x028101d0 {value={...} refcount__gc=1515870810 type='Z' ...} ------------------------------------------------------------------------ -- Edit this bug report at http://bugs.php.net/bug.php?id=52774&edit=1