]> granicus.if.org Git - php/commitdiff
Improved unserialize() performance. Checks for object propery "visibility change...
authorDmitry Stogov <dmitry@zend.com>
Tue, 16 Feb 2021 12:27:58 +0000 (15:27 +0300)
committerDmitry Stogov <dmitry@zend.com>
Tue, 16 Feb 2021 12:27:58 +0000 (15:27 +0300)
ext/standard/var_unserializer.re

index 3c620613eef3fc0733719491bf49b274b34d3df3..dec3d9c0817b38291c988a58493e77d0f5111641 100644 (file)
@@ -450,7 +450,7 @@ static inline size_t parse_uiv(const unsigned char *p)
 
 static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER, int as_key);
 
-static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
+static zend_always_inline int process_nested_array_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements)
 {
        if (var_hash) {
                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
        while (elements-- > 0) {
                zval key, *data, d, *old_data;
                zend_ulong idx;
-               zend_property_info *info = NULL;
 
                ZVAL_UNDEF(&key);
 
@@ -479,118 +478,196 @@ static zend_always_inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTab
                data = NULL;
                ZVAL_UNDEF(&d);
 
-               if (!obj) {
-                       if (Z_TYPE(key) == IS_LONG) {
-                               idx = Z_LVAL(key);
+               if (Z_TYPE(key) == IS_LONG) {
+                       idx = Z_LVAL(key);
 numeric_key:
-                               if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) {
-                                       //??? update hash
-                                       var_push_dtor(var_hash, old_data);
-                                       data = zend_hash_index_update(ht, idx, &d);
-                               } else {
-                                       data = zend_hash_index_add_new(ht, idx, &d);
-                               }
-                       } else if (Z_TYPE(key) == IS_STRING) {
-                               if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
-                                       goto numeric_key;
-                               }
-                               if (UNEXPECTED((old_data = zend_hash_find(ht, Z_STR(key))) != NULL)) {
-                                       //??? update hash
-                                       var_push_dtor(var_hash, old_data);
-                                       data = zend_hash_update(ht, Z_STR(key), &d);
-                               } else {
-                                       data = zend_hash_add_new(ht, Z_STR(key), &d);
-                               }
+                       if (UNEXPECTED((old_data = zend_hash_index_find(ht, idx)) != NULL)) {
+                               //??? update hash
+                               var_push_dtor(var_hash, old_data);
+                               data = zend_hash_index_update(ht, idx, &d);
                        } else {
-                               zval_ptr_dtor(&key);
-                               goto failure;
+                               data = zend_hash_index_add_new(ht, idx, &d);
+                       }
+               } else if (Z_TYPE(key) == IS_STRING) {
+                       if (UNEXPECTED(ZEND_HANDLE_NUMERIC(Z_STR(key), idx))) {
+                               goto numeric_key;
+                       }
+                       if (UNEXPECTED((old_data = zend_hash_find(ht, Z_STR(key))) != NULL)) {
+                               //??? update hash
+                               var_push_dtor(var_hash, old_data);
+                               data = zend_hash_update(ht, Z_STR(key), &d);
+                       } else {
+                               data = zend_hash_add_new(ht, Z_STR(key), &d);
+                       }
+               } else {
+                       zval_ptr_dtor(&key);
+                       goto failure;
+               }
+
+               if (!php_var_unserialize_internal(data, p, max, var_hash, 0)) {
+                       zval_ptr_dtor(&key);
+                       goto failure;
+               }
+
+               if (BG(unserialize).level > 1) {
+                       var_push_dtor(var_hash, data);
+               }
+               zval_ptr_dtor_str(&key);
+
+               if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
+                       (*p)--;
+                       goto failure;
+               }
+       }
+
+       if (var_hash) {
+               (*var_hash)->cur_depth--;
+       }
+       return 1;
+
+failure:
+       if (var_hash) {
+               (*var_hash)->cur_depth--;
+       }
+       return 0;
+}
+
+static int is_property_visibility_changed(zend_class_entry *ce, zval *key)
+{
+       if (zend_hash_num_elements(&ce->properties_info) > 0) {
+               zend_property_info *existing_propinfo;
+               const char *unmangled_class = NULL;
+               const char *unmangled_prop;
+               size_t unmangled_prop_len;
+
+               if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR_P(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
+                       zval_ptr_dtor_str(key);
+                       return -1;
+               }
+
+               if (unmangled_class == NULL) {
+                       existing_propinfo = zend_hash_find_ptr(&ce->properties_info, Z_STR_P(key));
+                       if (existing_propinfo != NULL) {
+                               zval_ptr_dtor_str(key);
+                               ZVAL_STR_COPY(key, existing_propinfo->name);
+                               return 1;
                        }
                } else {
-                       if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
+                       if (!strcmp(unmangled_class, "*")
+                        || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) {
+                               zend_string *unmangled = zend_string_init(unmangled_prop, unmangled_prop_len, 0);
+
+                               existing_propinfo = zend_hash_find_ptr(&ce->properties_info, unmangled);
+                               if (existing_propinfo != NULL) {
+                                       zend_string_release_ex(unmangled, 0);
+                                       zval_ptr_dtor_str(key);
+                                       ZVAL_STR_COPY(key, existing_propinfo->name);
+                                       return 1;
+                               } else {
+                                       zval_ptr_dtor_str(key);
+                                       ZVAL_STR(key, unmangled);
+                                       return 0;
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
+
+static zend_always_inline int process_nested_object_data(UNSERIALIZE_PARAMETER, HashTable *ht, zend_long elements, zend_object *obj)
+{
+       if (var_hash) {
+               if ((*var_hash)->max_depth > 0 && (*var_hash)->cur_depth >= (*var_hash)->max_depth) {
+                       php_error_docref(NULL, E_WARNING,
+                               "Maximum depth of " ZEND_LONG_FMT " exceeded. "
+                               "The depth limit can be changed using the max_depth unserialize() option "
+                               "or the unserialize_max_depth ini setting",
+                               (*var_hash)->max_depth);
+                       return 0;
+               }
+               (*var_hash)->cur_depth++;
+       }
+
+       while (elements-- > 0) {
+               zval key, *data, d, *old_data;
+               zend_property_info *info = NULL;
+
+               ZVAL_UNDEF(&key);
+
+               if (!php_var_unserialize_internal(&key, p, max, NULL, 1)) {
+                       zval_ptr_dtor(&key);
+                       goto failure;
+               }
+
+               data = NULL;
+               ZVAL_UNDEF(&d);
+
+               if (EXPECTED(Z_TYPE(key) == IS_STRING)) {
 string_key:
-                               if (obj && zend_hash_num_elements(&obj->ce->properties_info) > 0) {
-                                       zend_property_info *existing_propinfo;
-                                       zend_string *new_key;
-                                       const char *unmangled_class = NULL;
-                                       const char *unmangled_prop;
-                                       size_t unmangled_prop_len;
-                                       zend_string *unmangled;
-
-                                       if (UNEXPECTED(zend_unmangle_property_name_ex(Z_STR(key), &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) {
-                                               zval_ptr_dtor(&key);
-                                               goto failure;
-                                       }
+                       if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
+                               if (Z_TYPE_P(old_data) == IS_INDIRECT) {
+declared_property:
+                                       /* This is a property with a declaration */
+                                       old_data = Z_INDIRECT_P(old_data);
+                                       info = zend_get_typed_property_info_for_slot(obj, old_data);
+                                       if (info) {
+                                               if (Z_ISREF_P(old_data)) {
+                                                       /* If the value is overwritten, remove old type source from ref. */
+                                                       ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(old_data), info);
+                                               }
 
-                                       unmangled = zend_string_init(unmangled_prop, unmangled_prop_len, 0);
-
-                                       existing_propinfo = zend_hash_find_ptr(&obj->ce->properties_info, unmangled);
-                    if ((unmangled_class == NULL || !strcmp(unmangled_class, "*") || !strcasecmp(unmangled_class, ZSTR_VAL(obj->ce->name)))
-                            && (existing_propinfo != NULL)
-                                                       && (existing_propinfo->flags & ZEND_ACC_PPP_MASK)) {
-                                               if (existing_propinfo->flags & ZEND_ACC_PROTECTED) {
-                                                       new_key = zend_mangle_property_name(
-                                                               "*", 1, ZSTR_VAL(unmangled), ZSTR_LEN(unmangled), 0);
-                                                       zend_string_release_ex(unmangled, 0);
-                                               } else if (existing_propinfo->flags & ZEND_ACC_PRIVATE) {
-                                                       if (unmangled_class != NULL && strcmp(unmangled_class, "*") != 0) {
-                                                               new_key = zend_mangle_property_name(
-                                                                       unmangled_class, strlen(unmangled_class),
-                                                                               ZSTR_VAL(unmangled), ZSTR_LEN(unmangled),
-                                                                               0);
-                                                       } else {
-                                                               new_key = zend_mangle_property_name(
-                                                                       ZSTR_VAL(existing_propinfo->ce->name), ZSTR_LEN(existing_propinfo->ce->name),
-                                                                       ZSTR_VAL(unmangled), ZSTR_LEN(unmangled),
-                                                                       0);
-                                                       }
-                                                       zend_string_release_ex(unmangled, 0);
-                                               } else {
-                                                       ZEND_ASSERT(existing_propinfo->flags & ZEND_ACC_PUBLIC);
-                                                       new_key = unmangled;
+                                               if ((*var_hash)->ref_props) {
+                                                       /* Remove old entry from ref_props table, if it exists. */
+                                                       zend_hash_index_del(
+                                                               (*var_hash)->ref_props, (zend_uintptr_t) old_data);
                                                }
-                                               zval_ptr_dtor_str(&key);
-                                               ZVAL_STR(&key, new_key);
+                                       }
+                                       var_push_dtor(var_hash, old_data);
+                                       Z_TRY_DELREF_P(old_data);
+                                       ZVAL_NULL(old_data);
+                                       data = old_data;
+                               } else {
+                                       int ret = is_property_visibility_changed(obj->ce, &key);
+
+                                       if (EXPECTED(!ret)) {
+                                               var_push_dtor(var_hash, old_data);
+                                               data = zend_hash_update(ht, Z_STR(key), &d);
+                                       } else if (ret < 0) {
+                                               goto failure;
                                        } else {
-                                               zend_string_release_ex(unmangled, 0);
+                                               goto second_try;
                                        }
                                }
+                       } else {
+                               int ret = is_property_visibility_changed(obj->ce, &key);
 
-                               if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
-                                       if (Z_TYPE_P(old_data) == IS_INDIRECT) {
-                                               /* This is a property with a declaration */
-                                               old_data = Z_INDIRECT_P(old_data);
-                                               info = zend_get_typed_property_info_for_slot(obj, old_data);
-                                               if (info) {
-                                                       if (Z_ISREF_P(old_data)) {
-                                                               /* If the value is overwritten, remove old type source from ref. */
-                                                               ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(old_data), info);
-                                                       }
-
-                                                       if ((*var_hash)->ref_props) {
-                                                               /* Remove old entry from ref_props table, if it exists. */
-                                                               zend_hash_index_del(
-                                                                       (*var_hash)->ref_props, (zend_uintptr_t) old_data);
-                                                       }
+                               if (EXPECTED(!ret)) {
+                                       data = zend_hash_add_new(ht, Z_STR(key), &d);
+                               } else if (ret < 0) {
+                                       goto failure;
+                               } else {
+second_try:
+                                       if ((old_data = zend_hash_find(ht, Z_STR(key))) != NULL) {
+                                               if (Z_TYPE_P(old_data) == IS_INDIRECT) {
+                                                       goto declared_property;
+                                               } else {
+                                                       var_push_dtor(var_hash, old_data);
+                                                       data = zend_hash_update(ht, Z_STR(key), &d);
                                                }
-                                               var_push_dtor(var_hash, old_data);
-                                               Z_TRY_DELREF_P(old_data);
-                                               ZVAL_COPY_VALUE(old_data, &d);
-                                               data = old_data;
                                        } else {
-                                               var_push_dtor(var_hash, old_data);
-                                               data = zend_hash_update_ind(ht, Z_STR(key), &d);
+                                               data = zend_hash_add_new(ht, Z_STR(key), &d);
                                        }
-                               } else {
-                                       data = zend_hash_add_new(ht, Z_STR(key), &d);
                                }
-                       } else if (Z_TYPE(key) == IS_LONG) {
-                               /* object properties should include no integers */
-                               convert_to_string(&key);
-                               goto string_key;
-                       } else {
-                               zval_ptr_dtor(&key);
-                               goto failure;
                        }
+                       zval_ptr_dtor_str(&key);
+               } else if (Z_TYPE(key) == IS_LONG) {
+                       /* object properties should include no integers */
+                       convert_to_string(&key);
+                       goto string_key;
+               } else {
+                       zval_ptr_dtor(&key);
+                       goto failure;
                }
 
                if (!php_var_unserialize_internal(data, p, max, var_hash, 0)) {
@@ -599,7 +676,6 @@ string_key:
                                 * The data is still stored in the property. */
                                ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info);
                        }
-                       zval_ptr_dtor(&key);
                        goto failure;
                }
 
@@ -607,7 +683,6 @@ string_key:
                        if (!zend_verify_prop_assignable_by_ref(info, data, /* strict */ 1)) {
                                zval_ptr_dtor(data);
                                ZVAL_UNDEF(data);
-                               zval_ptr_dtor_nogc(&key);
                                goto failure;
                        }
 
@@ -628,7 +703,6 @@ string_key:
                if (BG(unserialize).level > 1) {
                        var_push_dtor(var_hash, data);
                }
-               zval_ptr_dtor_str(&key);
 
                if (elements && *(*p-1) != ';' && *(*p-1) != '}') {
                        (*p)--;
@@ -707,7 +781,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool
                array_init_size(&ary, elements);
                /* Avoid reallocation due to packed -> mixed conversion. */
                zend_hash_real_init_mixed(Z_ARRVAL(ary));
-               if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements, NULL)) {
+               if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements)) {
                        ZVAL_DEREF(rval);
                        GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
                        zval_ptr_dtor(&ary);
@@ -735,7 +809,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool
        }
 
        zend_hash_extend(ht, zend_hash_num_elements(ht) + elements, HT_FLAGS(ht) & HASH_FLAG_PACKED);
-       if (!process_nested_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
+       if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) {
                if (has_wakeup) {
                        ZVAL_DEREF(rval);
                        GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED);
@@ -1025,7 +1099,7 @@ use_double:
         * prohibit "r:" references to non-objects, as we only generate them for objects. */
        HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(rval));
 
-       if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements, NULL)) {
+       if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL_P(rval), elements)) {
                return 0;
        }