From da1e89fd3db0b9d2017976d774270ee7ce3b35a7 Mon Sep 17 00:00:00 2001 From: Weilin Du <108666168+LamentXU123@users.noreply.github.com> Date: Tue, 10 Mar 2026 05:28:50 +0800 Subject: [PATCH 1/7] RFC: Add Form Feed in Trim Functions (#20788) RFC: https://wiki.php.net/rfc/trim_form_feed Resolves GH-20783. --- NEWS | 2 ++ UPGRADING | 2 ++ ext/standard/basic_functions.stub.php | 8 ++++---- ext/standard/basic_functions_arginfo.h | 4 ++-- ext/standard/basic_functions_decl.h | 8 ++++---- ext/standard/string.c | 19 +++++++++---------- ext/standard/tests/strings/trim.phpt | 6 ++++++ 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 482dfcb091342..ae0f6f3e8ff58 100644 --- a/NEWS +++ b/NEWS @@ -122,6 +122,8 @@ PHP NEWS - Standard: . Fixed bug GH-19926 (reset internal pointer earlier while splicing array while COW violation flag is still set). (alexandre-daubois) + . Added form feed (\f) in the default trimmed characters of trim(), rtrim() + and ltrim(). (Weilin Du) . Invalid mode values now throw in array_filter() instead of being silently defaulted to 0. (Jorg Sowa) . Fixed bug GH-21058 (error_log() crashes with message_type 3 and diff --git a/UPGRADING b/UPGRADING index 732d6f1d48598..595a49e41e4d7 100644 --- a/UPGRADING +++ b/UPGRADING @@ -47,6 +47,8 @@ PHP 8.6 UPGRADE NOTES - Standard: . Invalid mode values now throw in array_filter() instead of being silently defaulted to 0. + . Form feed (\f) is now added in the default trimmed characters of trim(), + rtrim() and ltrim(). RFC: https://wiki.php.net/rfc/trim_form_feed ======================================== 2. New Features diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 6fa0d47c7bd7f..6d0c565fc2d41 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2318,16 +2318,16 @@ function strcoll(string $string1, string $string2): int {} * @frameless-function {"arity": 1} * @frameless-function {"arity": 2} */ -function trim(string $string, string $characters = " \n\r\t\v\0"): string {} +function trim(string $string, string $characters = " \f\n\r\t\v\0"): string {} /** @compile-time-eval */ -function rtrim(string $string, string $characters = " \n\r\t\v\0"): string {} +function rtrim(string $string, string $characters = " \f\n\r\t\v\0"): string {} /** @alias rtrim */ -function chop(string $string, string $characters = " \n\r\t\v\0"): string {} +function chop(string $string, string $characters = " \f\n\r\t\v\0"): string {} /** @compile-time-eval */ -function ltrim(string $string, string $characters = " \n\r\t\v\0"): string {} +function ltrim(string $string, string $characters = " \f\n\r\t\v\0"): string {} /** * @compile-time-eval diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index e467710f72f91..d0109fa27c960 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 8d1c2a735f412f8571675c6b025c3a418b68fb65 + * Stub hash: f5583557f058e4862750d1262296d7f59cb0eed0 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) @@ -839,7 +839,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_trim, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, characters, IS_STRING, 0, "\" \\n\\r\\t\\v\\x00\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, characters, IS_STRING, 0, "\" \\f\\n\\r\\t\\v\\x00\"") ZEND_END_ARG_INFO() #define arginfo_rtrim arginfo_trim diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h index 9e6fb0def4472..139b47f2444d4 100644 --- a/ext/standard/basic_functions_decl.h +++ b/ext/standard/basic_functions_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 8d1c2a735f412f8571675c6b025c3a418b68fb65 */ + * Stub hash: f5583557f058e4862750d1262296d7f59cb0eed0 */ -#ifndef ZEND_BASIC_FUNCTIONS_DECL_8d1c2a735f412f8571675c6b025c3a418b68fb65_H -#define ZEND_BASIC_FUNCTIONS_DECL_8d1c2a735f412f8571675c6b025c3a418b68fb65_H +#ifndef ZEND_BASIC_FUNCTIONS_DECL_f5583557f058e4862750d1262296d7f59cb0eed0_H +#define ZEND_BASIC_FUNCTIONS_DECL_f5583557f058e4862750d1262296d7f59cb0eed0_H typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_HalfAwayFromZero = 1, @@ -15,4 +15,4 @@ typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_PositiveInfinity = 8, } zend_enum_RoundingMode; -#endif /* ZEND_BASIC_FUNCTIONS_DECL_8d1c2a735f412f8571675c6b025c3a418b68fb65_H */ +#endif /* ZEND_BASIC_FUNCTIONS_DECL_f5583557f058e4862750d1262296d7f59cb0eed0_H */ diff --git a/ext/standard/string.c b/ext/standard/string.c index d146b4534e275..7d609a032dd17 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -515,11 +515,16 @@ static inline zend_result php_charmask(const unsigned char *input, size_t len, c } /* }}} */ +static zend_always_inline bool php_is_whitespace(unsigned char c) +{ + return c <= ' ' && (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0'); +} + /* {{{ php_trim_int() * mode 1 : trim left * mode 2 : trim right * mode 3 : trim left and right - * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0') + * what indicates which chars are to be trimmed. NULL->default (' \f\t\n\r\v\0') */ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char *what, size_t what_len, int mode) { @@ -573,10 +578,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char } else { if (mode & 1) { while (start != end) { - unsigned char c = (unsigned char)*start; - - if (c <= ' ' && - (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) { + if (php_is_whitespace((unsigned char)*start)) { start++; } else { break; @@ -585,10 +587,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char } if (mode & 2) { while (start != end) { - unsigned char c = (unsigned char)*(end-1); - - if (c <= ' ' && - (c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '\0')) { + if (php_is_whitespace((unsigned char)*(end-1))) { end--; } else { break; @@ -611,7 +610,7 @@ static zend_always_inline zend_string *php_trim_int(zend_string *str, const char * mode 1 : trim left * mode 2 : trim right * mode 3 : trim left and right - * what indicates which chars are to be trimmed. NULL->default (' \t\n\r\v\0') + * what indicates which chars are to be trimmed. NULL->default (' \f\t\n\r\v\0') */ PHPAPI zend_string *php_trim(zend_string *str, const char *what, size_t what_len, int mode) { diff --git a/ext/standard/tests/strings/trim.phpt b/ext/standard/tests/strings/trim.phpt index fd0c26d7794aa..9ce5e1dfff02b 100644 --- a/ext/standard/tests/strings/trim.phpt +++ b/ext/standard/tests/strings/trim.phpt @@ -18,6 +18,9 @@ var_dump("ABC" === trim("ABC\x50\xC1\x60\x90","\x50..\xC1")); var_dump("ABC\x50\xC1" === trim("ABC\x50\xC1\x60\x90","\x51..\xC0")); var_dump("ABC\x50" === trim("ABC\x50\xC1\x60\x90","\x51..\xC1")); var_dump("ABC" === trim("ABC\x50\xC1\x60\x90","\x50..\xC1")); +var_dump("ABC" === trim("\fABC\f")); +var_dump("ABC" === ltrim("\fABC")); +var_dump("ABC" === rtrim("ABC\f")); ?> --EXPECT-- @@ -36,3 +39,6 @@ bool(true) bool(true) bool(true) bool(true) +bool(true) +bool(true) +bool(true) From 8bff64423377fb0c3c2ef279bdaf18e40e21a1c8 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 16:00:30 +0000 Subject: [PATCH 2/7] Zend: remove unused scope parameter of add_intersection_type() --- Zend/zend_compile.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 23db72bb4fda1..94956f990a0c4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1417,7 +1417,7 @@ static zend_string *resolve_class_name(zend_string *name, const zend_class_entry } static zend_string *add_intersection_type(zend_string *str, - const zend_type_list *intersection_type_list, zend_class_entry *scope, + const zend_type_list *intersection_type_list, bool is_bracketed) { const zend_type *single_type; @@ -1448,13 +1448,13 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry /* Pure intersection type */ if (ZEND_TYPE_IS_INTERSECTION(type)) { ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type)); - str = add_intersection_type(str, ZEND_TYPE_LIST(type), scope, /* is_bracketed */ false); + str = add_intersection_type(str, ZEND_TYPE_LIST(type), /* is_bracketed */ false); } else if (ZEND_TYPE_HAS_LIST(type)) { /* A union type might not be a list */ const zend_type *list_type; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - str = add_intersection_type(str, ZEND_TYPE_LIST(*list_type), scope, /* is_bracketed */ true); + str = add_intersection_type(str, ZEND_TYPE_LIST(*list_type), /* is_bracketed */ true); continue; } ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); From 6880a6c49d8a4eb6b464a07b0addf2778b3d39b2 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 16:00:56 +0000 Subject: [PATCH 3/7] Zend: mark scope parameter of zend_type_to_string_resolved() const And propagate const qualifier to use sites of this function. --- Zend/zend_compile.c | 2 +- Zend/zend_compile.h | 2 +- Zend/zend_inheritance.c | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 94956f990a0c4..3147fda23e9fd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1442,7 +1442,7 @@ static zend_string *add_intersection_type(zend_string *str, return str; } -zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) { +zend_string *zend_type_to_string_resolved(const zend_type type, const zend_class_entry *scope) { zend_string *str = NULL; /* Pure intersection type */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0b38084a107cd..5414467f3f874 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1034,7 +1034,7 @@ int ZEND_FASTCALL zendlex(zend_parser_stack_elem *elem); void zend_assert_valid_class_name(const zend_string *const_name, const char *type); -zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope); +zend_string *zend_type_to_string_resolved(zend_type type, const zend_class_entry *scope); ZEND_API zend_string *zend_type_to_string(zend_type type); /* class fetches */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 9b3b6312cf255..e623e1e7aba3b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -60,8 +60,8 @@ static void add_property_hook_obligation( zend_class_entry *ce, const zend_property_info *hooked_prop, const zend_function *hook_func); static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status); static void zend_type_copy_ctor(zend_type *const type, bool use_arena, bool persistent); @@ -897,7 +897,7 @@ static inheritance_status zend_do_perform_implementation_check( /* }}} */ static ZEND_COLD void zend_append_type_hint( - smart_str *str, zend_class_entry *scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ + smart_str *str, const zend_class_entry *scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */ { if (ZEND_TYPE_IS_SET(arg_info->type)) { zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope); @@ -911,7 +911,7 @@ static ZEND_COLD void zend_append_type_hint( /* }}} */ static ZEND_COLD zend_string *zend_get_function_declaration( - const zend_function *fptr, zend_class_entry *scope) /* {{{ */ + const zend_function *fptr, const zend_class_entry *scope) /* {{{ */ { smart_str str = {0}; @@ -1054,8 +1054,8 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) { } static void ZEND_COLD emit_incompatible_method_error( - const zend_function *child, zend_class_entry *child_scope, - const zend_function *parent, zend_class_entry *parent_scope, + const zend_function *child, const zend_class_entry *child_scope, + const zend_function *parent, const zend_class_entry *parent_scope, inheritance_status status) { zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope); zend_string *child_prototype = zend_get_function_declaration(child, child_scope); From 2b20627cbce44ca45f3230498d3ac17cb5831a0d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 16:09:32 +0000 Subject: [PATCH 4/7] Zend: mark zend_perform_covariant_type_check() as static This function is not defined in any headers --- Zend/zend_inheritance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e623e1e7aba3b..3313b07558612 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -671,7 +671,7 @@ static inheritance_status zend_is_intersection_subtype_of_type( return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } -ZEND_API inheritance_status zend_perform_covariant_type_check( +static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_scope, const zend_type fe_type, zend_class_entry *proto_scope, const zend_type proto_type) { From 032e5f6774694bfd64311387a205a7aec39c4fcb Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 16:14:06 +0000 Subject: [PATCH 5/7] Zend: mark arg_info parameters of zend_do_perform_arg_type_hint_check() const --- Zend/zend_inheritance.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 3313b07558612..93186f439d231 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -763,8 +763,8 @@ static inheritance_status zend_perform_covariant_type_check( } static inheritance_status zend_do_perform_arg_type_hint_check( - zend_class_entry *fe_scope, zend_arg_info *fe_arg_info, - zend_class_entry *proto_scope, zend_arg_info *proto_arg_info) /* {{{ */ + zend_class_entry *fe_scope, const zend_arg_info *fe_arg_info, + zend_class_entry *proto_scope, const zend_arg_info *proto_arg_info) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type) || ZEND_TYPE_PURE_MASK(fe_arg_info->type) == MAY_BE_ANY) { /* Child with no type or mixed type is always compatible */ From 1096ea149ae921956db31f694960e996c78888b5 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 16:24:51 +0000 Subject: [PATCH 6/7] Zend: mark variable as const --- Zend/zend_inheritance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 93186f439d231..b5fc262ec1ea0 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -320,7 +320,7 @@ static bool unlinked_instanceof(const zend_class_entry *ce1, const zend_class_en } if (ce1->parent) { - zend_class_entry *parent_ce; + const zend_class_entry *parent_ce; if (ce1->ce_flags & ZEND_ACC_RESOLVED_PARENT) { parent_ce = ce1->parent; } else { From f93b17076a25e2a06af50a6d177ae4badc5e44c3 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Mon, 9 Mar 2026 21:57:50 +0000 Subject: [PATCH 7/7] Zend: inherit interfaces early (#18622) The primary motivation for this is that this is required for my abstract generic types proof of concept, as the resolving of bound types needs to happen early to properly track the types. However, there doesn't seem to be a good reason for delaying the inheritance of interfaces. This approach might even allow us to drop the `iface` parameter of the `interface_gets_implemented()` handler as the interface name is always known. --- .../enum/no-class-implements-backed-enum.phpt | 2 +- ...y_interfaces_error_via_indirect_interface.phpt | 15 +++++++++++++++ Zend/zend_inheritance.c | 7 ++++--- 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/inheritance/enum_only_interfaces_error_via_indirect_interface.phpt diff --git a/Zend/tests/enum/no-class-implements-backed-enum.phpt b/Zend/tests/enum/no-class-implements-backed-enum.phpt index f6f37818da5ac..f8d4fd13a4239 100644 --- a/Zend/tests/enum/no-class-implements-backed-enum.phpt +++ b/Zend/tests/enum/no-class-implements-backed-enum.phpt @@ -7,4 +7,4 @@ class Foo implements BackedEnum {} ?> --EXPECTF-- -Fatal error: Non-enum class Foo cannot implement interface BackedEnum in %s on line %d +Fatal error: Non-enum class Foo cannot implement interface UnitEnum in %s on line %d diff --git a/Zend/tests/inheritance/enum_only_interfaces_error_via_indirect_interface.phpt b/Zend/tests/inheritance/enum_only_interfaces_error_via_indirect_interface.phpt new file mode 100644 index 0000000000000..66a9215320550 --- /dev/null +++ b/Zend/tests/inheritance/enum_only_interfaces_error_via_indirect_interface.phpt @@ -0,0 +1,15 @@ +--TEST-- +Interface that is extended from Enum only interface shouldn't be implementable by non-enum class +--FILE-- + +--EXPECTF-- +Fatal error: Non-enum class C cannot implement interface UnitEnum in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b5fc262ec1ea0..ba13a3233ed2a 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2168,6 +2168,10 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * zend_class_constant *c; uint32_t flags = ZEND_INHERITANCE_CHECK_PROTO | ZEND_INHERITANCE_CHECK_VISIBILITY; + if (iface->num_interfaces) { + zend_do_inherit_interfaces(ce, iface); + } + if (!(ce->ce_flags & ZEND_ACC_INTERFACE)) { /* We are not setting the prototype of overridden interface methods because of abstract * constructors. See Zend/tests/interface_constructor_prototype_001.phpt. */ @@ -2199,9 +2203,6 @@ static void do_interface_implementation(zend_class_entry *ce, zend_class_entry * } ZEND_HASH_FOREACH_END(); do_implement_interface(ce, iface); - if (iface->num_interfaces) { - zend_do_inherit_interfaces(ce, iface); - } } /* }}} */