Skip to content

Commit 081349f

Browse files
committed
Improved unserialize() performance. Checks for object propery "visibility change" were moved, to be performed only if name/visibility had been really changed.
1 parent bf0f6aa commit 081349f

File tree

1 file changed

+178
-104
lines changed

1 file changed

+178
-104
lines changed

ext/standard/var_unserializer.re

Lines changed: 178 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ static inline size_t parse_uiv(const unsigned char *p)
450450

451451
static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER, int as_key);
452452

453-
static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
453+
static zend_always_inline int process_nested_array_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements)
454454
{
455455
if (var_hash) {
456456
if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
@@ -467,7 +467,6 @@ static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTab
467467
while (elements-- > 0) {
468468
zval key, *data, d, *old_data;
469469
zend_ulong idx;
470-
zend_property_info *info = NULL;
471470

472471
ZVAL_UNDEF(&key);
473472

@@ -479,118 +478,196 @@ static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTab
479478
data = NULL;
480479
ZVAL_UNDEF(&d);
481480

482-
if (!obj) {
483-
if (Z_TYPE(key) == IS_LONG) {
484-
idx = Z_LVAL(key);
481+
if (Z_TYPE(key) == IS_LONG) {
482+
idx = Z_LVAL(key);
485483
numeric_key:
486-
if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) {
487-
//??? update hash
488-
var_push_dtor(var_hash, old_data);
489-
data = zend_hash_index_update(ht, idx, &d);
490-
} else {
491-
data = zend_hash_index_add_new(ht, idx, &d);
492-
}
493-
} else if (Z_TYPE(key) == IS_STRING) {
494-
if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
495-
goto numeric_key;
496-
}
497-
if (UNEXPECTED((old_data = zend_hash_find(ht, Z_STR(key))) != NULL)) {
498-
//??? update hash
499-
var_push_dtor(var_hash, old_data);
500-
data = zend_hash_update(ht, Z_STR(key), &d);
501-
} else {
502-
data = zend_hash_add_new(ht, Z_STR(key), &d);
503-
}
484+
if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) {
485+
//??? update hash
486+
var_push_dtor(var_hash, old_data);
487+
data = zend_hash_index_update(ht, idx, &d);
504488
} else {
505-
zval_ptr_dtor(&key);
506-
goto failure;
489+
data = zend_hash_index_add_new(ht, idx, &d);
490+
}
491+
} else if (Z_TYPE(key) == IS_STRING) {
492+
if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
493+
goto numeric_key;
494+
}
495+
if (UNEXPECTED((old_data = zend_hash_find(ht, Z_STR(key))) != NULL)) {
496+
//??? update hash
497+
var_push_dtor(var_hash, old_data);
498+
data = zend_hash_update(ht, Z_STR(key), &d);
499+
} else {
500+
data = zend_hash_add_new(ht, Z_STR(key), &d);
501+
}
502+
} else {
503+
zval_ptr_dtor(&key);
504+
goto failure;
505+
}
506+
507+
if (!php_var_unserialize_internal(data, p, max, var_hash, 0)) {
508+
zval_ptr_dtor(&key);
509+
goto failure;
510+
}
511+
512+
if (BG(unserialize).level > 1) {
513+
var_push_dtor(var_hash, data);
514+
}
515+
zval_ptr_dtor_str(&key);
516+
517+
if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
518+
(*p)--;
519+
goto failure;
520+
}
521+
}
522+
523+
if (var_hash) {
524+
(*var_hash)->cur_depth--;
525+
}
526+
return 1;
527+
528+
failure:
529+
if (var_hash) {
530+
(*var_hash)->cur_depth--;
531+
}
532+
return 0;
533+
}
534+
535+
static int is_property_visibility_changed(zend_class_entry *ce, zval *key)
536+
{
537+
if (zend_hash_num_elements(&ce->properties_info) > 0) {
538+
zend_property_info *existing_propinfo;
539+
const char *unmangled_class = NULL;
540+
const char *unmangled_prop;
541+
size_t unmangled_prop_len;
542+
543+
if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR_P(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
544+
zval_ptr_dtor_str(key);
545+
return -1;
546+
}
547+
548+
if (unmangled_class == NULL) {
549+
existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key));
550+
if (existing_propinfo != NULL) {
551+
zval_ptr_dtor_str(key);
552+
ZVAL_STR_COPY(key, existing_propinfo->name);
553+
return 1;
507554
}
508555
} else {
509-
if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
556+
if (!strcmp(unmangled_class, "*")
557+
|| !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) {
558+
zend_string *unmangled = zend_string_init(unmangled_prop, unmangled_prop_len, 0);
559+
560+
existing_propinfo = zend_hash_find_ptr(&ce->properties_info, unmangled);
561+
if (existing_propinfo != NULL) {
562+
zend_string_release_ex(unmangled, 0);
563+
zval_ptr_dtor_str(key);
564+
ZVAL_STR_COPY(key, existing_propinfo->name);
565+
return 1;
566+
} else {
567+
zval_ptr_dtor_str(key);
568+
ZVAL_STR(key, unmangled);
569+
return 0;
570+
}
571+
}
572+
}
573+
}
574+
return 0;
575+
}
576+
577+
578+
static zend_always_inline int process_nested_object_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
579+
{
580+
if (var_hash) {
581+
if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
582+
php_error_docref(NULL, E_WARNING,
583+
"Maximum depth of " ZEND_LONG_FMT " exceeded. "
584+
"The depth limit can be changed using the max_depth unserialize() option "
585+
"or the unserialize_max_depth ini setting",
586+
(*var_hash)->max_depth);
587+
return 0;
588+
}
589+
(*var_hash)->cur_depth++;
590+
}
591+
592+
while (elements-- > 0) {
593+
zval key, *data, d, *old_data;
594+
zend_property_info *info = NULL;
595+
596+
ZVAL_UNDEF(&key);
597+
598+
if (!php_var_unserialize_internal(&key, p, max, NULL, 1)) {
599+
zval_ptr_dtor(&key);
600+
goto failure;
601+
}
602+
603+
data = NULL;
604+
ZVAL_UNDEF(&d);
605+
606+
if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
510607
string_key:
511-
if (obj && zend_hash_num_elements(&obj->ce->properties_info) > 0) {
512-
zend_property_info *existing_propinfo;
513-
zend_string *new_key;
514-
const char *unmangled_class = NULL;
515-
const char *unmangled_prop;
516-
size_t unmangled_prop_len;
517-
zend_string *unmangled;
518-
519-
if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
520-
zval_ptr_dtor(&key);
521-
goto failure;
522-
}
608+
if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
609+
if (Z_TYPE_P(old_data) == IS_INDIRECT) {
610+
declared_property:
611+
/* This is a property with a declaration */
612+
old_data = Z_INDIRECT_P(old_data);
613+
info = zend_get_typed_property_info_for_slot(obj, old_data);
614+
if (info) {
615+
if (Z_ISREF_P(old_data)) {
616+
/* If the value is overwritten, remove old type source from ref. */
617+
ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(old_data), info);
618+
}
523619

524-
unmangled = zend_string_init(unmangled_prop, unmangled_prop_len, 0);
525-
526-
existing_propinfo = zend_hash_find_ptr(&obj->ce->properties_info, unmangled);
527-
if ((unmangled_class == NULL || !strcmp(unmangled_class, "*") || !strcasecmp(unmangled_class, ZSTR_VAL(obj->ce->name)))
528-
&& (existing_propinfo != NULL)
529-
&& (existing_propinfo->flags & ZEND_ACC_PPP_MASK)) {
530-
if (existing_propinfo->flags & ZEND_ACC_PROTECTED) {
531-
new_key = zend_mangle_property_name(
532-
"*", 1, ZSTR_VAL(unmangled), ZSTR_LEN(unmangled), 0);
533-
zend_string_release_ex(unmangled, 0);
534-
} else if (existing_propinfo->flags & ZEND_ACC_PRIVATE) {
535-
if (unmangled_class != NULL && strcmp(unmangled_class, "*") != 0) {
536-
new_key = zend_mangle_property_name(
537-
unmangled_class, strlen(unmangled_class),
538-
ZSTR_VAL(unmangled), ZSTR_LEN(unmangled),
539-
0);
540-
} else {
541-
new_key = zend_mangle_property_name(
542-
ZSTR_VAL(existing_propinfo->ce->name), ZSTR_LEN(existing_propinfo->ce->name),
543-
ZSTR_VAL(unmangled), ZSTR_LEN(unmangled),
544-
0);
545-
}
546-
zend_string_release_ex(unmangled, 0);
547-
} else {
548-
ZEND_ASSERT(existing_propinfo->flags & ZEND_ACC_PUBLIC);
549-
new_key = unmangled;
620+
if ((*var_hash)->ref_props) {
621+
/* Remove old entry from ref_props table, if it exists. */
622+
zend_hash_index_del(
623+
(*var_hash)->ref_props, (zend_uintptr_t) old_data);
550624
}
551-
zval_ptr_dtor_str(&key);
552-
ZVAL_STR(&key, new_key);
625+
}
626+
var_push_dtor(var_hash, old_data);
627+
Z_TRY_DELREF_P(old_data);
628+
ZVAL_NULL(old_data);
629+
data = old_data;
630+
} else {
631+
int ret = is_property_visibility_changed(obj->ce, &key);
632+
633+
if (EXPECTED(!ret)) {
634+
var_push_dtor(var_hash, old_data);
635+
data = zend_hash_update(ht, Z_STR(key), &d);
636+
} else if (ret < 0) {
637+
goto failure;
553638
} else {
554-
zend_string_release_ex(unmangled, 0);
639+
goto second_try;
555640
}
556641
}
642+
} else {
643+
int ret = is_property_visibility_changed(obj->ce, &key);
557644

558-
if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
559-
if (Z_TYPE_P(old_data) == IS_INDIRECT) {
560-
/* This is a property with a declaration */
561-
old_data = Z_INDIRECT_P(old_data);
562-
info = zend_get_typed_property_info_for_slot(obj, old_data);
563-
if (info) {
564-
if (Z_ISREF_P(old_data)) {
565-
/* If the value is overwritten, remove old type source from ref. */
566-
ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(old_data), info);
567-
}
568-
569-
if ((*var_hash)->ref_props) {
570-
/* Remove old entry from ref_props table, if it exists. */
571-
zend_hash_index_del(
572-
(*var_hash)->ref_props, (zend_uintptr_t) old_data);
573-
}
645+
if (EXPECTED(!ret)) {
646+
data = zend_hash_add_new(ht, Z_STR(key), &d);
647+
} else if (ret < 0) {
648+
goto failure;
649+
} else {
650+
second_try:
651+
if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
652+
if (Z_TYPE_P(old_data) == IS_INDIRECT) {
653+
goto declared_property;
654+
} else {
655+
var_push_dtor(var_hash, old_data);
656+
data = zend_hash_update(ht, Z_STR(key), &d);
574657
}
575-
var_push_dtor(var_hash, old_data);
576-
Z_TRY_DELREF_P(old_data);
577-
ZVAL_COPY_VALUE(old_data, &d);
578-
data = old_data;
579658
} else {
580-
var_push_dtor(var_hash, old_data);
581-
data = zend_hash_update_ind(ht, Z_STR(key), &d);
659+
data = zend_hash_add_new(ht, Z_STR(key), &d);
582660
}
583-
} else {
584-
data = zend_hash_add_new(ht, Z_STR(key), &d);
585661
}
586-
} else if (Z_TYPE(key) == IS_LONG) {
587-
/* object properties should include no integers */
588-
convert_to_string(&key);
589-
goto string_key;
590-
} else {
591-
zval_ptr_dtor(&key);
592-
goto failure;
593662
}
663+
zval_ptr_dtor_str(&key);
664+
} else if (Z_TYPE(key) == IS_LONG) {
665+
/* object properties should include no integers */
666+
convert_to_string(&key);
667+
goto string_key;
668+
} else {
669+
zval_ptr_dtor(&key);
670+
goto failure;
594671
}
595672

596673
if (!php_var_unserialize_internal(data, p, max, var_hash, 0)) {
@@ -599,15 +676,13 @@ string_key:
599676
* The data is still stored in the property. */
600677
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
601678
}
602-
zval_ptr_dtor(&key);
603679
goto failure;
604680
}
605681

606682
if (UNEXPECTED(info)) {
607683
if (!zend_verify_prop_assignable_by_ref(info, data, /* strict */ 1)) {
608684
zval_ptr_dtor(data);
609685
ZVAL_UNDEF(data);
610-
zval_ptr_dtor_nogc(&key);
611686
goto failure;
612687
}
613688

@@ -628,7 +703,6 @@ string_key:
628703
if (BG(unserialize).level > 1) {
629704
var_push_dtor(var_hash, data);
630705
}
631-
zval_ptr_dtor_str(&key);
632706

633707
if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
634708
(*p)--;
@@ -707,7 +781,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool
707781
array_init_size(&ary, elements);
708782
/* Avoid reallocation due to packed -> mixed conversion. */
709783
zend_hash_real_init_mixed(Z_ARRVAL(ary));
710-
if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements, NULL)) {
784+
if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements)) {
711785
ZVAL_DEREF(rval);
712786
GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
713787
zval_ptr_dtor(&ary);
@@ -735,7 +809,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool
735809
}
736810

737811
zend_hash_extend(ht, zend_hash_num_elements(ht) + elements, HT_FLAGS(ht) & HASH_FLAG_PACKED);
738-
if (!process_nested_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
812+
if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
739813
if (has_wakeup) {
740814
ZVAL_DEREF(rval);
741815
GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
@@ -1025,7 +1099,7 @@ use_double:
10251099
* prohibit "r:" references to non-objects, as we only generate them for objects. */
10261100
HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(rval));
10271101
1028-
if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements, NULL)) {
1102+
if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements)) {
10291103
return 0;
10301104
}
10311105

0 commit comments

Comments
 (0)