From 12c1f1c12f369a06259d239644cd0d3ce37dbe96 Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Tue, 7 Jul 2026 22:23:11 +0800 Subject: [PATCH 01/35] ext/calendar: Fix integer overflow in GregorianToSdn() and JulianToJd() (#22604) https://github.com/php/php-src/pull/22603 GregorianToSdn() accepted large positive years up to INT_MAX and then added 4800 before doing the SDN calculation, which triggered signed integer overflow. JulianToSdn() used the same adjustment and had the same overflow surface. Reject years that cannot be adjusted safely before the calculation. Add coverage for the reported gregoriantojd() case and the matching juliantojd() case with boundary values. Closes #22602 Closes #22604 --- NEWS | 4 ++++ ext/calendar/gregor.c | 1 + ext/calendar/julian.c | 1 + ext/calendar/tests/gh22602.phpt | 20 ++++++++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 ext/calendar/tests/gh22602.phpt diff --git a/NEWS b/NEWS index 41fc6d053bc2..5f615701b4ab 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.24 +- Calendar: + . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with + INT_MAX year). (arshidkv12) + - DBA: . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz) diff --git a/ext/calendar/gregor.c b/ext/calendar/gregor.c index 3aef7ae6ac50..eaee9c8c73c7 100644 --- a/ext/calendar/gregor.c +++ b/ext/calendar/gregor.c @@ -209,6 +209,7 @@ zend_long GregorianToSdn( /* check for invalid dates */ if (inputYear == 0 || inputYear < -4714 || + inputYear > INT_MAX - 4800 || inputMonth <= 0 || inputMonth > 12 || inputDay <= 0 || inputDay > 31) { return (0); diff --git a/ext/calendar/julian.c b/ext/calendar/julian.c index ac580aa08e06..53a207a606f4 100644 --- a/ext/calendar/julian.c +++ b/ext/calendar/julian.c @@ -222,6 +222,7 @@ zend_long JulianToSdn( /* check for invalid dates */ if (inputYear == 0 || inputYear < -4713 || + inputYear > INT_MAX - 4800 || inputMonth <= 0 || inputMonth > 12 || inputDay <= 0 || inputDay > 31) { return (0); diff --git a/ext/calendar/tests/gh22602.phpt b/ext/calendar/tests/gh22602.phpt new file mode 100644 index 000000000000..d165a0174094 --- /dev/null +++ b/ext/calendar/tests/gh22602.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug GH-22602: (gregoriantojd() and juliantojd() integer overflow with INT_MAX year) +--EXTENSIONS-- +calendar +--FILE-- + 2147483647 ? 2147483647 : PHP_INT_MAX; +$min = PHP_INT_MAX > 2147483647 ? -2147483648 : PHP_INT_MIN; + +var_dump(gregoriantojd(5, 5, $max)); +var_dump(gregoriantojd(5, 5, $min)); +var_dump(juliantojd(5, 5, 2147483647)); +var_dump(juliantojd(5, 5, -2147483647)); + +?> +--EXPECT-- +int(0) +int(0) +int(0) +int(0) From dd86a8ab58c32a563047861496b420f87277e9b8 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:44:29 -0700 Subject: [PATCH 02/35] Implement `ReflectionClass::isAnonymous()` using `_class_check_flag()` --- ext/reflection/php_reflection.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 37c45cb02168..d611b5d6bc11 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4362,15 +4362,22 @@ ZEND_METHOD(ReflectionClass, isUserDefined) } /* }}} */ -/* {{{ Returns whether this class is anonymous */ -ZEND_METHOD(ReflectionClass, isAnonymous) +/* {{{ _class_check_flag */ +static void _class_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) { reflection_object *intern; zend_class_entry *ce; ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(ce); - RETURN_BOOL(ce->ce_flags & ZEND_ACC_ANON_CLASS); + RETVAL_BOOL(ce->ce_flags & mask); +} +/* }}} */ + +/* {{{ Returns whether this class is anonymous */ +ZEND_METHOD(ReflectionClass, isAnonymous) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ANON_CLASS); } /* }}} */ @@ -4880,18 +4887,6 @@ ZEND_METHOD(ReflectionClass, getReflectionConstant) } /* }}} */ -/* {{{ _class_check_flag */ -static void _class_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) -{ - reflection_object *intern; - zend_class_entry *ce; - - ZEND_PARSE_PARAMETERS_NONE(); - GET_REFLECTION_OBJECT_PTR(ce); - RETVAL_BOOL(ce->ce_flags & mask); -} -/* }}} */ - /* {{{ Returns whether this class is instantiable */ ZEND_METHOD(ReflectionClass, isInstantiable) { From ed27dbcdc83fd3a66e0af583c2e114ead3a1b620 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 17:05:54 -0700 Subject: [PATCH 03/35] Reflection: use `RETURN_BOOL` in `_class_check_flag()` --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index d611b5d6bc11..bfff775cc43e 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4370,7 +4370,7 @@ static void _class_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(ce); - RETVAL_BOOL(ce->ce_flags & mask); + RETURN_BOOL(ce->ce_flags & mask); } /* }}} */ From f98ebbf5423676fd65d4215e4bbc0994b5d1f5c6 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:46:34 -0700 Subject: [PATCH 04/35] Implement `ReflectionClassConstant::isEnumCase()` using existing helper Use the `_class_constant_check_flag()` helper function to simplify things --- ext/reflection/php_reflection.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bfff775cc43e..8e3113f7a6ec 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4045,12 +4045,7 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) ZEND_METHOD(ReflectionClassConstant, isEnumCase) { - reflection_object *intern; - zend_class_constant *ref; - - GET_REFLECTION_OBJECT_PTR(ref); - - RETURN_BOOL(ZEND_CLASS_CONST_FLAGS(ref) & ZEND_CLASS_CONST_IS_CASE); + _class_constant_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_CLASS_CONST_IS_CASE); } ZEND_METHOD(ReflectionClassConstant, isDeprecated) From 22f80dcf9932035bacbde7fc61712373ff72bdb0 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:47:41 -0700 Subject: [PATCH 05/35] Implement `ReflectionClassConstant::isDeprecated()` using existing helper Use the `_class_constant_check_flag()` helper function to simplify things --- ext/reflection/php_reflection.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8e3113f7a6ec..f25cd09e067f 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4050,14 +4050,7 @@ ZEND_METHOD(ReflectionClassConstant, isEnumCase) ZEND_METHOD(ReflectionClassConstant, isDeprecated) { - reflection_object *intern; - zend_constant *ref; - - ZEND_PARSE_PARAMETERS_NONE(); - - GET_REFLECTION_OBJECT_PTR(ref); - - RETURN_BOOL(ZEND_CLASS_CONST_FLAGS(ref) & ZEND_ACC_DEPRECATED); + _class_constant_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_DEPRECATED); } /* {{{ reflection_class_object_ctor */ From 0a858ea9f4f2ff5c280b0ac7e53d31282d5333b2 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:48:07 -0700 Subject: [PATCH 06/35] Fix inline documentation of `ReflectionProperty::getName()` --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index f25cd09e067f..0eb458fa9272 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5741,7 +5741,7 @@ ZEND_METHOD(ReflectionProperty, __toString) } /* }}} */ -/* {{{ Returns the class' name */ +/* {{{ Returns the property's name */ ZEND_METHOD(ReflectionProperty, getName) { reflection_object *intern; From 36a602b077f95d5dadd52e9fd0af0136c4c7e695 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:48:50 -0700 Subject: [PATCH 07/35] Update inline documentation of `ReflectionProperty::setAccessible()` Since PHP 8.1 ReflectionProperty has always allowed access to non-public properties and the method is a no-op. --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0eb458fa9272..6fdf038bd140 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6392,7 +6392,7 @@ ZEND_METHOD(ReflectionProperty, getAttributes) } /* }}} */ -/* {{{ Sets whether non-public properties can be requested */ +/* {{{ No-op; previously controlled whether non-public properties can be requested */ ZEND_METHOD(ReflectionProperty, setAccessible) { bool visible; From ff3947756473ed5c813018dd8d363944d34beec5 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:56:00 -0700 Subject: [PATCH 08/35] Fix inline documentation of `Reflection*::getModifiers()` methods Affected methods: * `ReflectionMethod::getModifiers()` * `ReflectionClassConstant::getModifiers()` * `ReflectionClass::getModifiers()` * `ReflectionProperty::getModifiers()` They were all documented bitfields of "access modifiers", however they all included modifiers unrelated to visibility/access. --- ext/reflection/php_reflection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 6fdf038bd140..be0446e2302a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3735,7 +3735,7 @@ ZEND_METHOD(ReflectionMethod, isDestructor) } /* }}} */ -/* {{{ Returns a bitfield of the access modifiers for this method */ +/* {{{ Returns a bitfield of the modifiers for this method */ ZEND_METHOD(ReflectionMethod, getModifiers) { reflection_object *intern; @@ -3959,7 +3959,7 @@ ZEND_METHOD(ReflectionClassConstant, isFinal) _class_constant_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); } -/* {{{ Returns a bitfield of the access modifiers for this constant */ +/* {{{ Returns a bitfield of the modifiers for this constant */ ZEND_METHOD(ReflectionClassConstant, getModifiers) { reflection_object *intern; @@ -4965,7 +4965,7 @@ ZEND_METHOD(ReflectionClass, isAbstract) } /* }}} */ -/* {{{ Returns a bitfield of the access modifiers for this class */ +/* {{{ Returns a bitfield of the modifiers for this class */ ZEND_METHOD(ReflectionClass, getModifiers) { reflection_object *intern; @@ -5865,7 +5865,7 @@ ZEND_METHOD(ReflectionProperty, isPromoted) } /* }}} */ -/* {{{ Returns a bitfield of the access modifiers for this property */ +/* {{{ Returns a bitfield of the modifiers for this property */ ZEND_METHOD(ReflectionProperty, getModifiers) { reflection_object *intern; From 4795c51aecfb2315ee2087ce29896c72e0c22c3b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:57:27 -0700 Subject: [PATCH 09/35] Fix inline documentation of `ReflectionType::allowsNull()` --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index be0446e2302a..37bbc40f08df 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3058,7 +3058,7 @@ ZEND_METHOD(ReflectionParameter, isPromoted) } /* }}} */ -/* {{{ Returns whether parameter MAY be null */ +/* {{{ Returns whether the type MAY be null */ ZEND_METHOD(ReflectionType, allowsNull) { reflection_object *intern; From 5dfab102bd6edb92050a363d282b9c5aa19e1d0f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 10:58:33 -0700 Subject: [PATCH 10/35] Fix inline documentation of `ReflectionMethod::getClosure()` --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 37bbc40f08df..5d5c8eaeb038 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3364,7 +3364,7 @@ ZEND_METHOD(ReflectionMethod, __toString) } /* }}} */ -/* {{{ Invokes the function */ +/* {{{ Returns a dynamically created closure for the function */ ZEND_METHOD(ReflectionMethod, getClosure) { reflection_object *intern; From 6a19160ca561c2ae11313d98105db3f4a0598276 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 11:03:32 -0700 Subject: [PATCH 11/35] Avoid unnecessary concatenation in `zend_named_reflection_type_to_string()` When a type corresponds to an iterator that may be null, the string representation is known ahead of time to be "?iterable". Use `ZSTR_INIT_LITERAL()` rather than building up a string with `zend_string_concat2()` from a "?" and the "iterable" known string. --- ext/reflection/php_reflection.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5d5c8eaeb038..8d409a127b81 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3074,11 +3074,10 @@ ZEND_METHOD(ReflectionType, allowsNull) /* For BC with iterable for named types */ static zend_string *zend_named_reflection_type_to_string(zend_type type) { if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) { - zend_string *iterable = ZSTR_KNOWN(ZEND_STR_ITERABLE); if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_NULL) { - return zend_string_concat2("?", strlen("?"), ZSTR_VAL(iterable), ZSTR_LEN(iterable)); + return ZSTR_INIT_LITERAL("?iterable", false); } - return iterable; + return ZSTR_KNOWN(ZEND_STR_ITERABLE); } return zend_type_to_string(type); } From f44ae1bedb4bd24c797b9082854e2f72cc994a5a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 11:25:32 -0700 Subject: [PATCH 12/35] Reflection: use `true` and `false` for booleans rather than `0`/`1` Update local variables and arguments to * `zend_read_static_property_ex()` * `zend_string_alloc()` * `zend_string_init()` * `zend_string_release_ex()` * `zend_verify_property_type()` * `zend_verify_ref_assignable_zval()` * Object handlers' get_closure() function * `RETURN_ZVAL` (macro) * `ZEND_TYPE_INIT` (macro) * `ZEND_TYPE_INIT_CODE` (macro) --- ext/reflection/php_reflection.c | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8d409a127b81..07a97fe55ad5 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -212,7 +212,7 @@ static void _free_function(zend_function *fptr) /* {{{ */ if (fptr && (fptr->internal_function.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_string_release_ex(fptr->internal_function.function_name, 0); + zend_string_release_ex(fptr->internal_function.function_name, false); zend_free_trampoline(fptr); } } @@ -220,7 +220,7 @@ static void _free_function(zend_function *fptr) /* {{{ */ static void reflection_free_property_reference(property_reference *reference) { - zend_string_release_ex(reference->unmangled_name, 0); + zend_string_release_ex(reference->unmangled_name, false); efree(reference); } @@ -567,7 +567,7 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const smart_str_append_printf(str, "%s }\n", indent); smart_str_append_printf(str, "%s}\n", indent); - zend_string_release_ex(sub_indent, 0); + zend_string_release_ex(sub_indent, false); } /* }}} */ @@ -1227,7 +1227,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c smart_str_append_printf(str, "%s }\n", indent); } smart_str_free(&str_classes); - zend_string_release_ex(sub_indent, 0); + zend_string_release_ex(sub_indent, false); } smart_str_append_printf(str, "%s}\n", indent); @@ -1587,7 +1587,7 @@ static void reflection_property_factory(zend_class_entry *ce, zend_string *name, static void reflection_property_factory_str(zend_class_entry *ce, const char *name_str, size_t name_len, zend_property_info *prop, zval *object) { - zend_string *name = zend_string_init(name_str, name_len, 0); + zend_string *name = zend_string_init(name_str, name_len, false); reflection_property_factory(ce, name, prop, object); zend_string_release(name); } @@ -1847,7 +1847,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureCalledClass) zend_function *closure_func; zend_object *object; if (Z_OBJ_HANDLER(intern->obj, get_closure) - && Z_OBJ_HANDLER(intern->obj, get_closure)(Z_OBJ(intern->obj), &called_scope, &closure_func, &object, 1) == SUCCESS + && Z_OBJ_HANDLER(intern->obj, get_closure)(Z_OBJ(intern->obj), &called_scope, &closure_func, &object, true) == SUCCESS && closure_func && (called_scope || closure_func->common.scope)) { zend_reflection_class_factory(called_scope ? (zend_class_entry *) called_scope : closure_func->common.scope, return_value); } @@ -2107,7 +2107,7 @@ ZEND_METHOD(ReflectionFunction, invoke) if (!Z_ISUNDEF(intern->obj)) { Z_OBJ_HT(intern->obj)->get_closure( - Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, 0); + Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, false); } zend_call_known_fcc(&fcc, &retval, num_args, params, named_params); @@ -2140,7 +2140,7 @@ ZEND_METHOD(ReflectionFunction, invokeArgs) if (!Z_ISUNDEF(intern->obj)) { Z_OBJ_HT(intern->obj)->get_closure( - Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, 0); + Z_OBJ(intern->obj), &fcc.called_scope, &fcc.function_handler, &fcc.object, false); } zend_call_known_fcc(&fcc, &retval, /* num_params */ 0, /* params */ NULL, params); @@ -2460,7 +2460,7 @@ ZEND_METHOD(ReflectionParameter, __construct) struct _zend_arg_info *arg_info; uint32_t num_args; zend_class_entry *ce = NULL; - bool is_closure = 0; + bool is_closure = false; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(reference) @@ -2554,7 +2554,7 @@ ZEND_METHOD(ReflectionParameter, __construct) if (instanceof_function(ce, zend_ce_closure)) { fptr = (zend_function *)zend_get_closure_method_def(Z_OBJ_P(reference)); Z_ADDREF_P(reference); - is_closure = 1; + is_closure = true; } else if ((fptr = zend_hash_find_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE))) == NULL) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Method %s::%s() does not exist", ZSTR_VAL(ce->name), ZEND_INVOKE_FUNC_NAME); @@ -2628,7 +2628,7 @@ ZEND_METHOD(ReflectionParameter, __construct) failure: if (fptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { - zend_string_release_ex(fptr->common.function_name, 0); + zend_string_release_ex(fptr->common.function_name, false); zend_free_trampoline(fptr); } if (is_closure) { @@ -3168,7 +3168,7 @@ ZEND_METHOD(ReflectionUnionType, getTypes) } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(param->type)) { zend_string *name = ZEND_TYPE_NAME(param->type); - append_type(return_value, (zend_type) ZEND_TYPE_INIT_CLASS(name, 0, 0)); + append_type(return_value, (zend_type) ZEND_TYPE_INIT_CLASS(name, false, 0)); } type_mask = ZEND_TYPE_PURE_MASK(param->type); @@ -3290,7 +3290,7 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_ } tmp_len = tmp - name; - class_name = zend_string_init(name, tmp_len, 0); + class_name = zend_string_init(name, tmp_len, false); method_name = tmp + 2; method_name_len = ZSTR_LEN(arg1_str) - tmp_len - 2; } @@ -4266,12 +4266,12 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue) zend_reference *ref = Z_REF_P(variable_ptr); variable_ptr = Z_REFVAL_P(variable_ptr); - if (!zend_verify_ref_assignable_zval(ref, value, 0)) { + if (!zend_verify_ref_assignable_zval(ref, value, false)) { return; } } - if (ZEND_TYPE_IS_SET(prop_info->type) && !zend_verify_property_type(prop_info, value, 0)) { + if (ZEND_TYPE_IS_SET(prop_info->type) && !zend_verify_property_type(prop_info, value, false)) { return; } @@ -4636,7 +4636,7 @@ ZEND_METHOD(ReflectionClass, getProperty) str_name = ZSTR_VAL(name); if ((tmp = strstr(ZSTR_VAL(name), "::")) != NULL) { classname_len = tmp - ZSTR_VAL(name); - classname = zend_string_init(ZSTR_VAL(name), classname_len, 0); + classname = zend_string_init(ZSTR_VAL(name), classname_len, false); str_name_len = ZSTR_LEN(name) - (classname_len + 2); str_name = tmp + 2; @@ -4645,10 +4645,10 @@ ZEND_METHOD(ReflectionClass, getProperty) if (!EG(exception)) { zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" does not exist", ZSTR_VAL(classname)); } - zend_string_release_ex(classname, 0); + zend_string_release_ex(classname, false); RETURN_THROWS(); } - zend_string_release_ex(classname, 0); + zend_string_release_ex(classname, false); if (!instanceof_function(ce, ce2)) { zend_throw_exception_ex(reflection_exception_ptr, -1, "Fully qualified property name %s::$%s does not specify a base class of %s", ZSTR_VAL(ce2->name), str_name, ZSTR_VAL(ce->name)); @@ -5299,7 +5299,7 @@ ZEND_METHOD(ReflectionClass, getLazyInitializer) RETURN_NULL(); } - RETURN_ZVAL(zend_lazy_object_get_initializer_zv(object), 1, 0); + RETURN_ZVAL(zend_lazy_object_get_initializer_zv(object), true, false); } /* }}} */ @@ -5437,11 +5437,11 @@ ZEND_METHOD(ReflectionClass, getTraitAliases) break; } } - zend_string_release_ex(lcname, 0); + zend_string_release_ex(lcname, false); ZEND_ASSERT(class_name != NULL); } - mname = zend_string_alloc(ZSTR_LEN(class_name) + ZSTR_LEN(cur_ref->method_name) + 2, 0); + mname = zend_string_alloc(ZSTR_LEN(class_name) + ZSTR_LEN(cur_ref->method_name) + 2, false); snprintf(ZSTR_VAL(mname), ZSTR_LEN(mname) + 1, "%s::%s", ZSTR_VAL(class_name), ZSTR_VAL(cur_ref->method_name)); add_assoc_str_ex(return_value, ZSTR_VAL(ce->trait_aliases[i]->alias), ZSTR_LEN(ce->trait_aliases[i]->alias), mname); } @@ -5894,7 +5894,7 @@ ZEND_METHOD(ReflectionProperty, getValue) 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, 0); + member_p = zend_read_static_property_ex(intern->ce, ref->unmangled_name, false); if (member_p) { RETURN_COPY_DEREF(member_p); } @@ -6304,7 +6304,7 @@ ZEND_METHOD(ReflectionProperty, isInitialized) GET_REFLECTION_OBJECT_PTR(ref); if (prop_get_flags(ref) & ZEND_ACC_STATIC) { - const zval *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, true); if (member_p) { RETURN_BOOL(!Z_ISUNDEF_P(member_p)); } @@ -6437,7 +6437,7 @@ ZEND_METHOD(ReflectionProperty, getSettableType) /* Get-only virtual property can never be written to. */ if (prop->hooks && (prop->flags & ZEND_ACC_VIRTUAL) && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) { - zend_type never_type = ZEND_TYPE_INIT_CODE(IS_NEVER, 0, 0); + zend_type never_type = ZEND_TYPE_INIT_CODE(IS_NEVER, false, 0); reflection_type_factory(never_type, return_value, true); return; } @@ -7142,7 +7142,7 @@ ZEND_METHOD(ReflectionExtension, getDependencies) len += strlen(dep->version) + 1; } - relation = zend_string_alloc(len, 0); + relation = zend_string_alloc(len, false); snprintf(ZSTR_VAL(relation), ZSTR_LEN(relation) + 1, "%s%s%s%s%s", rel_type, dep->rel ? " " : "", @@ -7728,7 +7728,7 @@ ZEND_METHOD(ReflectionEnum, getBackingType) if (ce->enum_backing_type == IS_UNDEF) { RETURN_NULL(); } else { - zend_type type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0); + zend_type type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, false, 0); reflection_type_factory(type, return_value, false); } } From 4f8f79893657f0205e0d7a4fa1610b9087d2f3f4 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 11:33:23 -0700 Subject: [PATCH 13/35] Reflection: convert some integers used for conditions to booleans The previous commit addressed places where `0` or `1` where used in places where a boolean was already expected, such a function arguments declared to be booleans. Now, address some inner implementation details of the reflection extension that use integers for conditions: * `dynam_prop` local variable in `ReflectionProperty::__construct()` * `first` local variable in `_extension_string()` * `is_object` parameter to `reflection_class_object_ctor()` * `variadic` parameter to `reflection_method_invoke()` --- ext/reflection/php_reflection.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 07a97fe55ad5..bcc0fc39d98a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1194,14 +1194,14 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c { zend_function *fptr; - int first = 1; + bool first = true; ZEND_HASH_MAP_FOREACH_PTR(CG(function_table), fptr) { if (fptr->common.type==ZEND_INTERNAL_FUNCTION && fptr->internal_function.module == module) { if (first) { smart_str_appends(str, "\n - Functions {\n"); - first = 0; + first = false; } _function_string(str, fptr, NULL, " "); } @@ -3402,7 +3402,7 @@ ZEND_METHOD(ReflectionMethod, getClosure) /* }}} */ /* {{{ reflection_method_invoke */ -static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic) +static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, bool variadic) { zval retval; zval *params = NULL, *object; @@ -3503,14 +3503,14 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic) /* {{{ Invokes the method. */ ZEND_METHOD(ReflectionMethod, invoke) { - reflection_method_invoke(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + reflection_method_invoke(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } /* }}} */ /* {{{ Invokes the function and pass its arguments as array. */ ZEND_METHOD(ReflectionMethod, invokeArgs) { - reflection_method_invoke(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + reflection_method_invoke(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } /* }}} */ @@ -4053,7 +4053,7 @@ ZEND_METHOD(ReflectionClassConstant, isDeprecated) } /* {{{ reflection_class_object_ctor */ -static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_object) +static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, bool is_object) { zval *object; zend_string *arg_class = NULL; @@ -4100,7 +4100,7 @@ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_ob /* {{{ Constructor. Takes a string or an instance as an argument */ ZEND_METHOD(ReflectionClass, __construct) { - reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } /* }}} */ @@ -5652,7 +5652,7 @@ ZEND_METHOD(ReflectionClass, getShortName) /* {{{ Constructor. Takes an instance as an argument */ ZEND_METHOD(ReflectionObject, __construct) { - reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } /* }}} */ @@ -5662,7 +5662,7 @@ ZEND_METHOD(ReflectionProperty, __construct) zend_string *classname_str; zend_object *classname_obj; zend_string *name; - int dynam_prop = 0; + bool dynam_prop = false; zval *object; reflection_object *intern; zend_class_entry *ce; @@ -5693,10 +5693,10 @@ ZEND_METHOD(ReflectionProperty, __construct) /* Check for dynamic properties */ if (property_info == NULL && classname_obj) { if (zend_hash_exists(classname_obj->handlers->get_properties(classname_obj), name)) { - dynam_prop = 1; + dynam_prop = true; } } - if (dynam_prop == 0) { + if (!dynam_prop) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Property %s::$%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name)); RETURN_THROWS(); } @@ -5706,7 +5706,7 @@ ZEND_METHOD(ReflectionProperty, __construct) zval_ptr_dtor(prop_name); ZVAL_STR_COPY(prop_name, name); /* Note: class name are always interned, no need to destroy them */ - if (dynam_prop == 0) { + if (!dynam_prop) { ZVAL_STR_COPY(reflection_prop_class(object), property_info->ce->name); } else { ZVAL_STR_COPY(reflection_prop_class(object), ce->name); @@ -7624,7 +7624,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance) ZEND_METHOD(ReflectionEnum, __construct) { - reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); if (EG(exception)) { RETURN_THROWS(); } From 5370fc8f1667f9022995a2652bd95066d6139a8f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 11:44:10 -0700 Subject: [PATCH 14/35] Reflection: remove unneeded indentation support from `_extension_string()` The only caller (`ReflectionExtension::__toString()`) always provided an empty string as the indentation; removing the indentation entirely simplifies the building of the string representation, including replacing some uses of `smart_str_append_printf()` with `smart_str_appends()`, which is more performant. The indentation levels passed to `_extension_ini_string()` and `_extension_class_string()` from within `_extension_string()` are kept for now and will be addressed in follow-up commits. --- ext/reflection/php_reflection.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bcc0fc39d98a..28e16602a77d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -300,7 +300,7 @@ static void _property_string(smart_str *str, const zend_property_info *prop, con 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); -static void _extension_string(smart_str *str, const zend_module_entry *module, const char *indent); +static void _extension_string(smart_str *str, const zend_module_entry *module); static void _zend_extension_string(smart_str *str, const zend_extension *extension, const char *indent); /* {{{ _class_string */ @@ -1110,9 +1110,9 @@ static void _extension_class_string(zend_class_entry *ce, zend_string *key, smar } /* }}} */ -static void _extension_string(smart_str *str, const zend_module_entry *module, const char *indent) /* {{{ */ +static void _extension_string(smart_str *str, const zend_module_entry *module) /* {{{ */ { - smart_str_append_printf(str, "%sExtension [ ", indent); + smart_str_appends(str, "Extension [ "); if (module->type == MODULE_PERSISTENT) { smart_str_appends(str, ""); } @@ -1129,7 +1129,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c smart_str_appends(str, "\n - Dependencies {\n"); while(dep->name) { - smart_str_append_printf(str, "%s Dependency [ %s (", indent, dep->name); + smart_str_append_printf(str, " Dependency [ %s (", dep->name); switch(dep->type) { case MODULE_DEP_REQUIRED: @@ -1155,19 +1155,19 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c smart_str_appends(str, ") ]\n"); dep++; } - smart_str_append_printf(str, "%s }\n", indent); + smart_str_appends(str, " }\n"); } { smart_str str_ini = {0}; zend_ini_entry *ini_entry; ZEND_HASH_MAP_FOREACH_PTR(EG(ini_directives), ini_entry) { - _extension_ini_string(ini_entry, &str_ini, indent, module->module_number); + _extension_ini_string(ini_entry, &str_ini, "", module->module_number); } ZEND_HASH_FOREACH_END(); if (smart_str_get_len(&str_ini) > 0) { smart_str_appends(str, "\n - INI {\n"); smart_str_append_smart_str(str, &str_ini); - smart_str_append_printf(str, "%s }\n", indent); + smart_str_appends(str, " }\n"); } smart_str_free(&str_ini); } @@ -1187,7 +1187,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c if (num_constants) { smart_str_append_printf(str, "\n - Constants [%d] {\n", num_constants); smart_str_append_smart_str(str, &str_constants); - smart_str_append_printf(str, "%s }\n", indent); + smart_str_appends(str, " }\n"); } smart_str_free(&str_constants); } @@ -1207,30 +1207,28 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c } } ZEND_HASH_FOREACH_END(); if (!first) { - smart_str_append_printf(str, "%s }\n", indent); + smart_str_appends(str, " }\n"); } } { - zend_string *sub_indent = strpprintf(0, "%s ", indent); smart_str str_classes = {0}; zend_string *key; zend_class_entry *ce; int num_classes = 0; ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(EG(class_table), key, ce) { - _extension_class_string(ce, key, &str_classes, ZSTR_VAL(sub_indent), module, &num_classes); + _extension_class_string(ce, key, &str_classes, " ", module, &num_classes); } ZEND_HASH_FOREACH_END(); if (num_classes) { smart_str_append_printf(str, "\n - Classes [%d] {", num_classes); smart_str_append_smart_str(str, &str_classes); - smart_str_append_printf(str, "%s }\n", indent); + smart_str_appends(str, " }\n"); } smart_str_free(&str_classes); - zend_string_release_ex(sub_indent, false); } - smart_str_append_printf(str, "%s}\n", indent); + smart_str_appends(str, "}\n"); } /* }}} */ @@ -6921,7 +6919,7 @@ ZEND_METHOD(ReflectionExtension, __toString) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(module); - _extension_string(&str, module, ""); + _extension_string(&str, module); RETURN_STR(smart_str_extract(&str)); } /* }}} */ From 4427cb451682502e4d301506923f4982627ccb01 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 12:11:58 -0700 Subject: [PATCH 15/35] Reflection: remove unneeded indentation support from `_extension_ini_string()` The only caller (`_extension_string()`) always provided `_extension_ini_string()` with the same indentation that it was called with; after the prior commit removed indentation support from `_extension_string()` it became clear that the INI string function also always received an empty string as the indentation. Removing the indentation entirely simplifies the building of the string representation, including replacing a use of `smart_str_append_printf()` with `smart_str_appends()`, which is more performant. --- ext/reflection/php_reflection.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 28e16602a77d..47c5311e2465 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1065,12 +1065,12 @@ static void _property_string(smart_str *str, const zend_property_info *prop, con } /* }}} */ -static void _extension_ini_string(const zend_ini_entry *ini_entry, smart_str *str, const char *indent, int number) /* {{{ */ +static void _extension_ini_string(const zend_ini_entry *ini_entry, smart_str *str, int number) /* {{{ */ { char *comma = ""; if (number == ini_entry->module_number) { - smart_str_append_printf(str, " %sEntry [ %s <", indent, ZSTR_VAL(ini_entry->name)); + smart_str_append_printf(str, " Entry [ %s <", ZSTR_VAL(ini_entry->name)); if (ini_entry->modifiable == ZEND_INI_ALL) { smart_str_appends(str, "ALL"); } else { @@ -1088,11 +1088,11 @@ static void _extension_ini_string(const zend_ini_entry *ini_entry, smart_str *st } smart_str_appends(str, "> ]\n"); - smart_str_append_printf(str, " %s Current = '%s'\n", indent, ini_entry->value ? ZSTR_VAL(ini_entry->value) : ""); + smart_str_append_printf(str, " Current = '%s'\n", ini_entry->value ? ZSTR_VAL(ini_entry->value) : ""); if (ini_entry->modified) { - smart_str_append_printf(str, " %s Default = '%s'\n", indent, ini_entry->orig_value ? ZSTR_VAL(ini_entry->orig_value) : ""); + smart_str_append_printf(str, " Default = '%s'\n", ini_entry->orig_value ? ZSTR_VAL(ini_entry->orig_value) : ""); } - smart_str_append_printf(str, " %s}\n", indent); + smart_str_appends(str, " }\n"); } } /* }}} */ @@ -1162,7 +1162,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module) / smart_str str_ini = {0}; zend_ini_entry *ini_entry; ZEND_HASH_MAP_FOREACH_PTR(EG(ini_directives), ini_entry) { - _extension_ini_string(ini_entry, &str_ini, "", module->module_number); + _extension_ini_string(ini_entry, &str_ini, module->module_number); } ZEND_HASH_FOREACH_END(); if (smart_str_get_len(&str_ini) > 0) { smart_str_appends(str, "\n - INI {\n"); From 0d680a2390d647f3417d2a02fabd25eb149b628b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 12:15:29 -0700 Subject: [PATCH 16/35] Reflection: remove unneeded indentation support from `_zend_extension_string()` The only caller (`ReflectionZendExtension::__toString()`) always provided an empty string as the indentation; removing the indentation slightly simplifies the building of the string representation. --- ext/reflection/php_reflection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 47c5311e2465..2c7690d1fe6f 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -301,7 +301,7 @@ static void _class_const_string(smart_str *str, const zend_string *name, zend_cl 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); static void _extension_string(smart_str *str, const zend_module_entry *module); -static void _zend_extension_string(smart_str *str, const zend_extension *extension, const char *indent); +static void _zend_extension_string(smart_str *str, const zend_extension *extension); /* {{{ _class_string */ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent) @@ -1347,9 +1347,9 @@ static void reflect_attributes(INTERNAL_FUNCTION_PARAMETERS, HashTable *attribut } /* }}} */ -static void _zend_extension_string(smart_str *str, const zend_extension *extension, const char *indent) /* {{{ */ +static void _zend_extension_string(smart_str *str, const zend_extension *extension) /* {{{ */ { - smart_str_append_printf(str, "%sZend Extension [ %s ", indent, extension->name); + smart_str_append_printf(str, "Zend Extension [ %s ", extension->name); if (extension->version) { smart_str_append_printf(str, "%s ", extension->version); @@ -7230,7 +7230,7 @@ ZEND_METHOD(ReflectionZendExtension, __toString) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(extension); - _zend_extension_string(&str, extension, ""); + _zend_extension_string(&str, extension); RETURN_STR(smart_str_extract(&str)); } /* }}} */ From 7eef7c9a9a813c8f0768108ee6321130aeecec10 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 12:45:04 -0700 Subject: [PATCH 17/35] Reflection: access common function fields through `zend_function.common` The `zend_function` union has both an `op_array` member for userland code, and an `internal_function` member for internal code. The elements at the start of each match, and are also made available under the `common` member which only has the fields present in both `op_array` and `internal_function`. For function pointers that have not been checked to determine if they are userland functions or internal functions, access common fields through `common`. While technically there shouldn't be a difference due to the common layout (up through the relevant fields), semantically accessing information about an internal function through `op_array` or about a userland function through `internal_function` does not make sense. --- ext/reflection/php_reflection.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 2c7690d1fe6f..0a670c856eb2 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -193,12 +193,12 @@ static inline bool is_closure_invoke(const zend_class_entry *ce, const zend_stri static zend_function *_copy_function(zend_function *fptr) /* {{{ */ { if (fptr - && (fptr->internal_function.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) + && (fptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { zend_function *copy_fptr; copy_fptr = emalloc(sizeof(zend_function)); memcpy(copy_fptr, fptr, sizeof(zend_function)); - copy_fptr->internal_function.function_name = zend_string_copy(fptr->internal_function.function_name); + copy_fptr->common.function_name = zend_string_copy(fptr->common.function_name); return copy_fptr; } else { /* no copy needed */ @@ -210,9 +210,9 @@ static zend_function *_copy_function(zend_function *fptr) /* {{{ */ static void _free_function(zend_function *fptr) /* {{{ */ { if (fptr - && (fptr->internal_function.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) + && (fptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_string_release_ex(fptr->internal_function.function_name, false); + zend_string_release_ex(fptr->common.function_name, false); zend_free_trampoline(fptr); } } @@ -930,7 +930,7 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze smart_str_appends(str, "function "); } - if (fptr->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) { + if (fptr->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) { smart_str_appendc(str, '&'); } smart_str_append_printf(str, "%s ] {\n", ZSTR_VAL(fptr->common.function_name)); @@ -948,7 +948,7 @@ static void _function_string(smart_str *str, const zend_function *fptr, const ze } _function_parameter_string(str, fptr, ZSTR_VAL(param_indent.s)); smart_str_free(¶m_indent); - if ((fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + if ((fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { smart_str_append_printf(str, " %s- %s [ ", indent, ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1]) ? "Tentative return" : "Return"); if (ZEND_TYPE_IS_SET(fptr->common.arg_info[-1].type)) { zend_string *type_str = zend_type_to_string(fptr->common.arg_info[-1].type); @@ -2160,7 +2160,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, returnsReference) GET_REFLECTION_OBJECT_PTR(fptr); - RETURN_BOOL((fptr->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0); + RETURN_BOOL((fptr->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0); } /* }}} */ @@ -2392,8 +2392,8 @@ ZEND_METHOD(ReflectionGenerator, getFunction) zval closure; ZVAL_OBJ(&closure, ZEND_CLOSURE_OBJECT(func)); reflection_function_factory(func, &closure, return_value); - } else if (func->op_array.scope) { - reflection_method_factory(func->op_array.scope, func, NULL, return_value); + } else if (func->common.scope) { + reflection_method_factory(func->common.scope, func, NULL, return_value); } else { reflection_function_factory(func, NULL, return_value); } @@ -3650,7 +3650,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, hasReturnType) GET_REFLECTION_OBJECT_PTR(fptr); - RETVAL_BOOL((fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && !ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])); + RETVAL_BOOL((fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && !ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])); } /* }}} */ @@ -3664,7 +3664,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getReturnType) GET_REFLECTION_OBJECT_PTR(fptr); - if (!(fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])) { + if (!(fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])) { RETURN_NULL(); } @@ -3682,7 +3682,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, hasTentativeReturnType) GET_REFLECTION_OBJECT_PTR(fptr); - RETVAL_BOOL(fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE && ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])); + RETVAL_BOOL(fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE && ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])); } /* }}} */ @@ -3696,7 +3696,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getTentativeReturnType) GET_REFLECTION_OBJECT_PTR(fptr); - if (!(fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || !ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])) { + if (!(fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) || !ZEND_ARG_TYPE_IS_TENTATIVE(&fptr->common.arg_info[-1])) { RETURN_NULL(); } From c89e3df7646cc4feeb7b9b6d7a6cf270d0d0e556 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 12:52:00 -0700 Subject: [PATCH 18/35] `ReflectionNamedType::getName()`: inline logic for legacy behavior Instead of a single-use two-line helper function `zend_type_to_string_without_null()`, move the relevant logic into `ReflectionNamedType::getName()` directly. To maintain the behavior of removing the `MAY_BE_NULL` flag from a *copy* of the stored type information, make an explicit copy of the type before the flag is removed. --- ext/reflection/php_reflection.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0a670c856eb2..271a4aae6e0b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3080,11 +3080,6 @@ static zend_string *zend_named_reflection_type_to_string(zend_type type) { return zend_type_to_string(type); } -static zend_string *zend_type_to_string_without_null(zend_type type) { - ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; - return zend_named_reflection_type_to_string(type); -} - /* {{{ Return the text of the type hint */ ZEND_METHOD(ReflectionType, __toString) { @@ -3107,10 +3102,12 @@ ZEND_METHOD(ReflectionNamedType, getName) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(param); + // Make a copy so that we don't modify the stored type information + zend_type type = param->type; if (param->legacy_behavior) { - RETURN_STR(zend_type_to_string_without_null(param->type)); + ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; } - RETURN_STR(zend_named_reflection_type_to_string(param->type)); + RETURN_STR(zend_named_reflection_type_to_string(type)); } /* }}} */ From 09eaaae5dd551424ad747e9427180a27a0a811b3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 13:10:34 -0700 Subject: [PATCH 19/35] `ReflectionFunction::__toString()`: stop accessing `intern->ce` For global functions there is no class entry set, the constructor a few lines above explicitly sets `intern->ce = NULL;`, as does `reflection_function_factory()`. Replace the access with `NULL` to avoid suggesting that there might be a class entry to use. --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 271a4aae6e0b..a336ab5b8854 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1767,7 +1767,7 @@ ZEND_METHOD(ReflectionFunction, __toString) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(fptr); - _function_string(&str, fptr, intern->ce, ""); + _function_string(&str, fptr, NULL, ""); RETURN_STR(smart_str_extract(&str)); } /* }}} */ From 046596102d607518ca7e629f949a91f853eddaeb Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 13:32:09 -0700 Subject: [PATCH 20/35] `ReflectionFunctionAbstract::getClosureUsedVariables()`: optimize arrays Avoid `array_init(return_value);` in cases where the function does not correspond to a closure or has no static variables, to avoid an unneeded allocation for an array that will be empty. --- ext/reflection/php_reflection.c | 61 +++++++++++++++++---------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a336ab5b8854..69ce3ae8a93d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1862,44 +1862,47 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureUsedVariables) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT(); - array_init(return_value); - if (!Z_ISUNDEF(intern->obj)) { - closure_func = zend_get_closure_method_def(Z_OBJ(intern->obj)); - if (closure_func == NULL || - closure_func->type != ZEND_USER_FUNCTION || - closure_func->op_array.static_variables == NULL) { - return; - } + if (Z_ISUNDEF(intern->obj)) { + RETURN_EMPTY_ARRAY(); + } - const zend_op_array *ops = &closure_func->op_array; + closure_func = zend_get_closure_method_def(Z_OBJ(intern->obj)); + if (closure_func == NULL || + closure_func->type != ZEND_USER_FUNCTION || + closure_func->op_array.static_variables == NULL + ) { + RETURN_EMPTY_ARRAY(); + } - HashTable *static_variables = ZEND_MAP_PTR_GET(ops->static_variables_ptr); + const zend_op_array *ops = &closure_func->op_array; - if (!static_variables) { - return; - } + HashTable *static_variables = ZEND_MAP_PTR_GET(ops->static_variables_ptr); - zend_op *opline = ops->opcodes + ops->num_args; - if (ops->fn_flags & ZEND_ACC_VARIADIC) { - opline++; - } + if (!static_variables) { + RETURN_EMPTY_ARRAY(); + } - for (; opline->opcode == ZEND_BIND_STATIC; opline++) { - if (!(opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) { - continue; - } + array_init(return_value); + zend_op *opline = ops->opcodes + ops->num_args; + if (ops->fn_flags & ZEND_ACC_VARIADIC) { + opline++; + } - Bucket *bucket = (Bucket*) - (((char*)static_variables->arData) + - (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); + for (; opline->opcode == ZEND_BIND_STATIC; opline++) { + if (!(opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) { + continue; + } - if (Z_ISUNDEF(bucket->val)) { - continue; - } + Bucket *bucket = (Bucket*) + (((char*)static_variables->arData) + + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))); - zend_hash_add_new(Z_ARRVAL_P(return_value), bucket->key, &bucket->val); - Z_TRY_ADDREF(bucket->val); + if (Z_ISUNDEF(bucket->val)) { + continue; } + + zend_hash_add_new(Z_ARRVAL_P(return_value), bucket->key, &bucket->val); + Z_TRY_ADDREF(bucket->val); } } /* }}} */ From 08c039c87a06eccf110076960e15a3ec71851953 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 13:46:16 -0700 Subject: [PATCH 21/35] `ReflectionClass::getStaticProperties()`: optimize array initialization For classes with no static properties, avoid allocating an array that will be empty. Includes a regression test for the function since it was previously untested, to confirm that even when a class declares no static properties of its own, inherited static properties are still returned. --- ext/reflection/php_reflection.c | 12 +++- .../ReflectionClass_getStaticProperties.phpt | 57 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/ReflectionClass_getStaticProperties.phpt diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 69ce3ae8a93d..3abd95731ca3 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4159,7 +4159,17 @@ ZEND_METHOD(ReflectionClass, getStaticProperties) RETURN_THROWS(); } - if (ce->default_static_members_count && !CE_STATIC_MEMBERS(ce)) { + // default_static_members_count includes inherited static members, see + // zend_do_inheritance_ex(). + // PHP does not support *dynamic* static properties the same way non-static + // properties can be dynamic (i.e. present on an object without having been + // declared on the class). Thus, default_static_members_count will always + // reflect the count of all static members that a class might have. + if (ce->default_static_members_count == 0) { + RETURN_EMPTY_ARRAY(); + } + + if (!CE_STATIC_MEMBERS(ce)) { zend_class_init_statics(ce); } diff --git a/ext/reflection/tests/ReflectionClass_getStaticProperties.phpt b/ext/reflection/tests/ReflectionClass_getStaticProperties.phpt new file mode 100644 index 000000000000..89d894f83989 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_getStaticProperties.phpt @@ -0,0 +1,57 @@ +--TEST-- +ReflectionClass::getStaticProperties() +--FILE-- +getStaticProperties()); +var_dump($child->getStaticProperties()); + +echo "\nTyped properties initialized\n:"; +Base::$pubTyped = true; +$base->setStaticPropertyValue('privTyped', true); + +var_dump($base->getStaticProperties()); +var_dump($child->getStaticProperties()); +?> +--EXPECT-- +Start: +array(2) { + ["pub"]=> + NULL + ["priv"]=> + NULL +} +array(1) { + ["pub"]=> + NULL +} + +Typed properties initialized +:array(4) { + ["pub"]=> + NULL + ["priv"]=> + NULL + ["pubTyped"]=> + bool(true) + ["privTyped"]=> + bool(true) +} +array(2) { + ["pub"]=> + NULL + ["pubTyped"]=> + bool(true) +} From dca2c720112041158f5a199c54d44750c59eeee8 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 13:49:54 -0700 Subject: [PATCH 22/35] Fix inline documentation of tentative return methods Both `ReflectionFunctionAbstract::hasTentativeReturnType()` and `ReflectionFunctionAbstract::getTentativeReturnType()` were documented as checking or retrieving the "return type" of a function rather than the "tentative return type". The two are different; update the documentation accordingly. --- ext/reflection/php_reflection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 3abd95731ca3..7a24f084045c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3672,7 +3672,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getReturnType) } /* }}} */ -/* {{{ Return whether the function has a return type */ +/* {{{ Return whether the function has a tentative return type */ ZEND_METHOD(ReflectionFunctionAbstract, hasTentativeReturnType) { reflection_object *intern; @@ -3686,7 +3686,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, hasTentativeReturnType) } /* }}} */ -/* {{{ Returns the return type associated with the function */ +/* {{{ Returns the tentative return type associated with the function */ ZEND_METHOD(ReflectionFunctionAbstract, getTentativeReturnType) { reflection_object *intern; From 4ba865110df53f6d24c3af689c4412c63d166e48 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 13:51:11 -0700 Subject: [PATCH 23/35] `ReflectionFunctionAbstract::getClosureCalledClass()`: remove pointer cast `called_scope` was declared to be a pointer to a `zend_class_entry`, no cast is needed. --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 7a24f084045c..b120e8e4eb9a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1847,7 +1847,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getClosureCalledClass) if (Z_OBJ_HANDLER(intern->obj, get_closure) && Z_OBJ_HANDLER(intern->obj, get_closure)(Z_OBJ(intern->obj), &called_scope, &closure_func, &object, true) == SUCCESS && closure_func && (called_scope || closure_func->common.scope)) { - zend_reflection_class_factory(called_scope ? (zend_class_entry *) called_scope : closure_func->common.scope, return_value); + zend_reflection_class_factory(called_scope ? called_scope : closure_func->common.scope, return_value); } } } From 9b5f5e7992dbce868e6a183cf99ec860e7bdd6ce Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 14:07:44 -0700 Subject: [PATCH 24/35] Reflection: remove `reflection_extension_factory()`, avoid extra lookups `reflection_extension_factory()` was used to initialize a `ReflectionExtension` instance based on the name of the extension. It would look for an extension with the given name in the global `module_registry` and, if found, delegate to `reflection_extension_factory_ex()` to do the actual initialization using the located `zend_module_entry` pointer. However, both callers of `reflection_extension_factory()` already had a `zend_module_entry` pointer! Replace both uses of `reflection_extension_factory()` with `reflection_extension_factory_ex()` using the available pointer, and remove the `reflection_extension_factory()` method. In a follow-up commit the _ex variant will be renamed to remove the suffix. --- ext/reflection/php_reflection.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b120e8e4eb9a..a683989a487c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1408,19 +1408,6 @@ static void reflection_extension_factory_ex(zval *object, zend_module_entry *mod } /* }}} */ -/* {{{ reflection_extension_factory */ -static void reflection_extension_factory(zval *object, const char *name_str) -{ - size_t name_len = strlen(name_str); - struct _zend_module_entry *module = zend_hash_str_find_ptr_lc(&module_registry, name_str, name_len); - if (!module) { - return; - } - - reflection_extension_factory_ex(object, module); -} -/* }}} */ - /* {{{ reflection_parameter_factory */ static void reflection_parameter_factory(zend_function *fptr, zval *closure_object, struct _zend_arg_info *arg_info, uint32_t offset, bool required, zval *object) { @@ -2259,7 +2246,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getExtension) internal = (zend_internal_function *)fptr; if (internal->module) { - reflection_extension_factory(return_value, internal->module->name); + reflection_extension_factory_ex(return_value, internal->module); } else { RETURN_NULL(); } @@ -5580,7 +5567,7 @@ ZEND_METHOD(ReflectionClass, getExtension) GET_REFLECTION_OBJECT_PTR(ce); if ((ce->type == ZEND_INTERNAL_CLASS) && ce->info.internal.module) { - reflection_extension_factory(return_value, ce->info.internal.module->name); + reflection_extension_factory_ex(return_value, ce->info.internal.module); } } /* }}} */ From 86b0eaabf09482bff29b175ee01c993fa3fae285 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 14:10:33 -0700 Subject: [PATCH 25/35] Reflection: rename `reflection_extension_factory_ex()` After the original `reflection_extension_factory()` was removed in the prior commit, the _ex suffix is no longer needed. Remove it. --- ext/reflection/php_reflection.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a683989a487c..4a8db77347e4 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1396,8 +1396,8 @@ PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object) } /* }}} */ -/* {{{ reflection_extension_factory_ex */ -static void reflection_extension_factory_ex(zval *object, zend_module_entry *module) +/* {{{ reflection_extension_factory */ +static void reflection_extension_factory(zval *object, zend_module_entry *module) { object_init_ex(object, reflection_extension_ptr); reflection_object *intern = Z_REFLECTION_P(object); @@ -2246,7 +2246,7 @@ ZEND_METHOD(ReflectionFunctionAbstract, getExtension) internal = (zend_internal_function *)fptr; if (internal->module) { - reflection_extension_factory_ex(return_value, internal->module); + reflection_extension_factory(return_value, internal->module); } else { RETURN_NULL(); } @@ -5567,7 +5567,7 @@ ZEND_METHOD(ReflectionClass, getExtension) GET_REFLECTION_OBJECT_PTR(ce); if ((ce->type == ZEND_INTERNAL_CLASS) && ce->info.internal.module) { - reflection_extension_factory_ex(return_value, ce->info.internal.module); + reflection_extension_factory(return_value, ce->info.internal.module); } } /* }}} */ @@ -8103,7 +8103,7 @@ static void reflection_constant_find_ext(INTERNAL_FUNCTION_PARAMETERS, bool only if (only_name) { RETURN_STRING(module->name); } - reflection_extension_factory_ex(return_value, module); + reflection_extension_factory(return_value, module); return; } ZEND_HASH_FOREACH_END(); From fc6aaf78ce9c5193f159daac8e43c9af9add3634 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 14:13:48 -0700 Subject: [PATCH 26/35] Reflection: improve string release in `reflection_property_factory_str()` Use `zend_string_release_ex()` rather than the generic `zend_string_release()` since we know that the string is not marked as persistent (based on the `false` parameter to `zend_string_init()` two lines earlier). --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 4a8db77347e4..54f420b3cb05 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1574,7 +1574,7 @@ static void reflection_property_factory_str(zend_class_entry *ce, const char *na { zend_string *name = zend_string_init(name_str, name_len, false); reflection_property_factory(ce, name, prop, object); - zend_string_release(name); + zend_string_release_ex(name, false); } /* {{{ reflection_class_constant_factory */ From 35f2e885cd3db49a072303e1966f3d64e16d65e3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 14:36:15 -0700 Subject: [PATCH 27/35] Simplify `ReflectionProperty::getSettableType()` property handling Once the `property_reference` has been dereferenced to identify the `zend_property_info`, no need to do so again since the result was already stored in a variable. --- ext/reflection/php_reflection.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 54f420b3cb05..7dc65d09cc24 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6426,7 +6426,7 @@ ZEND_METHOD(ReflectionProperty, getSettableType) const zend_property_info *prop = ref->prop; /* Dynamic property is untyped. */ - if (!ref->prop) { + if (!prop) { RETURN_NULL(); } @@ -6448,10 +6448,10 @@ ZEND_METHOD(ReflectionProperty, getSettableType) } /* Fall back to property type */ - if (!ZEND_TYPE_IS_SET(ref->prop->type)) { + if (!ZEND_TYPE_IS_SET(prop->type)) { RETURN_NULL(); } - reflection_type_factory(ref->prop->type, return_value, true); + reflection_type_factory(prop->type, return_value, true); } /* {{{ Returns whether property has a type */ From e7b1d99ea609ff8358c9b5b47c47512400f7d07a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 14:38:07 -0700 Subject: [PATCH 28/35] Rename `ReflectionParameter_isDefault.phpt` to reflect actual contents This is testing `ReflectionProperty::isDefault()`, `ReflectionParameter::isDefault()` does not exist. --- ...rameter_isDefault.phpt => ReflectionProperty_isDefault.phpt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ext/reflection/tests/{ReflectionParameter_isDefault.phpt => ReflectionProperty_isDefault.phpt} (94%) diff --git a/ext/reflection/tests/ReflectionParameter_isDefault.phpt b/ext/reflection/tests/ReflectionProperty_isDefault.phpt similarity index 94% rename from ext/reflection/tests/ReflectionParameter_isDefault.phpt rename to ext/reflection/tests/ReflectionProperty_isDefault.phpt index 3ab0d980fd04..ebb55a6a0d94 100644 --- a/ext/reflection/tests/ReflectionParameter_isDefault.phpt +++ b/ext/reflection/tests/ReflectionProperty_isDefault.phpt @@ -1,5 +1,5 @@ --TEST-- -ReflectionParameter::isDefault() +ReflectionProperty::isDefault() --FILE-- Date: Thu, 2 Jul 2026 14:40:29 -0700 Subject: [PATCH 29/35] Update `ReflectionMethod_getClosureThis.phpt` to match actual contents The file name was correct, but the test name mentioned `Reflection::getClosureThis()` (which does not exist). Update the test name to say `ReflectionMethod::getClosureThis()`. --- ext/reflection/tests/ReflectionMethod_getClosureThis.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/tests/ReflectionMethod_getClosureThis.phpt b/ext/reflection/tests/ReflectionMethod_getClosureThis.phpt index 656fde814d93..c9237e296427 100644 --- a/ext/reflection/tests/ReflectionMethod_getClosureThis.phpt +++ b/ext/reflection/tests/ReflectionMethod_getClosureThis.phpt @@ -1,5 +1,5 @@ --TEST-- -Reflection::getClosureThis() +ReflectionMethod::getClosureThis() --FILE-- Date: Thu, 2 Jul 2026 14:48:05 -0700 Subject: [PATCH 30/35] Fix inline comment in `ReflectionMethod::isConstructor()` "we we looking at" was presumably meant to be "we are looking at". --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 7dc65d09cc24..bcbbf65b110e 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3699,7 +3699,7 @@ ZEND_METHOD(ReflectionMethod, isConstructor) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(mptr); - /* we need to check if the ctor is the ctor of the class level we we + /* we need to check if the ctor is the ctor of the class level we are * looking at since we might be looking at an inherited old style ctor * defined in base class. */ RETURN_BOOL((mptr->common.fn_flags & ZEND_ACC_CTOR) && intern->ce->constructor && intern->ce->constructor->common.scope == mptr->common.scope); From 22d2e4186eb45c3d0d463bd9141cf7655cb60b37 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 15:06:55 -0700 Subject: [PATCH 31/35] Reflection: minor whitespace cleanup Consistently indent with tabs, have a space after the `while` keyword, and adjust the lines of a few braces. --- ext/reflection/php_reflection.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bcbbf65b110e..93c14e0ec72b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1128,7 +1128,7 @@ static void _extension_string(smart_str *str, const zend_module_entry *module) / smart_str_appends(str, "\n - Dependencies {\n"); - while(dep->name) { + while (dep->name) { smart_str_append_printf(str, " Dependency [ %s (", dep->name); switch(dep->type) { @@ -3751,13 +3751,13 @@ ZEND_METHOD(ReflectionMethod, getDeclaringClass) /* {{{ Returns whether a method has a prototype or not */ ZEND_METHOD(ReflectionMethod, hasPrototype) { - reflection_object *intern; - zend_function *mptr; + reflection_object *intern; + zend_function *mptr; - ZEND_PARSE_PARAMETERS_NONE(); + ZEND_PARSE_PARAMETERS_NONE(); - GET_REFLECTION_OBJECT_PTR(mptr); - RETURN_BOOL(mptr->common.prototype != NULL); + GET_REFLECTION_OBJECT_PTR(mptr); + RETURN_BOOL(mptr->common.prototype != NULL); } /* }}} */ @@ -7099,13 +7099,12 @@ ZEND_METHOD(ReflectionExtension, getDependencies) dep = module->deps; - if (!dep) - { + if (!dep) { RETURN_EMPTY_ARRAY(); } array_init(return_value); - while(dep->name) { + while (dep->name) { zend_string *relation; char *rel_type; size_t len = 0; @@ -7933,9 +7932,7 @@ static zval *_reflection_write_property(zend_object *object, zend_string *name, zend_throw_exception_ex(reflection_exception_ptr, 0, "Cannot set read-only property %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name)); return &EG(uninitialized_zval); - } - else - { + } else { return zend_std_write_property(object, name, value, cache_slot); } } From f6a4702d89bef2063cfc93297bed719e27b4e9cf Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 15:08:49 -0700 Subject: [PATCH 32/35] `ReflectionAttribute::getArguments()`: optimize array initialization For attributes without arguments, avoid allocating an array that will be empty. --- ext/reflection/php_reflection.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 93c14e0ec72b..67111157fb1d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7504,6 +7504,10 @@ ZEND_METHOD(ReflectionAttribute, getArguments) ZEND_PARSE_PARAMETERS_NONE(); GET_REFLECTION_OBJECT_PTR(attr); + if (attr->data->argc == 0) { + RETURN_EMPTY_ARRAY(); + } + array_init(return_value); for (i = 0; i < attr->data->argc; i++) { From 85f2424398d0c886f539e112a80d5eb561cad01e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 15:14:21 -0700 Subject: [PATCH 33/35] `ReflectionClass::get(Reflection)Constants()`: optimize array initialization In both `ReflectionClass::getConstants()` and `ReflectionClass::getReflectionConstants()`, for classes without constants, avoid allocating an array that will be empty. --- ext/reflection/php_reflection.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 67111157fb1d..9fd7b778c349 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4772,8 +4772,13 @@ ZEND_METHOD(ReflectionClass, getConstants) GET_REFLECTION_OBJECT_PTR(ce); + const HashTable *constants_table = CE_CONSTANTS_TABLE(ce); + if (zend_hash_num_elements(constants_table) == 0) { + RETURN_EMPTY_ARRAY(); + } + array_init(return_value); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, constant) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(constants_table, key, constant) { if (UNEXPECTED(Z_TYPE(constant->value) == IS_CONSTANT_AST && zend_update_class_constant(constant, key, constant->ce) != SUCCESS)) { RETURN_THROWS(); } @@ -4806,8 +4811,13 @@ ZEND_METHOD(ReflectionClass, getReflectionConstants) GET_REFLECTION_OBJECT_PTR(ce); + const HashTable *constants_table = CE_CONSTANTS_TABLE(ce); + if (zend_hash_num_elements(constants_table) == 0) { + RETURN_EMPTY_ARRAY(); + } + array_init(return_value); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), name, constant) { + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(constants_table, name, constant) { if (ZEND_CLASS_CONST_FLAGS(constant) & filter) { zval class_const; reflection_class_constant_factory(name, constant, &class_const); From 2e2b03e1ceeef4cf8ba2a5e863ccc2622311dac6 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 2 Jul 2026 15:15:16 -0700 Subject: [PATCH 34/35] `ReflectionClass::isSubclassOf()`: remove extraneous parentheses --- ext/reflection/php_reflection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 9fd7b778c349..f04bd020b770 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5504,7 +5504,7 @@ ZEND_METHOD(ReflectionClass, isSubclassOf) GET_REFLECTION_OBJECT_PTR(ce); - RETURN_BOOL((ce != class_ce && instanceof_function(ce, class_ce))); + RETURN_BOOL(ce != class_ce && instanceof_function(ce, class_ce)); } /* }}} */ From 9390b68d5d68c03bbfddf5ae7b48a28751b1b62b Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Tue, 7 Jul 2026 15:35:43 -0300 Subject: [PATCH 35/35] Change bless to clean up absolute paths (#22067) User-specific absolute paths should be avoided in tests, since they aren't portable. Change bless to detect common places where this occurs and make it use %s in EXPECTF if so. This was originally developed as part of GH-12276. While the approved RFC doesn't enable function parameters to be printed for tests, the functionality is generally useful. --- scripts/dev/bless_tests.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/dev/bless_tests.php b/scripts/dev/bless_tests.php index 7493a504729c..b38935503ae3 100755 --- a/scripts/dev/bless_tests.php +++ b/scripts/dev/bless_tests.php @@ -74,6 +74,10 @@ function normalizeOutput(string $out): string { 'Resource ID#%d used as offset, casting to integer (%d)', $out); $out = preg_replace('/string\(\d+\) "([^"]*%d)/', 'string(%d) "$1', $out); + // Inside of strings, replace absolute paths that have been truncated with + // any string. These tend to contain homedirs with usernames, not good. + $out = preg_replace("/'(\/|[A-Z]:\\\\)\S+\\.\\.\\.'/", "'%s'", $out); + $out = preg_replace("/'file:(\/|[A-Z]:\\\\)\S+\\.\\.\\.'/", "'%s'", $out); $out = str_replace("\0", '%0', $out); return $out; }