From: Dmitry Stogov Date: Thu, 7 Jul 2005 16:07:09 +0000 (+0000) Subject: Fixed bug #33512 (Add missing support for isset()/unset() overloading to complement... X-Git-Tag: php-5.1.0b3~119 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=345e0255b5af2d81215c33e107f12df2b3724a92;p=php Fixed bug #33512 (Add missing support for isset()/unset() overloading to complement the property get/set methods) --- diff --git a/NEWS b/NEWS index 20ff8f09ac..ebc9816954 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,8 @@ PHP NEWS - Fixed bug #33523 (Memory leak in xmlrpc_encode_request()). (Ilia) - Fixed bug #33520 (crash if safe_mode is on and session.save_path is changed). (Dmitry) +- Fixed bug #33512 (Add missing support for isset()/unset() overloading to + complement the property get/set methods). (Dmitry) - Fixed bug #33491 (crash after extending MySQLi internal class). (Tony) - Fixed bug #33475 (cURL handle is not closed on curl_close(). (Ilia) - Fixed bug #33469 (Compile error undefined reference to ifx_checkAPI). (Jani) diff --git a/Zend/zend.h b/Zend/zend.h index eb06003029..13b14ea9c1 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -270,6 +270,8 @@ typedef struct _zend_object { HashTable *properties; unsigned int in_get:1; unsigned int in_set:1; + unsigned int in_unset:1; + unsigned int in_isset:1; } zend_object; typedef unsigned int zend_object_handle; @@ -338,6 +340,8 @@ struct _zend_class_entry { union _zend_function *clone; union _zend_function *__get; union _zend_function *__set; + union _zend_function *__unset; + union _zend_function *__isset; union _zend_function *__call; union _zend_function *serialize_func; union _zend_function *unserialize_func; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index eef2254172..b4338c7f74 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1408,6 +1408,10 @@ ZEND_API void zend_check_magic_method_implementation(zend_class_entry *ce, zend_ zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_GET_FUNC_NAME); } else if (name_len == sizeof(ZEND_SET_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)) && fptr->common.num_args != 2) { zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ce->name, ZEND_SET_FUNC_NAME); + } else if (name_len == sizeof(ZEND_UNSET_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME)) && fptr->common.num_args != 1) { + zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_UNSET_FUNC_NAME); + } else if (name_len == sizeof(ZEND_ISSET_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME)) && fptr->common.num_args != 1) { + zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_ISSET_FUNC_NAME); } else if (name_len == sizeof(ZEND_CALL_FUNC_NAME) - 1 && !memcmp(lcname, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)) && fptr->common.num_args != 2) { zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ce->name, ZEND_CALL_FUNC_NAME); } @@ -1422,7 +1426,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr int count=0, unload=0; HashTable *target_function_table = function_table; int error_type; - zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__call = NULL; + zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL; char *lowercase_name; int fname_len; char *lc_class_name; @@ -1529,6 +1533,10 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr __get = reg_function; } else if ((fname_len == sizeof(ZEND_SET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME))) { __set = reg_function; + } else if ((fname_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME))) { + __unset = reg_function; + } else if ((fname_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME))) { + __isset = reg_function; } else { reg_function = NULL; } @@ -1560,6 +1568,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr scope->__call = __call; scope->__get = __get; scope->__set = __set; + scope->__unset = __unset; + scope->__isset = __isset; if (ctor) { ctor->common.fn_flags |= ZEND_ACC_CTOR; if (ctor->common.fn_flags & ZEND_ACC_STATIC) { @@ -1599,6 +1609,18 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr } __set->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC; } + if (__unset) { + if (__unset->common.fn_flags & ZEND_ACC_STATIC) { + zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __unset->common.function_name); + } + __unset->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC; + } + if (__isset) { + if (__isset->common.fn_flags & ZEND_ACC_STATIC) { + zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __isset->common.function_name); + } + __isset->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC; + } efree(lc_class_name); } return SUCCESS; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 33f8eeb870..d8833a237c 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -118,7 +118,7 @@ typedef struct _zend_function_entry { #define INIT_CLASS_ENTRY(class_container, class_name, functions) INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL) -#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \ +#define INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, handle_propunset, handle_propisset) \ { \ class_container.name = strdup(class_name); \ class_container.name_length = sizeof(class_name) - 1; \ @@ -131,6 +131,8 @@ typedef struct _zend_function_entry { class_container.__call = handle_fcall; \ class_container.__get = handle_propget; \ class_container.__set = handle_propset; \ + class_container.__unset = handle_propunset; \ + class_container.__isset = handle_propisset; \ class_container.serialize = NULL; \ class_container.unserialize = NULL; \ class_container.parent = NULL; \ @@ -141,6 +143,9 @@ typedef struct _zend_function_entry { class_container.module = NULL; \ } +#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \ + INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, NULL, NULL) + int zend_next_free_module(void); BEGIN_EXTERN_C() diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 94442bee98..f50d9d01a6 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1119,6 +1119,10 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n CG(active_class_entry)->__get = (zend_function *) CG(active_op_array); } else if ((name_len == sizeof(ZEND_SET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)))) { CG(active_class_entry)->__set = (zend_function *) CG(active_op_array); + } else if ((name_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME)))) { + CG(active_class_entry)->__unset = (zend_function *) CG(active_op_array); + } else if ((name_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME)))) { + CG(active_class_entry)->__isset = (zend_function *) CG(active_op_array); } else if (!(fn_flags & ZEND_ACC_STATIC)) { CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC; } @@ -1762,6 +1766,12 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) if (!ce->__set) { ce->__set = ce->parent->__set; } + if (!ce->__unset) { + ce->__unset = ce->parent->__unset; + } + if (!ce->__isset) { + ce->__isset = ce->parent->__isset; + } if (!ce->__call) { ce->__call = ce->parent->__call; } @@ -3932,6 +3942,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify ce->clone = NULL; ce->__get = NULL; ce->__set = NULL; + ce->__unset = NULL; + ce->__isset = NULL; ce->__call = NULL; ce->create_object = NULL; ce->get_iterator = NULL; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 97352fdb28..4ac1740374 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -680,6 +680,8 @@ END_EXTERN_C() #define ZEND_DESTRUCTOR_FUNC_NAME "__destruct" #define ZEND_GET_FUNC_NAME "__get" #define ZEND_SET_FUNC_NAME "__set" +#define ZEND_UNSET_FUNC_NAME "__unset" +#define ZEND_ISSET_FUNC_NAME "__isset" #define ZEND_CALL_FUNC_NAME "__call" #define ZEND_AUTOLOAD_FUNC_NAME "__autoload" diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index f8e01920e0..c0dbe82a0d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -109,6 +109,40 @@ static int zend_std_call_setter(zval *object, zval *member, zval *value TSRMLS_D } } +static void zend_std_call_unsetter(zval *object, zval *member TSRMLS_DC) +{ + zend_class_entry *ce = Z_OBJCE_P(object); + + /* __unset handler is called with one argument: + property name + */ + + SEPARATE_ARG_IF_REF(member); + + zend_call_method_with_1_params(&object, ce, &ce->__unset, ZEND_UNSET_FUNC_NAME, NULL, member); + + zval_ptr_dtor(&member); +} + +static zval *zend_std_call_issetter(zval *object, zval *member TSRMLS_DC) +{ + zval *retval = NULL; + zend_class_entry *ce = Z_OBJCE_P(object); + + /* __isset handler is called with one argument: + property name + + it should return whether the property is set or not + */ + + SEPARATE_ARG_IF_REF(member); + + zend_call_method_with_1_params(&object, ce, &ce->__isset, ZEND_ISSET_FUNC_NAME, &retval, member); + + zval_ptr_dtor(&member); + + return retval; +} static int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC) { @@ -420,18 +454,25 @@ static int zend_std_has_dimension(zval *object, zval *offset, int check_empty TS if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { SEPARATE_ARG_IF_REF(offset); zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset); - zval_ptr_dtor(&offset); if (retval) { result = i_zend_is_true(retval); zval_ptr_dtor(&retval); - return result; + if (check_empty && result && !EG(exception)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); + if (retval) { + result = i_zend_is_true(retval); + zval_ptr_dtor(&retval); + } + } } else { - return 0; + result = 0; } + zval_ptr_dtor(&offset); } else { zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); return 0; } + return result; } @@ -482,23 +523,35 @@ static zval **zend_std_get_property_ptr_ptr(zval *object, zval *member TSRMLS_DC static void zend_std_unset_property(zval *object, zval *member TSRMLS_DC) { zend_object *zobj; - zval tmp_member; + zval *tmp_member = NULL; zend_property_info *property_info; + zend_bool use_unset; zobj = Z_OBJ_P(object); + use_unset = (zobj->ce->__unset && !zobj->in_unset); if (member->type != IS_STRING) { - tmp_member = *member; - zval_copy_ctor(&tmp_member); - convert_to_string(&tmp_member); - member = &tmp_member; + ALLOC_ZVAL(tmp_member); + *tmp_member = *member; + INIT_PZVAL(tmp_member); + zval_copy_ctor(tmp_member); + convert_to_string(tmp_member); + member = tmp_member; } property_info = zend_get_property_info(zobj->ce, member, 0 TSRMLS_CC); - zend_hash_del(zobj->properties, property_info->name, property_info->name_length+1); - if (member == &tmp_member) { - zval_dtor(member); + if (!property_info || zend_hash_del(zobj->properties, property_info->name, property_info->name_length+1) == FAILURE) { + if (use_unset) { + /* have unseter - try with it! */ + zobj->in_unset = 1; /* prevent circular unsetting */ + zend_std_call_unsetter(object, member TSRMLS_CC); + zobj->in_unset = 0; + } + } + + if (tmp_member) { + zval_ptr_dtor(&tmp_member); } } @@ -837,27 +890,51 @@ static int zend_std_has_property(zval *object, zval *member, int has_set_exists zend_object *zobj; int result; zval **value; - zval tmp_member; + zval *tmp_member = NULL; zend_property_info *property_info; + zend_bool use_isset; zobj = Z_OBJ_P(object); + use_isset = (zobj->ce->__isset && !zobj->in_isset); if (member->type != IS_STRING) { - tmp_member = *member; - zval_copy_ctor(&tmp_member); - convert_to_string(&tmp_member); - member = &tmp_member; + ALLOC_ZVAL(tmp_member); + *tmp_member = *member; + INIT_PZVAL(tmp_member); + zval_copy_ctor(tmp_member); + convert_to_string(tmp_member); + member = tmp_member; } #if DEBUG_OBJECT_HANDLERS fprintf(stderr, "Read object #%d property: %s\n", Z_OBJ_HANDLE_P(object), Z_STRVAL_P(member)); #endif - if (!(property_info = zend_get_property_info(zobj->ce, member, 1 TSRMLS_CC))) { - return 0; - } + property_info = zend_get_property_info(zobj->ce, member, 1 TSRMLS_CC); + + if (!property_info || zend_hash_quick_find(zobj->properties, property_info->name, property_info->name_length+1, property_info->h, (void **) &value) == FAILURE) { + result = 0; + if (use_isset && (has_set_exists != 2)) { + zval *rv; - if (zend_hash_find(zobj->properties, property_info->name, property_info->name_length+1, (void **) &value) == SUCCESS) { + /* have issetter - try with it! */ + zobj->in_isset = 1; /* prevent circular getting */ + rv = zend_std_call_issetter(object, member TSRMLS_CC); + zobj->in_isset = 0; + if (rv) { + result = zend_is_true(rv); + zval_ptr_dtor(&rv); + if (has_set_exists && result && !EG(exception) && zobj->ce->__get && !zobj->in_get) { + rv = zend_std_call_getter(object, member TSRMLS_CC); + if (rv) { + rv->refcount++; + result = i_zend_is_true(rv); + zval_ptr_dtor(&rv); + } + } + } + } + } else { switch (has_set_exists) { case 0: result = (Z_TYPE_PP(value) != IS_NULL); @@ -869,12 +946,10 @@ static int zend_std_has_property(zval *object, zval *member, int has_set_exists result = 1; break; } - } else { - result = 0; } - if (member == &tmp_member) { - zval_dtor(member); + if (tmp_member) { + zval_ptr_dtor(&tmp_member); } return result; } diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 41ee18f9c0..e9b68b13af 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -103,6 +103,8 @@ ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_ent retval.handlers = &std_object_handlers; (*object)->in_get = 0; (*object)->in_set = 0; + (*object)->in_unset = 0; + (*object)->in_isset = 0; return retval; } diff --git a/Zend/zend_reflection_api.c b/Zend/zend_reflection_api.c index 970ab38d98..19d32fa351 100644 --- a/Zend/zend_reflection_api.c +++ b/Zend/zend_reflection_api.c @@ -205,6 +205,8 @@ static void reflection_objects_clone(void *object, void **object_clone TSRMLS_DC (*intern_clone)->zo.ce = intern->zo.ce; (*intern_clone)->zo.in_get = 0; (*intern_clone)->zo.in_set = 0; + (*intern_clone)->zo.in_unset = 0; + (*intern_clone)->zo.in_isset = 0; ALLOC_HASHTABLE((*intern_clone)->zo.properties); (*intern_clone)->ptr = intern->ptr; (*intern_clone)->free_ptr = intern->free_ptr; @@ -224,6 +226,8 @@ static zend_object_value reflection_objects_new(zend_class_entry *class_type TSR intern->zo.ce = class_type; intern->zo.in_get = 0; intern->zo.in_set = 0; + intern->zo.in_unset = 0; + intern->zo.in_isset = 0; intern->ptr = NULL; intern->obj = NULL; intern->free_ptr = 0; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 970ab38d98..19d32fa351 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -205,6 +205,8 @@ static void reflection_objects_clone(void *object, void **object_clone TSRMLS_DC (*intern_clone)->zo.ce = intern->zo.ce; (*intern_clone)->zo.in_get = 0; (*intern_clone)->zo.in_set = 0; + (*intern_clone)->zo.in_unset = 0; + (*intern_clone)->zo.in_isset = 0; ALLOC_HASHTABLE((*intern_clone)->zo.properties); (*intern_clone)->ptr = intern->ptr; (*intern_clone)->free_ptr = intern->free_ptr; @@ -224,6 +226,8 @@ static zend_object_value reflection_objects_new(zend_class_entry *class_type TSR intern->zo.ce = class_type; intern->zo.in_get = 0; intern->zo.in_set = 0; + intern->zo.in_unset = 0; + intern->zo.in_isset = 0; intern->ptr = NULL; intern->obj = NULL; intern->free_ptr = 0;