From 824770675130360543604e03b9f828a0e41c91ad Mon Sep 17 00:00:00 2001 From: Xuyang Zhang <119476662+kn1g78@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:08:38 +0800 Subject: [PATCH 1/5] ext/intl: Pre-size arrays created from ICU enumerations (#22483) Use uenum_count() to pre-size arrays built from ICU enumerations. This avoids unnecessary HashTable growth in transliterator_list_ids(), resourcebundle_locales() and locale_get_keywords(). locale_get_keywords() also uses the keyword length reported by ICU when inserting the associative entry, with a fallback to strlen() for unknown lengths. If counting fails, the previous behavior is preserved by falling back to an unsized array initialization. --- UPGRADING | 4 ++++ ext/intl/resourcebundle/resourcebundle_class.cpp | 9 ++++++++- ext/intl/transliterator/transliterator_methods.cpp | 12 +++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/UPGRADING b/UPGRADING index 15c1aad15db0..0c5253267e39 100644 --- a/UPGRADING +++ b/UPGRADING @@ -500,6 +500,10 @@ PHP 8.6 UPGRADE NOTES . Improved performance of indentation generation in json_encode() when using PHP_JSON_PRETTY_PRINT. +- Intl: + . Improved performance of transliterator_list_ids() and + resourcebundle_locales() by pre-allocating their returned arrays. + - Phar: . Reduced temporary allocations when iterating Phar directories. diff --git a/ext/intl/resourcebundle/resourcebundle_class.cpp b/ext/intl/resourcebundle/resourcebundle_class.cpp index f796a6ffc8aa..7e22e9e8c7df 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.cpp +++ b/ext/intl/resourcebundle/resourcebundle_class.cpp @@ -339,6 +339,7 @@ U_CFUNC PHP_FUNCTION( resourcebundle_locales ) size_t bundlename_len = 0; const char * entry; int entry_len; + int32_t count; UEnumeration *icuenum; UErrorCode icuerror = U_ZERO_ERROR; @@ -364,7 +365,13 @@ U_CFUNC PHP_FUNCTION( resourcebundle_locales ) uenum_reset( icuenum, &icuerror ); INTL_CHECK_STATUS(icuerror, "Cannot iterate locales list"); - array_init( return_value ); + count = uenum_count( icuenum, &icuerror ); + if (U_FAILURE(icuerror)) { + count = 0; + icuerror = U_ZERO_ERROR; + } + + array_init_size( return_value, count ); while ((entry = uenum_next( icuenum, &entry_len, &icuerror ))) { add_next_index_stringl( return_value, (char *) entry, entry_len); } diff --git a/ext/intl/transliterator/transliterator_methods.cpp b/ext/intl/transliterator/transliterator_methods.cpp index 45dd00b42bcf..8efcff95b310 100644 --- a/ext/intl/transliterator/transliterator_methods.cpp +++ b/ext/intl/transliterator/transliterator_methods.cpp @@ -21,6 +21,8 @@ #include #endif +#include + extern "C" { #include "php_intl.h" #include "intl_data.h" @@ -226,6 +228,7 @@ U_CFUNC PHP_FUNCTION( transliterator_list_ids ) UEnumeration *en; const UChar *elem; int32_t elem_len; + int32_t count; UErrorCode status = U_ZERO_ERROR; intl_error_reset( nullptr ); @@ -236,7 +239,14 @@ U_CFUNC PHP_FUNCTION( transliterator_list_ids ) INTL_CHECK_STATUS( status, "Failed to obtain registered transliterators" ); - array_init( return_value ); + count = uenum_count( en, &status ); + if( U_FAILURE( status ) ) + { + count = 0; + status = U_ZERO_ERROR; + } + + array_init_size( return_value, count ); while( (elem = uenum_unext( en, &elem_len, &status )) ) { zend_string *el = intl_convert_utf16_to_utf8(elem, elem_len, &status ); From fe49107c8bfb51e33f4a456bb811e60c2257d2b1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 27 Jun 2026 11:55:32 +0100 Subject: [PATCH 2/5] ext/reflection: add const qualifiers (#22463) --- ext/reflection/php_reflection.c | 118 +++++++++++++++----------------- ext/reflection/php_reflection.h | 4 +- 2 files changed, 59 insertions(+), 63 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 6c50619a35d8..a9f6b579977e 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -295,8 +295,8 @@ static zend_object *reflection_objects_new(zend_class_entry *class_type) /* {{{ /* }}} */ static void _const_string(smart_str *str, const char *name, zval *value, const char *indent); -static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, const char* indent); -static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent); +static void _function_string(smart_str *str, const zend_function *fptr, const zend_class_entry *scope, const char* indent); +static void _property_string(smart_str *str, const zend_property_info *prop, const char *prop_name, const char* indent); static void _class_const_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent); static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent); static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent); @@ -699,7 +699,7 @@ static zend_op *get_recv_op(const zend_op_array *op_array, uint32_t offset) return NULL; } -static zval *get_default_from_recv(zend_op_array *op_array, uint32_t offset) { +static zval *get_default_from_recv(const zend_op_array *op_array, uint32_t offset) { zend_op *recv = get_recv_op(op_array, offset); if (!recv || recv->opcode != ZEND_RECV_INIT) { return NULL; @@ -708,7 +708,7 @@ static zval *get_default_from_recv(zend_op_array *op_array, uint32_t offset) { return RT_CONSTANT(recv, recv->op2); } -static void format_default_value(smart_str *str, zval *value) { +static void format_default_value(smart_str *str, const zval *value) { if (smart_str_append_zval(str, value, SIZE_MAX) == SUCCESS) { /* Nothing to do. */ } else if (Z_TYPE_P(value) == IS_ARRAY) { @@ -741,8 +741,8 @@ static void format_default_value(smart_str *str, zval *value) { /* This branch is reached if the constant AST was already evaluated and * resulted in an object; enums are already handled in smart_str_append_zval() * (GH-15902) */ - zend_object *obj = Z_OBJ_P(value); - zend_class_entry *class = obj->ce; + const zend_object *obj = Z_OBJ_P(value); + const zend_class_entry *class = obj->ce; ZEND_ASSERT(!(class->ce_flags & ZEND_ACC_ENUM)); smart_str_appends(str, "object("); smart_str_append(str, class->name); @@ -756,7 +756,7 @@ static void format_default_value(smart_str *str, zval *value) { } /* {{{ _parameter_string */ -static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_arg_info *arg_info, uint32_t offset, bool required, char* indent) +static void _parameter_string(smart_str *str, const zend_function *fptr, const struct _zend_arg_info *arg_info, uint32_t offset, bool required, char* indent) { smart_str_append_printf(str, "Parameter #%d [ ", offset); if (!required) { @@ -789,7 +789,7 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_ smart_str_appends(str, ""); } } else { - zval *default_value = get_default_from_recv((zend_op_array*)fptr, offset); + const zval *default_value = get_default_from_recv((const zend_op_array*)fptr, offset); if (default_value) { smart_str_appends(str, " = "); format_default_value(str, default_value); @@ -801,7 +801,7 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_ /* }}} */ /* {{{ _function_parameter_string */ -static void _function_parameter_string(smart_str *str, zend_function *fptr, char* indent) +static void _function_parameter_string(smart_str *str, const zend_function *fptr, char* indent) { struct _zend_arg_info *arg_info = fptr->common.arg_info; uint32_t i, num_args, num_required = fptr->common.required_num_args; @@ -855,7 +855,7 @@ static void _function_closure_string(smart_str *str, const zend_function *fptr, /* }}} */ /* {{{ _function_string */ -static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, const char* indent) +static void _function_string(smart_str *str, const zend_function *fptr, const zend_class_entry *scope, const char* indent) { smart_str param_indent = {0}; zend_function *overwrites; @@ -877,8 +877,8 @@ static void _function_string(smart_str *str, zend_function *fptr, zend_class_ent if (fptr->common.fn_flags & ZEND_ACC_DEPRECATED) { smart_str_appends(str, ", deprecated"); } - if (fptr->type == ZEND_INTERNAL_FUNCTION && ((zend_internal_function*)fptr)->module) { - smart_str_append_printf(str, ":%s", ((zend_internal_function*)fptr)->module->name); + if (fptr->type == ZEND_INTERNAL_FUNCTION && ((const zend_internal_function*)fptr)->module) { + smart_str_append_printf(str, ":%s", ((const zend_internal_function*)fptr)->module->name); } if (scope && fptr->common.scope) { @@ -964,8 +964,8 @@ static void _function_string(smart_str *str, zend_function *fptr, zend_class_ent } /* }}} */ -static zval *property_get_default(zend_property_info *prop_info) { - zend_class_entry *ce = prop_info->ce; +static zval *property_get_default(const zend_property_info *prop_info) { + const zend_class_entry *ce = prop_info->ce; if (prop_info->flags & ZEND_ACC_STATIC) { zval *prop = &ce->default_static_members_table[prop_info->offset]; ZVAL_DEINDIRECT(prop); @@ -978,7 +978,7 @@ static zval *property_get_default(zend_property_info *prop_info) { } /* {{{ _property_string */ -static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent) +static void _property_string(smart_str *str, const zend_property_info *prop, const char *prop_name, const char* indent) { if (prop && prop->doc_comment) { smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(prop->doc_comment)); @@ -1037,7 +1037,7 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha } smart_str_append_printf(str, "$%s", prop_name); - zval *default_value = property_get_default(prop); + const zval *default_value = property_get_default(prop); if (default_value && !Z_ISUNDEF_P(default_value)) { smart_str_appends(str, " = "); format_default_value(str, default_value); @@ -1259,7 +1259,7 @@ static void reflection_attribute_factory(zval *object, HashTable *attributes, ze /* }}} */ static zend_result read_attributes(zval *ret, HashTable *attributes, zend_class_entry *scope, - uint32_t offset, uint32_t target, zend_string *name, zend_class_entry *base, zend_string *filename) /* {{{ */ + uint32_t offset, uint32_t target, zend_string *name, const zend_class_entry *base, zend_string *filename) /* {{{ */ { ZEND_ASSERT(attributes != NULL); @@ -1288,7 +1288,7 @@ static zend_result read_attributes(zval *ret, HashTable *attributes, zend_class_ if (base) { // Base type filtering. - zend_class_entry *ce = zend_lookup_class_ex(attr->name, attr->lcname, 0); + const zend_class_entry *ce = zend_lookup_class_ex(attr->name, attr->lcname, 0); if (ce == NULL) { // Bailout on error, otherwise ignore unavailable class. @@ -1317,7 +1317,7 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut { zend_string *name = NULL; zend_long flags = 0; - zend_class_entry *base = NULL; + const zend_class_entry *base = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!l", &name, &flags) == FAILURE) { RETURN_THROWS(); @@ -1611,7 +1611,7 @@ static void reflection_class_constant_factory(zend_string *name_str, zend_class_ } /* }}} */ -static void reflection_enum_case_factory(zend_class_entry *ce, zend_string *name_str, zend_class_constant *constant, zval *object) +static void reflection_enum_case_factory(const zend_class_entry *ce, zend_string *name_str, zend_class_constant *constant, zval *object) { reflection_object *intern; @@ -1628,7 +1628,7 @@ static void reflection_enum_case_factory(zend_class_entry *ce, zend_string *name ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name); } -static zend_result get_parameter_default(zval *result, parameter_reference *param) { +static zend_result get_parameter_default(zval *result, const parameter_reference *param) { if (param->fptr->type == ZEND_INTERNAL_FUNCTION) { if (param->fptr->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { /* We don't have a way to determine the default value for this case right now. */ @@ -1636,7 +1636,7 @@ static zend_result get_parameter_default(zval *result, parameter_reference *para } return zend_get_default_from_internal_arg_info(result, param->arg_info); } else { - zval *default_value = get_default_from_recv((zend_op_array *) param->fptr, param->offset); + zval *default_value = get_default_from_recv((const zend_op_array *) param->fptr, param->offset); if (!default_value) { return FAILURE; } @@ -2937,7 +2937,7 @@ ZEND_METHOD(ReflectionParameter, isDefaultValueAvailable) if (param->fptr->type == ZEND_INTERNAL_FUNCTION) { RETURN_BOOL(param->arg_info->default_value); } else { - zval *default_value = get_default_from_recv((zend_op_array *)param->fptr, param->offset); + const zval *default_value = get_default_from_recv((const zend_op_array *)param->fptr, param->offset); RETURN_BOOL(default_value != NULL); } } @@ -2983,7 +2983,7 @@ ZEND_METHOD(ReflectionParameter, isDefaultValueConstant) } if (Z_TYPE(default_value) == IS_CONSTANT_AST) { - zend_ast *ast = Z_ASTVAL(default_value); + const zend_ast *ast = Z_ASTVAL(default_value); RETVAL_BOOL(ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_CONSTANT_CLASS || ast->kind == ZEND_AST_CLASS_CONST); @@ -3023,8 +3023,8 @@ ZEND_METHOD(ReflectionParameter, getDefaultValueConstantName) } else if (ast->kind == ZEND_AST_CONSTANT_CLASS) { RETVAL_STRINGL("__CLASS__", sizeof("__CLASS__")-1); } else if (ast->kind == ZEND_AST_CLASS_CONST) { - zend_string *class_name = zend_ast_get_str(ast->child[0]); - zend_string *const_name = zend_ast_get_str(ast->child[1]); + const zend_string *class_name = zend_ast_get_str(ast->child[0]); + const zend_string *const_name = zend_ast_get_str(ast->child[1]); RETVAL_NEW_STR(zend_string_concat3( ZSTR_VAL(class_name), ZSTR_LEN(class_name), "::", sizeof("::")-1, @@ -3595,7 +3595,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, inNamespace) RETURN_FALSE; } - zend_string *name = fptr->common.function_name; + const zend_string *name = fptr->common.function_name; const char *backslash = zend_memrchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); RETURN_BOOL(backslash); } @@ -3615,7 +3615,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getNamespaceName) RETURN_EMPTY_STRING(); } - zend_string *name = fptr->common.function_name; + const zend_string *name = fptr->common.function_name; const char *backslash = zend_memrchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); if (backslash) { RETURN_STRINGL(ZSTR_VAL(name), backslash - ZSTR_VAL(name)); @@ -3986,7 +3986,7 @@ ZEND_METHOD(ReflectionClassConstant, getValue) GET_REFLECTION_OBJECT_PTR(ref); - zval *name = reflection_prop_name(ZEND_THIS); + const zval *name = reflection_prop_name(ZEND_THIS); if (Z_ISUNDEF_P(name)) { zend_throw_error(NULL, "Typed property ReflectionClassConstant::$name " @@ -5626,7 +5626,7 @@ ZEND_METHOD(ReflectionClass, inNamespace) GET_REFLECTION_OBJECT_PTR(ce); - zend_string *name = ce->name; + const zend_string *name = ce->name; const char *backslash = zend_memrchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); RETURN_BOOL(backslash); } @@ -5642,7 +5642,7 @@ ZEND_METHOD(ReflectionClass, getNamespaceName) GET_REFLECTION_OBJECT_PTR(ce); - zend_string *name = ce->name; + const zend_string *name = ce->name; const char *backslash = zend_memrchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name)); if (backslash) { RETURN_STRINGL(ZSTR_VAL(name), backslash - ZSTR_VAL(name)); @@ -6017,7 +6017,7 @@ ZEND_METHOD(ReflectionProperty, setValue) * The effective prop may add hooks or change flags. */ static zend_property_info *reflection_property_get_effective_prop( zend_property_info *prop, zend_string *unmangled_name, - zend_class_entry *scope, zend_object *object) { + const zend_class_entry *scope, const zend_object *object) { if (scope != object->ce && !(prop && (prop->flags & ZEND_ACC_PRIVATE))) { prop = zend_hash_find_ptr(&object->ce->properties_info, unmangled_name); } @@ -6052,7 +6052,7 @@ ZEND_METHOD(ReflectionProperty, getRawValue) } } - zend_property_info *prop = reflection_property_get_effective_prop(ref->prop, + const zend_property_info *prop = reflection_property_get_effective_prop(ref->prop, ref->unmangled_name, intern->ce, Z_OBJ_P(object)); if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) { @@ -6085,7 +6085,7 @@ ZEND_METHOD(ReflectionProperty, getRawValue) static void zend_reflection_property_set_raw_value_ex(zend_property_info *prop, zend_string *unmangled_name, void *cache_slot[3], - zend_class_entry *scope, zend_object *object, zval *value) + const zend_class_entry *scope, zend_object *object, zval *value) { ZEND_ASSERT(!prop || !(prop->flags & ZEND_ACC_STATIC)); @@ -6102,7 +6102,7 @@ static void zend_reflection_property_set_raw_value_ex(zend_property_info *prop, PHPAPI void zend_reflection_property_set_raw_value(zend_property_info *prop, zend_string *unmangled_name, void *cache_slot[3], - zend_class_entry *scope, zend_object *object, zval *value) + const zend_class_entry *scope, zend_object *object, zval *value) { prop = reflection_property_get_effective_prop(prop, unmangled_name, scope, object); @@ -6135,8 +6135,8 @@ ZEND_METHOD(ReflectionProperty, setRawValue) } static zend_result reflection_property_check_lazy_compatible( - zend_property_info *prop, zend_string *unmangled_name, - zend_class_entry *scope, zend_object *object, const char *method) + const zend_property_info *prop, zend_string *unmangled_name, + const zend_class_entry *scope, const zend_object *object, const char *method) { if (!prop) { zend_throw_exception_ex(reflection_exception_ptr, 0, @@ -6178,7 +6178,7 @@ static zend_result reflection_property_check_lazy_compatible( PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization( zend_property_info *prop, zend_string *unmangled_name, - void *cache_slot[3], zend_class_entry *scope, + void *cache_slot[3], const zend_class_entry *scope, zend_object *object, zval *value) { while (zend_object_is_lazy_proxy(object) @@ -6265,7 +6265,7 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization) object = zend_lazy_object_get_instance(object); } - zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(ref->prop->offset)]; + const zval *src = &object->ce->default_properties_table[OBJ_PROP_TO_NUM(ref->prop->offset)]; zval *dst = OBJ_PROP(object, ref->prop->offset); if (!(Z_PROP_FLAG_P(dst) & IS_PROP_LAZY)) { @@ -6316,7 +6316,6 @@ ZEND_METHOD(ReflectionProperty, isInitialized) reflection_object *intern; property_reference *ref; zval *object = NULL; - zval *member_p = NULL; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL @@ -6326,7 +6325,7 @@ ZEND_METHOD(ReflectionProperty, isInitialized) GET_REFLECTION_OBJECT_PTR(ref); if (prop_get_flags(ref) & ZEND_ACC_STATIC) { - member_p = zend_read_static_property_ex(intern->ce, ref->unmangled_name, 1); + const zval *member_p = zend_read_static_property_ex(intern->ce, ref->unmangled_name, 1); if (member_p) { RETURN_BOOL(!Z_ISUNDEF_P(member_p)); } @@ -6451,7 +6450,7 @@ ZEND_METHOD(ReflectionProperty, getSettableType) GET_REFLECTION_OBJECT_PTR(ref); - zend_property_info *prop = ref->prop; + const zend_property_info *prop = ref->prop; /* Dynamic property is untyped. */ if (!ref->prop) { RETURN_NULL(); @@ -6466,7 +6465,7 @@ ZEND_METHOD(ReflectionProperty, getSettableType) /* Extract set $value parameter type. */ if (prop->hooks && prop->hooks[ZEND_PROPERTY_HOOK_SET]) { - zend_arg_info *arg_info = &prop->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0]; + const zend_arg_info *arg_info = &prop->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0]; if (!ZEND_TYPE_IS_SET(arg_info->type)) { RETURN_NULL(); } @@ -6500,20 +6499,18 @@ ZEND_METHOD(ReflectionProperty, hasDefaultValue) { reflection_object *intern; property_reference *ref; - zend_property_info *prop_info; - zval *prop; ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(ref); - prop_info = ref->prop; + const zend_property_info *prop_info = ref->prop; if (prop_info == NULL) { RETURN_FALSE; } - prop = property_get_default(prop_info); + const zval *prop = property_get_default(prop_info); RETURN_BOOL(prop && !Z_ISUNDEF_P(prop)); } /* }}} */ @@ -6523,14 +6520,13 @@ ZEND_METHOD(ReflectionProperty, getDefaultValue) { reflection_object *intern; property_reference *ref; - zend_property_info *prop_info; zval *prop; ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(ref); - prop_info = ref->prop; + const zend_property_info *prop_info = ref->prop; if (prop_info == NULL) { // Dynamic property @@ -6694,7 +6690,7 @@ static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_vis } } -static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope) +static bool check_visibility(uint32_t visibility, const zend_class_entry *ce, zend_class_entry *scope) { if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) { if (!scope) { @@ -6852,7 +6848,7 @@ ZEND_METHOD(ReflectionProperty, isWritable) ref->unmangled_name, intern->ce, obj); } - zend_class_entry *ce = obj ? obj->ce : intern->ce; + const zend_class_entry *ce = obj ? obj->ce : intern->ce; if (!prop) { if (!(ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) { RETURN_TRUE; @@ -7026,7 +7022,7 @@ ZEND_METHOD(ReflectionExtension, getConstants) /* }}} */ /* {{{ _addinientry */ -static void _addinientry(zend_ini_entry *ini_entry, zval *retval, int number) +static void _addinientry(const zend_ini_entry *ini_entry, const zval *retval, int number) { if (number == ini_entry->module_number) { zval zv; @@ -7058,7 +7054,7 @@ ZEND_METHOD(ReflectionExtension, getINIEntries) /* }}} */ /* {{{ add_extension_class */ -static void add_extension_class(zend_class_entry *ce, zend_string *key, zval *class_array, zend_module_entry *module, bool add_reflection_class) +static void add_extension_class(zend_class_entry *ce, zend_string *key, zval *class_array, const zend_module_entry *module, bool add_reflection_class) { if (ce->type == ZEND_INTERNAL_CLASS && ce->info.internal.module && !strcasecmp(ce->info.internal.module->name, module->name)) { zend_string *name; @@ -7352,7 +7348,7 @@ ZEND_METHOD(ReflectionReference, __construct) } /* }}} */ -static bool is_ignorable_reference(HashTable *ht, zval *ref) { +static bool is_ignorable_reference(const HashTable *ht, const zval *ref) { if (Z_REFCOUNT_P(ref) != 1) { return false; } @@ -7676,7 +7672,7 @@ ZEND_METHOD(ReflectionEnum, hasCase) GET_REFLECTION_OBJECT_PTR(ce); - zend_class_constant *class_const = zend_hash_find_ptr(&ce->constants_table, name); + const zend_class_constant *class_const = zend_hash_find_ptr(&ce->constants_table, name); if (class_const == NULL) { RETURN_FALSE; } @@ -7771,7 +7767,7 @@ ZEND_METHOD(ReflectionEnumUnitCase, __construct) GET_REFLECTION_OBJECT_PTR(ref); if (!(ZEND_CLASS_CONST_FLAGS(ref) & ZEND_CLASS_CONST_IS_CASE)) { - zval *case_name = reflection_prop_name(ZEND_THIS); + const zval *case_name = reflection_prop_name(ZEND_THIS); zend_throw_exception_ex(reflection_exception_ptr, 0, "Constant %s::%s is not a case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); RETURN_THROWS(); } @@ -7801,7 +7797,7 @@ ZEND_METHOD(ReflectionEnumBackedCase, __construct) GET_REFLECTION_OBJECT_PTR(ref); if (ref->ce->enum_backing_type == IS_UNDEF) { - zval *case_name = reflection_prop_name(ZEND_THIS); + const zval *case_name = reflection_prop_name(ZEND_THIS); zend_throw_exception_ex(reflection_exception_ptr, 0, "Enum case %s::%s is not a backed case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); RETURN_THROWS(); } @@ -7823,7 +7819,7 @@ ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue) } ZEND_ASSERT(intern->ce->enum_backing_type != IS_UNDEF); - zval *member_p = zend_enum_fetch_case_value(Z_OBJ(ref->value)); + const zval *member_p = zend_enum_fetch_case_value(Z_OBJ(ref->value)); ZVAL_COPY_OR_DUP(return_value, member_p); } @@ -7894,7 +7890,7 @@ ZEND_METHOD(ReflectionFiber, getTrace) ZEND_METHOD(ReflectionFiber, getExecutingLine) { - zend_fiber *fiber = (zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); + const zend_fiber *fiber = (const zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); zend_execute_data *prev_execute_data; ZEND_PARSE_PARAMETERS_NONE(); @@ -7918,7 +7914,7 @@ ZEND_METHOD(ReflectionFiber, getExecutingLine) ZEND_METHOD(ReflectionFiber, getExecutingFile) { - zend_fiber *fiber = (zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); + const zend_fiber *fiber = (const zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); zend_execute_data *prev_execute_data; ZEND_PARSE_PARAMETERS_NONE(); @@ -7942,7 +7938,7 @@ ZEND_METHOD(ReflectionFiber, getExecutingFile) ZEND_METHOD(ReflectionFiber, getCallable) { - zend_fiber *fiber = (zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); + const zend_fiber *fiber = (const zend_fiber *) Z_OBJ(Z_REFLECTION_P(ZEND_THIS)->obj); ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index 51f48b8039cc..93b53de74133 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -62,13 +62,13 @@ PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object); */ PHPAPI void zend_reflection_property_set_raw_value( zend_property_info *prop, zend_string *unmangled_name, - void *cache_slot[3], zend_class_entry *scope, + void *cache_slot[3], const zend_class_entry *scope, zend_object *object, zval *value); /* Same as zend_reflection_property_set_raw_value(), but skips lazy object initialization. */ PHPAPI void zend_reflection_property_set_raw_value_without_lazy_initialization( zend_property_info *prop, zend_string *unmangled_name, - void *cache_slot[3], zend_class_entry *scope, + void *cache_slot[3], const zend_class_entry *scope, zend_object *object, zval *value); END_EXTERN_C() From 1cbd3b9ba90c7296c10bae76a7cd7287ddc9ada3 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 26 Jun 2026 08:15:04 -0400 Subject: [PATCH 3/5] Fix use-after-free in RecursiveIteratorIterator on reentry move_forward_ex() caches the active sub-iterator, then calls the inner iterator's move_forward(), which can re-enter userland. A next() that rewinds or advances the RecursiveIteratorIterator frees that sub-iterator, and the following validity check then reads freed memory. Re-fetch the sub-iterator after the call, the same way the no-more-elements branch already re-checks the level after endChildren(). Closes GH-22466 --- ext/spl/spl_iterators.c | 1 + ...veiteratoriterator_rewind_during_next.phpt | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c index db100d228341..eb068a50c91f 100644 --- a/ext/spl/spl_iterators.c +++ b/ext/spl/spl_iterators.c @@ -273,6 +273,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv zend_clear_exception(); } } + iterator = object->iterators[object->level].iterator; ZEND_FALLTHROUGH; case RS_START: if (iterator->funcs->valid(iterator) == FAILURE) { diff --git a/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt b/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt new file mode 100644 index 000000000000..3c9863e81a30 --- /dev/null +++ b/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt @@ -0,0 +1,41 @@ +--TEST-- +RecursiveIteratorIterator: rewind() re-entered from an inner next() must not use-after-free +--FILE-- +data = $d; $this->depth = $depth; } + function current(): mixed { return $this->data[$this->pos] ?? null; } + function key(): mixed { return $this->pos; } + function next(): void { + $this->pos++; + if ($this->rii && $this->depth === 1 && $this->pos === 1 && !self::$fired) { + self::$fired = true; + $this->rii->rewind(); + } + } + function rewind(): void { $this->pos = 0; } + function valid(): bool { return $this->pos < count($this->data); } + function hasChildren(): bool { return is_array($this->current()); } + function getChildren(): RecursiveIterator { + $c = new Reenter($this->current(), $this->depth + 1); + $c->rii = $this->rii; + return $c; + } +} +$root = new Reenter([[10, 11], [20, 21]]); +$rii = new RecursiveIteratorIterator($root, RecursiveIteratorIterator::SELF_FIRST); +$root->rii = $rii; +$seen = []; +foreach ($rii as $v) { + if (is_array($v)) { $v = '[' . implode(',', $v) . ']'; } + $seen[] = $v; + if (count($seen) > 20) { $seen[] = '...'; break; } +} +echo implode(' ', $seen), "\n"; +echo "done\n"; +?> +--EXPECT-- +[10,11] 10 [10,11] 10 11 [20,21] 20 21 +done From f9821dd1dda7941a4528aef0529b2a454e6da792 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 26 Jun 2026 08:15:14 -0400 Subject: [PATCH 4/5] Fix use-after-free in XPath php:function argument nodes A php:function() callback receives DOM node arguments as proxies that own the underlying libxml node. If the callback detaches such a node, the argument cleanup frees it while libxml is still evaluating the expression and still references it in the result node-set. Keep node and node-set argument proxies alive until evaluation ends, as returned nodes already are. Closes GH-22468 --- ...th_php_function_removes_argument_node.phpt | 33 +++++++++++++++++++ ext/dom/xpath_callbacks.c | 16 ++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 ext/dom/tests/xpath_php_function_removes_argument_node.phpt diff --git a/ext/dom/tests/xpath_php_function_removes_argument_node.phpt b/ext/dom/tests/xpath_php_function_removes_argument_node.phpt new file mode 100644 index 000000000000..e6696617be25 --- /dev/null +++ b/ext/dom/tests/xpath_php_function_removes_argument_node.phpt @@ -0,0 +1,33 @@ +--TEST-- +DOMXPath: a php:function callback that removes its argument node must not free it mid-evaluation +--EXTENSIONS-- +dom +--FILE-- +loadXML('1234'); +$xp = new DOMXPath($doc); +$xp->registerNamespace('php', 'http://php.net/xpath'); +$xp->registerPhpFunctions(); + +function cb($nodes) { + foreach ($nodes as $n) { + if ($n->parentNode) { + $n->parentNode->removeChild($n); + } + } + return true; +} + +$res = $xp->query('//a[php:function("cb", .)]'); +foreach ($res as $r) { + var_dump($r->nodeName); +} +echo "done\n"; +?> +--EXPECT-- +string(1) "a" +string(1) "a" +string(1) "a" +string(1) "a" +done diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c index 28a5272a302a..53e8f3443149 100644 --- a/ext/dom/xpath_callbacks.c +++ b/ext/dom/xpath_callbacks.c @@ -381,11 +381,19 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui return params; } -static void php_dom_xpath_callback_cleanup_args(zval *params, uint32_t param_count) +static void php_dom_xpath_callback_cleanup_args(php_dom_xpath_callbacks *xpath_callbacks, zval *params, uint32_t param_count) { if (params) { for (uint32_t i = 0; i < param_count; i++) { - zval_ptr_dtor(¶ms[i]); + zval *param = ¶ms[i]; + if (Z_TYPE_P(param) == IS_OBJECT || Z_TYPE_P(param) == IS_ARRAY) { + if (xpath_callbacks->node_list == NULL) { + xpath_callbacks->node_list = zend_new_array(0); + } + zend_hash_next_index_insert_new(xpath_callbacks->node_list, param); + } else { + zval_ptr_dtor(param); + } } efree(params); } @@ -483,7 +491,7 @@ PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_php_ns(php_dom_xpath_cal cleanup: xmlXPathFreeObject(obj); - php_dom_xpath_callback_cleanup_args(params, param_count); + php_dom_xpath_callback_cleanup_args(xpath_callbacks, params, param_count); cleanup_no_obj: if (UNEXPECTED(result != SUCCESS)) { /* Push sentinel value */ @@ -511,7 +519,7 @@ PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_custom_ns(php_dom_xpath_ zend_result result = php_dom_xpath_callback_dispatch(xpath_callbacks, ns, ctxt, params, param_count, function_name, function_name_length); - php_dom_xpath_callback_cleanup_args(params, param_count); + php_dom_xpath_callback_cleanup_args(xpath_callbacks, params, param_count); if (UNEXPECTED(result != SUCCESS)) { /* Push sentinel value */ valuePush(ctxt, xmlXPathNewString((const xmlChar *) "")); From 9bc8b7eaf50021f3a4718ae456ec0e6ac4f7f261 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sat, 27 Jun 2026 21:31:21 +0800 Subject: [PATCH 5/5] [skip ci] Add several UPGRADING entries for performance improvements Intl: PR #22069 URI: PR #21560 Zip: PR #21572 --- UPGRADING | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/UPGRADING b/UPGRADING index 0c5253267e39..27a095288d33 100644 --- a/UPGRADING +++ b/UPGRADING @@ -501,6 +501,9 @@ PHP 8.6 UPGRADE NOTES when using PHP_JSON_PRETTY_PRINT. - Intl: + . Improved performance of IntlCalendar::getAvailableLocales() and + IntlDateFormatter::localtime() / datefmt_localtime() by pre-allocating + their returned arrays. . Improved performance of transliterator_list_ids() and resourcebundle_locales() by pre-allocating their returned arrays. @@ -518,10 +521,14 @@ PHP 8.6 UPGRADE NOTES . Improved performance of str_split(). - URI: + . Improved performance of Uri\WhatWg\Url::parse() when collecting + validation errors by pre-allocating the error array. . Reduced allocations when reading IPv6/IPFuture hosts and paths with Uri\Rfc3986\Uri. . Improved performance and memory consumption when using normalizing (non-raw) getters on already-normalized URIs with Uri\Rfc3986\Uri. - Zip: + . Improved performance of ZipArchive::addGlob() and + ZipArchive::addPattern() by pre-allocating their returned arrays. . Avoid string copies in ZipArchive::addFromString().