diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 66de460a309e6..03acbed9f6336 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1701,6 +1701,130 @@ int zend_std_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **f } /* }}} */ +static int serialize_call_sleep(zval *retval, zval *struc) /* {{{ */ +{ + zval fname; + int res; + + ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1); + res = call_user_function_ex(CG(function_table), struc, &fname, retval, 0, 0, 1, NULL); + zval_dtor(&fname); + + if (res == FAILURE || Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + return FAILURE; + } + + if (!HASH_OF(retval)) { + zval_ptr_dtor(retval); + zend_error(E_NOTICE, "serialize(): __sleep should return an array only containing the names of instance-variables to serialize"); + return FAILURE; + } + + return SUCCESS; +} +/* }}} */ + +static int serialize_try_add_sleep_prop(HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name) /* {{{ */ +{ + zval *val = zend_hash_find(props, name); + if (val == NULL) { + return FAILURE; + } + + if (Z_TYPE_P(val) == IS_INDIRECT) { + val = Z_INDIRECT_P(val); + if (Z_TYPE_P(val) == IS_UNDEF) { + return FAILURE; + } + } + + if (!zend_hash_add(ht, name, val)) { + zend_error(E_NOTICE, "serialize(): \"%s\" is returned from __sleep multiple times", + ZSTR_VAL(error_name)); + return SUCCESS; + } + + Z_TRY_ADDREF_P(val); + return SUCCESS; +} +/* }}} */ + +static HashTable *serialize_get_sleep_props(zval *struc, HashTable *sleep_retval) /* {{{ */ +{ + zend_class_entry *ce = Z_OBJCE_P(struc); + HashTable *ht, *props = Z_OBJPROP_P(struc); + zval *name_val; + + ALLOC_HASHTABLE(ht); + zend_hash_init(ht, zend_hash_num_elements(sleep_retval), NULL, ZVAL_PTR_DTOR, 0); + + ZEND_HASH_FOREACH_VAL(sleep_retval, name_val) { + zend_string *name, *prot_name, *priv_name; + + if (Z_TYPE_P(name_val) != IS_STRING) { + zend_error(E_NOTICE, "serialize(): __sleep should return an array only containing the names of instance-variables to serialize."); + } + + name = zval_get_string(name_val); + + if (serialize_try_add_sleep_prop(ht, props, name, name) == SUCCESS) { + zend_string_release(name); + continue; + } + + priv_name = zend_mangle_property_name( + ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), + ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); + if (serialize_try_add_sleep_prop(ht, props, priv_name, name) == SUCCESS) { + zend_string_release(name); + zend_string_release(priv_name); + continue; + } + zend_string_release(priv_name); + + prot_name = zend_mangle_property_name( + "*", 1, ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); + if (serialize_try_add_sleep_prop(ht, props, prot_name, name) == SUCCESS) { + zend_string_release(name); + zend_string_release(prot_name); + continue; + } + zend_string_release(prot_name); + + zend_error(E_NOTICE, "serialize(): \"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name)); + zend_hash_add(ht, name, &EG(uninitialized_zval)); + + zend_string_release(name); + } ZEND_HASH_FOREACH_END(); + + return ht; +} +/* }}} */ + +ZEND_API HashTable *zend_std_get_serialize_properties(zval *obj, int *is_temp) /* {{{ */ +{ + zend_class_entry *ce = Z_OBJCE_P(obj); + HashTable *ht; + if (zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) { + zval retval; + + if (serialize_call_sleep(&retval, obj) == FAILURE) { + return NULL; + } + + *is_temp = 1; + ht = serialize_get_sleep_props(obj, HASH_OF(&retval)); + zval_ptr_dtor(&retval); + + return ht; + } + + *is_temp = 0; + return Z_OBJPROP_P(obj); +} +/* }}} */ + ZEND_API zend_object_handlers std_object_handlers = { 0, /* offset */ @@ -1732,6 +1856,7 @@ ZEND_API zend_object_handlers std_object_handlers = { zend_std_get_gc, /* get_gc */ NULL, /* do_operation */ NULL, /* compare */ + zend_std_get_serialize_properties, /* get_serialize_properties */ }; /* diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index ecb4f6e1a00b1..7d70d872f3bb9 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -121,6 +121,8 @@ typedef HashTable *(*zend_object_get_gc_t)(zval *object, zval **table, int *n); typedef int (*zend_object_do_operation_t)(zend_uchar opcode, zval *result, zval *op1, zval *op2); +typedef HashTable *(*zend_object_get_serialize_properties_t)(zval *object, int *is_temp); + struct _zend_object_handlers { /* offset of real object header (usually zero) */ int offset; @@ -153,6 +155,7 @@ struct _zend_object_handlers { zend_object_get_gc_t get_gc; zend_object_do_operation_t do_operation; zend_object_compare_zvals_t compare; + zend_object_get_serialize_properties_t get_serialize_properties; }; extern ZEND_API zend_object_handlers std_object_handlers; @@ -170,6 +173,7 @@ ZEND_API HashTable *zend_std_get_properties(zval *object); ZEND_API HashTable *zend_std_get_debug_info(zval *object, int *is_temp); ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type); ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, void **cache_slot); +ZEND_API HashTable *zend_std_get_serialize_properties(zval *object, int *is_temp); ZEND_API void rebuild_object_properties(zend_object *zobj); ZEND_API int zend_check_private(union _zend_function *fbc, zend_class_entry *ce, zend_string *function_name); diff --git a/ext/standard/tests/serialize/sleep_duplicate_props_via_mangling.phpt b/ext/standard/tests/serialize/sleep_duplicate_props_via_mangling.phpt new file mode 100644 index 0000000000000..13541f872b042 Binary files /dev/null and b/ext/standard/tests/serialize/sleep_duplicate_props_via_mangling.phpt differ diff --git a/ext/standard/var.c b/ext/standard/var.c index d6d17c2ec6d09..36fc7b53728e4 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -671,144 +671,54 @@ static inline zend_bool php_var_serialize_class_name(smart_str *buf, zval *struc } /* }}} */ -static int php_var_serialize_call_sleep(zval *retval, zval *struc) /* {{{ */ +static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable *ht, uint32_t count, zend_bool incomplete_class, php_serialize_data_t var_hash) /* {{{ */ { - zval fname; - int res; - - ZVAL_STRINGL(&fname, "__sleep", sizeof("__sleep") - 1); - BG(serialize_lock)++; - res = call_user_function_ex(CG(function_table), struc, &fname, retval, 0, 0, 1, NULL); - BG(serialize_lock)--; - zval_dtor(&fname); - - if (res == FAILURE || Z_ISUNDEF_P(retval)) { - zval_ptr_dtor(retval); - return FAILURE; - } - - if (!HASH_OF(retval)) { - zval_ptr_dtor(retval); - php_error_docref(NULL, E_NOTICE, "__sleep should return an array only containing the names of instance-variables to serialize"); - return FAILURE; - } - - return SUCCESS; -} -/* }}} */ - -static void php_var_serialize_collect_names(HashTable *ht, HashTable *src) /* {{{ */ -{ - zval *val; - zend_string *name; - - zend_hash_init(ht, zend_hash_num_elements(src), NULL, NULL, 0); - ZEND_HASH_FOREACH_VAL(src, val) { - if (Z_TYPE_P(val) != IS_STRING) { - php_error_docref(NULL, E_NOTICE, - "__sleep should return an array only containing the names of instance-variables to serialize."); - } - - name = zval_get_string(val); - if (zend_hash_exists(ht, name)) { - php_error_docref(NULL, E_NOTICE, - "\"%s\" is returned from __sleep multiple times", ZSTR_VAL(name)); - zend_string_release(name); - continue; - } - zend_hash_add_empty_element(ht, name); - zend_string_release(name); - } ZEND_HASH_FOREACH_END(); -} -/* }}} */ - -static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */ -{ - zend_class_entry *ce = Z_OBJCE_P(struc); - HashTable names, *propers; - zval nval; - zend_string *name; - - php_var_serialize_class_name(buf, struc); - php_var_serialize_collect_names(&names, HASH_OF(retval_ptr)); - - smart_str_append_unsigned(buf, zend_hash_num_elements(&names)); + smart_str_append_unsigned(buf, count); smart_str_appendl(buf, ":{", 2); - - ZVAL_NULL(&nval); - propers = Z_OBJPROP_P(struc); - - ZEND_HASH_FOREACH_STR_KEY(&names, name) { - zend_string *prot_name, *priv_name; - - zval *val = zend_hash_find(propers, name); - if (val != NULL) { - if (Z_TYPE_P(val) == IS_INDIRECT) { - val = Z_INDIRECT_P(val); - if (Z_TYPE_P(val) == IS_UNDEF) { - goto undef_prop; - } + if (count > 0) { + zend_string *key; + zval *data; + zend_ulong index; + + ZEND_HASH_FOREACH_KEY_VAL_IND(ht, index, key, data) { + if (incomplete_class && strcmp(ZSTR_VAL(key), MAGIC_MEMBER) == 0) { + continue; } - php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name)); - php_var_serialize_intern(buf, val, var_hash); - continue; - } + if (!key) { + php_var_serialize_long(buf, index); + } else { + php_var_serialize_string(buf, ZSTR_VAL(key), ZSTR_LEN(key)); + } - priv_name = zend_mangle_property_name( - ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); - val = zend_hash_find(propers, priv_name); - if (val != NULL) { - if (Z_TYPE_P(val) == IS_INDIRECT) { - val = Z_INDIRECT_P(val); - if (Z_ISUNDEF_P(val)) { - zend_string_free(priv_name); - goto undef_prop; - } + if (Z_ISREF_P(data) && Z_REFCOUNT_P(data) == 1) { + data = Z_REFVAL_P(data); } - php_var_serialize_string(buf, ZSTR_VAL(priv_name), ZSTR_LEN(priv_name)); - zend_string_free(priv_name); - php_var_serialize_intern(buf, val, var_hash); - continue; - } - zend_string_free(priv_name); - - prot_name = zend_mangle_property_name( - "*", 1, ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); - val = zend_hash_find(propers, prot_name); - if (val != NULL) { - if (Z_TYPE_P(val) == IS_INDIRECT) { - val = Z_INDIRECT_P(val); - if (Z_TYPE_P(val) == IS_UNDEF) { - zend_string_free(prot_name); - goto undef_prop; + /* we should still add element even if it's not OK, + * since we already wrote the length of the array before */ + if ((Z_TYPE_P(data) == IS_ARRAY && Z_TYPE_P(struc) == IS_ARRAY && Z_ARR_P(data) == Z_ARR_P(struc)) + || (Z_TYPE_P(data) == IS_ARRAY && Z_ARRVAL_P(data)->u.v.nApplyCount > 1) + ) { + smart_str_appendl(buf, "N;", 2); + } else { + if (Z_TYPE_P(data) == IS_ARRAY && ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(data))) { + Z_ARRVAL_P(data)->u.v.nApplyCount++; + } + php_var_serialize_intern(buf, data, var_hash); + if (Z_TYPE_P(data) == IS_ARRAY && ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(data))) { + Z_ARRVAL_P(data)->u.v.nApplyCount--; } } - - php_var_serialize_string(buf, ZSTR_VAL(prot_name), ZSTR_LEN(prot_name)); - zend_string_free(prot_name); - php_var_serialize_intern(buf, val, var_hash); - continue; - } - zend_string_free(prot_name); - -undef_prop: - php_var_serialize_string(buf, ZSTR_VAL(name), ZSTR_LEN(name)); - php_var_serialize_intern(buf, &nval, var_hash); - php_error_docref(NULL, E_NOTICE, - "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name)); - } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + } smart_str_appendc(buf, '}'); - - zend_hash_destroy(&names); } /* }}} */ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash) /* {{{ */ { zend_long var_already; - HashTable *myht; if (EG(exception)) { return; @@ -861,6 +771,11 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ case IS_OBJECT: { zend_class_entry *ce = Z_OBJCE_P(struc); + HashTable *ht; + int is_temp; + zend_bool incomplete_class; + uint32_t count; + zval tmp; if (ce->serialize != NULL) { /* has custom handler */ @@ -887,86 +802,48 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ return; } - if (ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep")-1)) { - zval retval, tmp; - ZVAL_COPY(&tmp, struc); - - if (php_var_serialize_call_sleep(&retval, &tmp) == FAILURE) { - if (!EG(exception)) { - /* we should still add element even if it's not OK, - * since we already wrote the length of the array before */ - smart_str_appendl(buf, "N;", 2); - } - zval_ptr_dtor(&tmp); - return; - } + /* Make sure the object is not freed by __sleep() or similar. */ + ZVAL_COPY(&tmp, struc); + + BG(serialize_lock)++; + ht = Z_OBJ_HT_P(&tmp)->get_serialize_properties + ? Z_OBJ_HT_P(&tmp)->get_serialize_properties(&tmp, &is_temp) + : zend_std_get_serialize_properties(&tmp, &is_temp); + BG(serialize_lock)--; - php_var_serialize_class(buf, &tmp, &retval, var_hash); - zval_ptr_dtor(&retval); + if (!ht) { + if (!EG(exception)) { + /* we should still add element even if it's not OK, + * since we already wrote the length of the array before */ + smart_str_appendl(buf, "N;", 2); + } zval_ptr_dtor(&tmp); return; } - /* fall-through */ - } - case IS_ARRAY: { - uint32_t i; - zend_bool incomplete_class = 0; - if (Z_TYPE_P(struc) == IS_ARRAY) { - smart_str_appendl(buf, "a:", 2); - myht = Z_ARRVAL_P(struc); - i = zend_array_count(myht); - } else { - incomplete_class = php_var_serialize_class_name(buf, struc); - myht = Z_OBJPROP_P(struc); + incomplete_class = php_var_serialize_class_name(buf, &tmp); /* count after serializing name, since php_var_serialize_class_name * changes the count if the variable is incomplete class */ - i = zend_array_count(myht); - if (i > 0 && incomplete_class) { - --i; + count = zend_array_count(ht); + if (count > 0 && incomplete_class) { + --count; } - } - smart_str_append_unsigned(buf, i); - smart_str_appendl(buf, ":{", 2); - if (i > 0) { - zend_string *key; - zval *data; - zend_ulong index; - ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) { + php_var_serialize_nested_data( + buf, &tmp, ht, count, incomplete_class, var_hash); - if (incomplete_class && strcmp(ZSTR_VAL(key), MAGIC_MEMBER) == 0) { - continue; - } - - if (!key) { - php_var_serialize_long(buf, index); - } else { - php_var_serialize_string(buf, ZSTR_VAL(key), ZSTR_LEN(key)); - } - - if (Z_ISREF_P(data) && Z_REFCOUNT_P(data) == 1) { - data = Z_REFVAL_P(data); - } - - /* we should still add element even if it's not OK, - * since we already wrote the length of the array before */ - if ((Z_TYPE_P(data) == IS_ARRAY && Z_TYPE_P(struc) == IS_ARRAY && Z_ARR_P(data) == Z_ARR_P(struc)) - || (Z_TYPE_P(data) == IS_ARRAY && Z_ARRVAL_P(data)->u.v.nApplyCount > 1) - ) { - smart_str_appendl(buf, "N;", 2); - } else { - if (Z_TYPE_P(data) == IS_ARRAY && ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(data))) { - Z_ARRVAL_P(data)->u.v.nApplyCount++; - } - php_var_serialize_intern(buf, data, var_hash); - if (Z_TYPE_P(data) == IS_ARRAY && ZEND_HASH_APPLY_PROTECTION(Z_ARRVAL_P(data))) { - Z_ARRVAL_P(data)->u.v.nApplyCount--; - } - } - } ZEND_HASH_FOREACH_END(); + if (is_temp) { + zend_hash_destroy(ht); + FREE_HASHTABLE(ht); + } + zval_ptr_dtor(&tmp); + return; } - smart_str_appendc(buf, '}'); + case IS_ARRAY: { + HashTable *ht = Z_ARRVAL_P(struc); + uint32_t count = zend_array_count(ht); + smart_str_appendl(buf, "a:", 2); + php_var_serialize_nested_data(buf, struc, ht, count, 0, var_hash); return; } case IS_REFERENCE: diff --git a/ext/wddx/wddx.c b/ext/wddx/wddx.c index a905f59fd5646..6890d747f2584 100644 --- a/ext/wddx/wddx.c +++ b/ext/wddx/wddx.c @@ -447,13 +447,13 @@ static void php_wddx_serialize_unset(wddx_packet *packet) static void php_wddx_serialize_object(wddx_packet *packet, zval *obj) { /* OBJECTS_FIXME */ - zval *ent, fname, *varname; - zval retval; + zval *ent; zend_string *key; zend_ulong idx; char tmp_buf[WDDX_BUF_LEN]; - HashTable *objhash, *sleephash; + HashTable *objhash; zend_class_entry *ce; + int is_temp; PHP_CLASS_ATTRIBUTES; PHP_SET_CLASS_ATTRIBUTES(obj); @@ -464,54 +464,23 @@ static void php_wddx_serialize_object(wddx_packet *packet, zval *obj) return; } - ZVAL_STRING(&fname, "__sleep"); - /* - * We try to call __sleep() method on object. It's supposed to return an - * array of property names to be serialized. - */ - if (call_user_function_ex(CG(function_table), obj, &fname, &retval, 0, 0, 1, NULL) == SUCCESS) { - if (!Z_ISUNDEF(retval) && (sleephash = HASH_OF(&retval))) { - PHP_CLASS_ATTRIBUTES; - - PHP_SET_CLASS_ATTRIBUTES(obj); - - php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); - snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); - php_wddx_add_chunk(packet, tmp_buf); - php_wddx_add_chunk_static(packet, WDDX_STRING_S); - php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); - php_wddx_add_chunk_static(packet, WDDX_STRING_E); - php_wddx_add_chunk_static(packet, WDDX_VAR_E); - - objhash = Z_OBJPROP_P(obj); - - ZEND_HASH_FOREACH_VAL(sleephash, varname) { - if (Z_TYPE_P(varname) != IS_STRING) { - php_error_docref(NULL, E_NOTICE, "__sleep should return an array only containing the names of instance-variables to serialize."); - continue; - } - - if ((ent = zend_hash_find(objhash, Z_STR_P(varname))) != NULL) { - php_wddx_serialize_var(packet, ent, Z_STR_P(varname)); - } - } ZEND_HASH_FOREACH_END(); - - php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); - } - } else { - php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); - snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); - php_wddx_add_chunk(packet, tmp_buf); - php_wddx_add_chunk_static(packet, WDDX_STRING_S); - php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); - php_wddx_add_chunk_static(packet, WDDX_STRING_E); - php_wddx_add_chunk_static(packet, WDDX_VAR_E); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_S); + snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR); + php_wddx_add_chunk(packet, tmp_buf); + php_wddx_add_chunk_static(packet, WDDX_STRING_S); + php_wddx_add_chunk_ex(packet, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); + php_wddx_add_chunk_static(packet, WDDX_STRING_E); + php_wddx_add_chunk_static(packet, WDDX_VAR_E); - objhash = Z_OBJPROP_P(obj); + objhash = Z_OBJ_HT_P(obj)->get_serialize_properties + ? Z_OBJ_HT_P(obj)->get_serialize_properties(obj, &is_temp) + : zend_std_get_serialize_properties(obj, &is_temp); + if (objhash) { ZEND_HASH_FOREACH_KEY_VAL(objhash, idx, key, ent) { if (ent == obj) { continue; } + if (key) { const char *class_name, *prop_name; size_t prop_name_len; @@ -527,13 +496,16 @@ static void php_wddx_serialize_object(wddx_packet *packet, zval *obj) zend_string_release(key); } } ZEND_HASH_FOREACH_END(); - php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); + + if (is_temp) { + zend_hash_destroy(objhash); + FREE_HASHTABLE(objhash); + } } - PHP_CLEANUP_CLASS_ATTRIBUTES(); + php_wddx_add_chunk_static(packet, WDDX_STRUCT_E); - zval_ptr_dtor(&fname); - zval_ptr_dtor(&retval); + PHP_CLEANUP_CLASS_ATTRIBUTES(); } /* }}} */