Skip to content

Commit 345e025

Browse files
committed
Fixed bug #33512 (Add missing support for isset()/unset() overloading to complement the property get/set methods)
1 parent 307f622 commit 345e025

File tree

10 files changed

+158
-26
lines changed

10 files changed

+158
-26
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ PHP NEWS
2020
- Fixed bug #33523 (Memory leak in xmlrpc_encode_request()). (Ilia)
2121
- Fixed bug #33520 (crash if safe_mode is on and session.save_path is changed).
2222
(Dmitry)
23+
- Fixed bug #33512 (Add missing support for isset()/unset() overloading to
24+
complement the property get/set methods). (Dmitry)
2325
- Fixed bug #33491 (crash after extending MySQLi internal class). (Tony)
2426
- Fixed bug #33475 (cURL handle is not closed on curl_close(). (Ilia)
2527
- Fixed bug #33469 (Compile error undefined reference to ifx_checkAPI). (Jani)

Zend/zend.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ typedef struct _zend_object {
270270
HashTable *properties;
271271
unsigned int in_get:1;
272272
unsigned int in_set:1;
273+
unsigned int in_unset:1;
274+
unsigned int in_isset:1;
273275
} zend_object;
274276

275277
typedef unsigned int zend_object_handle;
@@ -338,6 +340,8 @@ struct _zend_class_entry {
338340
union _zend_function *clone;
339341
union _zend_function *__get;
340342
union _zend_function *__set;
343+
union _zend_function *__unset;
344+
union _zend_function *__isset;
341345
union _zend_function *__call;
342346
union _zend_function *serialize_func;
343347
union _zend_function *unserialize_func;

Zend/zend_API.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,10 @@ ZEND_API void zend_check_magic_method_implementation(zend_class_entry *ce, zend_
14081408
zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_GET_FUNC_NAME);
14091409
} 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) {
14101410
zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ce->name, ZEND_SET_FUNC_NAME);
1411+
} 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) {
1412+
zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_UNSET_FUNC_NAME);
1413+
} 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) {
1414+
zend_error(error_type, "Method %s::%s() must take exactly 1 argument", ce->name, ZEND_ISSET_FUNC_NAME);
14111415
} 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) {
14121416
zend_error(error_type, "Method %s::%s() must take exactly 2 arguments", ce->name, ZEND_CALL_FUNC_NAME);
14131417
}
@@ -1422,7 +1426,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr
14221426
int count=0, unload=0;
14231427
HashTable *target_function_table = function_table;
14241428
int error_type;
1425-
zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__call = NULL;
1429+
zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL;
14261430
char *lowercase_name;
14271431
int fname_len;
14281432
char *lc_class_name;
@@ -1529,6 +1533,10 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr
15291533
__get = reg_function;
15301534
} else if ((fname_len == sizeof(ZEND_SET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME))) {
15311535
__set = reg_function;
1536+
} else if ((fname_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME))) {
1537+
__unset = reg_function;
1538+
} else if ((fname_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME))) {
1539+
__isset = reg_function;
15321540
} else {
15331541
reg_function = NULL;
15341542
}
@@ -1560,6 +1568,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, zend_function_entr
15601568
scope->__call = __call;
15611569
scope->__get = __get;
15621570
scope->__set = __set;
1571+
scope->__unset = __unset;
1572+
scope->__isset = __isset;
15631573
if (ctor) {
15641574
ctor->common.fn_flags |= ZEND_ACC_CTOR;
15651575
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
15991609
}
16001610
__set->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
16011611
}
1612+
if (__unset) {
1613+
if (__unset->common.fn_flags & ZEND_ACC_STATIC) {
1614+
zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __unset->common.function_name);
1615+
}
1616+
__unset->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
1617+
}
1618+
if (__isset) {
1619+
if (__isset->common.fn_flags & ZEND_ACC_STATIC) {
1620+
zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __isset->common.function_name);
1621+
}
1622+
__isset->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
1623+
}
16021624
efree(lc_class_name);
16031625
}
16041626
return SUCCESS;

Zend/zend_API.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ typedef struct _zend_function_entry {
118118

119119
#define INIT_CLASS_ENTRY(class_container, class_name, functions) INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, NULL, NULL, NULL)
120120

121-
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \
121+
#define INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, handle_propunset, handle_propisset) \
122122
{ \
123123
class_container.name = strdup(class_name); \
124124
class_container.name_length = sizeof(class_name) - 1; \
@@ -131,6 +131,8 @@ typedef struct _zend_function_entry {
131131
class_container.__call = handle_fcall; \
132132
class_container.__get = handle_propget; \
133133
class_container.__set = handle_propset; \
134+
class_container.__unset = handle_propunset; \
135+
class_container.__isset = handle_propisset; \
134136
class_container.serialize = NULL; \
135137
class_container.unserialize = NULL; \
136138
class_container.parent = NULL; \
@@ -141,6 +143,9 @@ typedef struct _zend_function_entry {
141143
class_container.module = NULL; \
142144
}
143145

146+
#define INIT_OVERLOADED_CLASS_ENTRY(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset) \
147+
INIT_OVERLOADED_CLASS_ENTRY_EX(class_container, class_name, functions, handle_fcall, handle_propget, handle_propset, NULL, NULL)
148+
144149
int zend_next_free_module(void);
145150

146151
BEGIN_EXTERN_C()

Zend/zend_compile.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,10 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
11191119
CG(active_class_entry)->__get = (zend_function *) CG(active_op_array);
11201120
} else if ((name_len == sizeof(ZEND_SET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME)))) {
11211121
CG(active_class_entry)->__set = (zend_function *) CG(active_op_array);
1122+
} else if ((name_len == sizeof(ZEND_UNSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_UNSET_FUNC_NAME, sizeof(ZEND_UNSET_FUNC_NAME)))) {
1123+
CG(active_class_entry)->__unset = (zend_function *) CG(active_op_array);
1124+
} else if ((name_len == sizeof(ZEND_ISSET_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_ISSET_FUNC_NAME, sizeof(ZEND_ISSET_FUNC_NAME)))) {
1125+
CG(active_class_entry)->__isset = (zend_function *) CG(active_op_array);
11221126
} else if (!(fn_flags & ZEND_ACC_STATIC)) {
11231127
CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC;
11241128
}
@@ -1762,6 +1766,12 @@ static void do_inherit_parent_constructor(zend_class_entry *ce)
17621766
if (!ce->__set) {
17631767
ce->__set = ce->parent->__set;
17641768
}
1769+
if (!ce->__unset) {
1770+
ce->__unset = ce->parent->__unset;
1771+
}
1772+
if (!ce->__isset) {
1773+
ce->__isset = ce->parent->__isset;
1774+
}
17651775
if (!ce->__call) {
17661776
ce->__call = ce->parent->__call;
17671777
}
@@ -3932,6 +3942,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify
39323942
ce->clone = NULL;
39333943
ce->__get = NULL;
39343944
ce->__set = NULL;
3945+
ce->__unset = NULL;
3946+
ce->__isset = NULL;
39353947
ce->__call = NULL;
39363948
ce->create_object = NULL;
39373949
ce->get_iterator = NULL;

Zend/zend_compile.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,8 @@ END_EXTERN_C()
680680
#define ZEND_DESTRUCTOR_FUNC_NAME "__destruct"
681681
#define ZEND_GET_FUNC_NAME "__get"
682682
#define ZEND_SET_FUNC_NAME "__set"
683+
#define ZEND_UNSET_FUNC_NAME "__unset"
684+
#define ZEND_ISSET_FUNC_NAME "__isset"
683685
#define ZEND_CALL_FUNC_NAME "__call"
684686
#define ZEND_AUTOLOAD_FUNC_NAME "__autoload"
685687

Zend/zend_object_handlers.c

Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,40 @@ static int zend_std_call_setter(zval *object, zval *member, zval *value TSRMLS_D
109109
}
110110
}
111111

112+
static void zend_std_call_unsetter(zval *object, zval *member TSRMLS_DC)
113+
{
114+
zend_class_entry *ce = Z_OBJCE_P(object);
115+
116+
/* __unset handler is called with one argument:
117+
property name
118+
*/
119+
120+
SEPARATE_ARG_IF_REF(member);
121+
122+
zend_call_method_with_1_params(&object, ce, &ce->__unset, ZEND_UNSET_FUNC_NAME, NULL, member);
123+
124+
zval_ptr_dtor(&member);
125+
}
126+
127+
static zval *zend_std_call_issetter(zval *object, zval *member TSRMLS_DC)
128+
{
129+
zval *retval = NULL;
130+
zend_class_entry *ce = Z_OBJCE_P(object);
131+
132+
/* __isset handler is called with one argument:
133+
property name
134+
135+
it should return whether the property is set or not
136+
*/
137+
138+
SEPARATE_ARG_IF_REF(member);
139+
140+
zend_call_method_with_1_params(&object, ce, &ce->__isset, ZEND_ISSET_FUNC_NAME, &retval, member);
141+
142+
zval_ptr_dtor(&member);
143+
144+
return retval;
145+
}
112146

113147
static int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC)
114148
{
@@ -420,18 +454,25 @@ static int zend_std_has_dimension(zval *object, zval *offset, int check_empty TS
420454
if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) {
421455
SEPARATE_ARG_IF_REF(offset);
422456
zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset);
423-
zval_ptr_dtor(&offset);
424457
if (retval) {
425458
result = i_zend_is_true(retval);
426459
zval_ptr_dtor(&retval);
427-
return result;
460+
if (check_empty && result && !EG(exception)) {
461+
zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset);
462+
if (retval) {
463+
result = i_zend_is_true(retval);
464+
zval_ptr_dtor(&retval);
465+
}
466+
}
428467
} else {
429-
return 0;
468+
result = 0;
430469
}
470+
zval_ptr_dtor(&offset);
431471
} else {
432472
zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name);
433473
return 0;
434474
}
475+
return result;
435476
}
436477

437478

@@ -482,23 +523,35 @@ static zval **zend_std_get_property_ptr_ptr(zval *object, zval *member TSRMLS_DC
482523
static void zend_std_unset_property(zval *object, zval *member TSRMLS_DC)
483524
{
484525
zend_object *zobj;
485-
zval tmp_member;
526+
zval *tmp_member = NULL;
486527
zend_property_info *property_info;
528+
zend_bool use_unset;
487529

488530
zobj = Z_OBJ_P(object);
531+
use_unset = (zobj->ce->__unset && !zobj->in_unset);
489532

490533
if (member->type != IS_STRING) {
491-
tmp_member = *member;
492-
zval_copy_ctor(&tmp_member);
493-
convert_to_string(&tmp_member);
494-
member = &tmp_member;
534+
ALLOC_ZVAL(tmp_member);
535+
*tmp_member = *member;
536+
INIT_PZVAL(tmp_member);
537+
zval_copy_ctor(tmp_member);
538+
convert_to_string(tmp_member);
539+
member = tmp_member;
495540
}
496541

497542
property_info = zend_get_property_info(zobj->ce, member, 0 TSRMLS_CC);
498543

499-
zend_hash_del(zobj->properties, property_info->name, property_info->name_length+1);
500-
if (member == &tmp_member) {
501-
zval_dtor(member);
544+
if (!property_info || zend_hash_del(zobj->properties, property_info->name, property_info->name_length+1) == FAILURE) {
545+
if (use_unset) {
546+
/* have unseter - try with it! */
547+
zobj->in_unset = 1; /* prevent circular unsetting */
548+
zend_std_call_unsetter(object, member TSRMLS_CC);
549+
zobj->in_unset = 0;
550+
}
551+
}
552+
553+
if (tmp_member) {
554+
zval_ptr_dtor(&tmp_member);
502555
}
503556
}
504557

@@ -837,27 +890,51 @@ static int zend_std_has_property(zval *object, zval *member, int has_set_exists
837890
zend_object *zobj;
838891
int result;
839892
zval **value;
840-
zval tmp_member;
893+
zval *tmp_member = NULL;
841894
zend_property_info *property_info;
895+
zend_bool use_isset;
842896

843897
zobj = Z_OBJ_P(object);
898+
use_isset = (zobj->ce->__isset && !zobj->in_isset);
844899

845900
if (member->type != IS_STRING) {
846-
tmp_member = *member;
847-
zval_copy_ctor(&tmp_member);
848-
convert_to_string(&tmp_member);
849-
member = &tmp_member;
901+
ALLOC_ZVAL(tmp_member);
902+
*tmp_member = *member;
903+
INIT_PZVAL(tmp_member);
904+
zval_copy_ctor(tmp_member);
905+
convert_to_string(tmp_member);
906+
member = tmp_member;
850907
}
851908

852909
#if DEBUG_OBJECT_HANDLERS
853910
fprintf(stderr, "Read object #%d property: %s\n", Z_OBJ_HANDLE_P(object), Z_STRVAL_P(member));
854911
#endif
855912

856-
if (!(property_info = zend_get_property_info(zobj->ce, member, 1 TSRMLS_CC))) {
857-
return 0;
858-
}
913+
property_info = zend_get_property_info(zobj->ce, member, 1 TSRMLS_CC);
914+
915+
if (!property_info || zend_hash_quick_find(zobj->properties, property_info->name, property_info->name_length+1, property_info->h, (void **) &value) == FAILURE) {
916+
result = 0;
917+
if (use_isset && (has_set_exists != 2)) {
918+
zval *rv;
859919

860-
if (zend_hash_find(zobj->properties, property_info->name, property_info->name_length+1, (void **) &value) == SUCCESS) {
920+
/* have issetter - try with it! */
921+
zobj->in_isset = 1; /* prevent circular getting */
922+
rv = zend_std_call_issetter(object, member TSRMLS_CC);
923+
zobj->in_isset = 0;
924+
if (rv) {
925+
result = zend_is_true(rv);
926+
zval_ptr_dtor(&rv);
927+
if (has_set_exists && result && !EG(exception) && zobj->ce->__get && !zobj->in_get) {
928+
rv = zend_std_call_getter(object, member TSRMLS_CC);
929+
if (rv) {
930+
rv->refcount++;
931+
result = i_zend_is_true(rv);
932+
zval_ptr_dtor(&rv);
933+
}
934+
}
935+
}
936+
}
937+
} else {
861938
switch (has_set_exists) {
862939
case 0:
863940
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
869946
result = 1;
870947
break;
871948
}
872-
} else {
873-
result = 0;
874949
}
875950

876-
if (member == &tmp_member) {
877-
zval_dtor(member);
951+
if (tmp_member) {
952+
zval_ptr_dtor(&tmp_member);
878953
}
879954
return result;
880955
}

Zend/zend_objects.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_ent
103103
retval.handlers = &std_object_handlers;
104104
(*object)->in_get = 0;
105105
(*object)->in_set = 0;
106+
(*object)->in_unset = 0;
107+
(*object)->in_isset = 0;
106108
return retval;
107109
}
108110

Zend/zend_reflection_api.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ static void reflection_objects_clone(void *object, void **object_clone TSRMLS_DC
205205
(*intern_clone)->zo.ce = intern->zo.ce;
206206
(*intern_clone)->zo.in_get = 0;
207207
(*intern_clone)->zo.in_set = 0;
208+
(*intern_clone)->zo.in_unset = 0;
209+
(*intern_clone)->zo.in_isset = 0;
208210
ALLOC_HASHTABLE((*intern_clone)->zo.properties);
209211
(*intern_clone)->ptr = intern->ptr;
210212
(*intern_clone)->free_ptr = intern->free_ptr;
@@ -224,6 +226,8 @@ static zend_object_value reflection_objects_new(zend_class_entry *class_type TSR
224226
intern->zo.ce = class_type;
225227
intern->zo.in_get = 0;
226228
intern->zo.in_set = 0;
229+
intern->zo.in_unset = 0;
230+
intern->zo.in_isset = 0;
227231
intern->ptr = NULL;
228232
intern->obj = NULL;
229233
intern->free_ptr = 0;

ext/reflection/php_reflection.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ static void reflection_objects_clone(void *object, void **object_clone TSRMLS_DC
205205
(*intern_clone)->zo.ce = intern->zo.ce;
206206
(*intern_clone)->zo.in_get = 0;
207207
(*intern_clone)->zo.in_set = 0;
208+
(*intern_clone)->zo.in_unset = 0;
209+
(*intern_clone)->zo.in_isset = 0;
208210
ALLOC_HASHTABLE((*intern_clone)->zo.properties);
209211
(*intern_clone)->ptr = intern->ptr;
210212
(*intern_clone)->free_ptr = intern->free_ptr;
@@ -224,6 +226,8 @@ static zend_object_value reflection_objects_new(zend_class_entry *class_type TSR
224226
intern->zo.ce = class_type;
225227
intern->zo.in_get = 0;
226228
intern->zo.in_set = 0;
229+
intern->zo.in_unset = 0;
230+
intern->zo.in_isset = 0;
227231
intern->ptr = NULL;
228232
intern->obj = NULL;
229233
intern->free_ptr = 0;

0 commit comments

Comments
 (0)