From 9a6aadf0d52bba44ce3977de44b5dbb68a5471e9 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 22 Jun 2026 13:07:02 +0800 Subject: [PATCH 1/4] ext/phar: Optimize temporary string handling in Phar directory streams (#22374) Reduced temporary allocations when iterating Phar directories. --- UPGRADING | 3 +++ ext/phar/dirstream.c | 16 ++++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/UPGRADING b/UPGRADING index c77bfbfa4e02..9f7917301b59 100644 --- a/UPGRADING +++ b/UPGRADING @@ -481,6 +481,9 @@ PHP 8.6 UPGRADE NOTES . Improved performance of indentation generation in json_encode() when using PHP_JSON_PRETTY_PRINT. +- Phar: + . Reduced temporary allocations when iterating Phar directories. + - Standard: . Improved performance of array_fill_keys(). . Improved performance of array_map() with multiple arrays passed. diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index b1c8c4747cc6..f700cc3d84b3 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -149,7 +149,7 @@ static int phar_compare_dir_name(Bucket *f, Bucket *s) /* {{{ */ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const HashTable *manifest) /* {{{ */ { HashTable *data; - char *entry; + const char *entry; ALLOC_HASHTABLE(data); zend_hash_init(data, 64, NULL, NULL, 0); @@ -181,9 +181,7 @@ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const Has /* the entry has a path separator and is a subdirectory */ keylen = has_slash - ZSTR_VAL(str_key); } - entry = safe_emalloc(keylen, 1, 1); - memcpy(entry, ZSTR_VAL(str_key), keylen); - entry[keylen] = '\0'; + entry = ZSTR_VAL(str_key); } else { if (0 != memcmp(ZSTR_VAL(str_key), dir, dirlen)) { /* entry in directory not found */ @@ -201,16 +199,12 @@ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const Has if (has_slash) { /* is subdirectory */ save -= dirlen + 1; - entry = safe_emalloc(has_slash - save + dirlen, 1, 1); - memcpy(entry, save + dirlen + 1, has_slash - save - dirlen - 1); keylen = has_slash - save - dirlen - 1; - entry[keylen] = '\0'; + entry = save + dirlen + 1; } else { /* is file */ save -= dirlen + 1; - entry = safe_emalloc(keylen - dirlen, 1, 1); - memcpy(entry, save + dirlen + 1, keylen - dirlen - 1); - entry[keylen - dirlen - 1] = '\0'; + entry = save + dirlen + 1; keylen = keylen - dirlen - 1; } } @@ -227,8 +221,6 @@ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const Has ZVAL_NULL(&dummy); zend_hash_str_update(data, entry, keylen, &dummy); } - - efree(entry); } ZEND_HASH_FOREACH_END(); if (FAILURE != zend_hash_has_more_elements(data)) { From 02f71b68132cde1f97343e8fa5210699659e89d2 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 22 Jun 2026 13:19:09 +0800 Subject: [PATCH 2/4] ext/intl: Fix double construction leaks (#22386) Calling Collator::__construct() or Spoofchecker::__construct() on an already constructed object replaces the stored ICU handle, which leaves the previous handle unreachable and prevents it from being released during object destruction. Reject repeated construction with an Error for both classes so the existing ICU handle remains owned by the object. Add PHPT coverage for the double construction path. Closes #22386 --- NEWS | 2 ++ ext/intl/collator/collator_create.c | 4 ++++ ext/intl/spoofchecker/spoofchecker_create.c | 8 ++++++-- ext/intl/tests/collator_double_ctor.phpt | 16 ++++++++++++++++ ext/intl/tests/spoofchecker_double_ctor.phpt | 18 ++++++++++++++++++ 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 ext/intl/tests/collator_double_ctor.phpt create mode 100644 ext/intl/tests/spoofchecker_double_ctor.phpt diff --git a/NEWS b/NEWS index 1b6809def6c4..f880b5ca33c8 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,8 @@ PHP NEWS and later. (Graham Campbell) . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the fallback locale when a language tag cannot be canonicalized. (Weilin Du) + . Fixed memory leaks when calling Collator::__construct() or + Spoofchecker::__construct() twice. (Weilin Du) - mysqli: . Fix stmt->query leak in mysqli_execute_query() validation errors. diff --git a/ext/intl/collator/collator_create.c b/ext/intl/collator/collator_create.c index 88dacc1c1db4..ca57d5431e01 100644 --- a/ext/intl/collator/collator_create.c +++ b/ext/intl/collator/collator_create.c @@ -42,6 +42,10 @@ static int collator_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handling *erro INTL_CHECK_LOCALE_LEN_OR_FAILURE(locale_len); COLLATOR_METHOD_FETCH_OBJECT; + if (co->ucoll) { + zend_throw_error(NULL, "Collator object is already constructed"); + return FAILURE; + } if(locale_len == 0) { locale = (char *)intl_locale_get_default(); diff --git a/ext/intl/spoofchecker/spoofchecker_create.c b/ext/intl/spoofchecker/spoofchecker_create.c index c1cecac8412a..4614d44c3174 100644 --- a/ext/intl/spoofchecker/spoofchecker_create.c +++ b/ext/intl/spoofchecker/spoofchecker_create.c @@ -31,9 +31,13 @@ PHP_METHOD(Spoofchecker, __construct) ZEND_PARSE_PARAMETERS_NONE(); - zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling); - SPOOFCHECKER_METHOD_FETCH_OBJECT_NO_CHECK; + if (co->uspoof) { + zend_throw_error(NULL, "Spoofchecker object is already constructed"); + RETURN_THROWS(); + } + + zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling); co->uspoof = uspoof_open(SPOOFCHECKER_ERROR_CODE_P(co)); INTL_METHOD_CHECK_STATUS(co, "spoofchecker: unable to open ICU Spoof Checker"); diff --git a/ext/intl/tests/collator_double_ctor.phpt b/ext/intl/tests/collator_double_ctor.phpt new file mode 100644 index 000000000000..93b72f7392b3 --- /dev/null +++ b/ext/intl/tests/collator_double_ctor.phpt @@ -0,0 +1,16 @@ +--TEST-- +Collator double construction should not be allowed +--EXTENSIONS-- +intl +--FILE-- +__construct('en_US'); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Collator object is already constructed diff --git a/ext/intl/tests/spoofchecker_double_ctor.phpt b/ext/intl/tests/spoofchecker_double_ctor.phpt new file mode 100644 index 000000000000..01dae5ab4bc5 --- /dev/null +++ b/ext/intl/tests/spoofchecker_double_ctor.phpt @@ -0,0 +1,18 @@ +--TEST-- +Spoofchecker double construction should not be allowed +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +__construct(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Spoofchecker object is already constructed From b8278fdf56c55564d8364f35276034dee06554cc Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 22 Jun 2026 13:36:56 +0800 Subject: [PATCH 3/4] ext/intl: Fix Spoofchecker build after double construction change (#22386) PHP-8.5 no longer declares a zend_error_handling variable in Spoofchecker::__construct(). The previous merge from PHP-8.4 carried forward a zend_replace_error_handling() call that references the removed local variable. Remove the stale call so PHP-8.5 and branches merged from it build again. --- ext/intl/spoofchecker/spoofchecker_create.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/intl/spoofchecker/spoofchecker_create.c b/ext/intl/spoofchecker/spoofchecker_create.c index 4305aec6f5fa..196886ad4eca 100644 --- a/ext/intl/spoofchecker/spoofchecker_create.c +++ b/ext/intl/spoofchecker/spoofchecker_create.c @@ -36,8 +36,6 @@ PHP_METHOD(Spoofchecker, __construct) RETURN_THROWS(); } - zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, &error_handling); - co->uspoof = uspoof_open(SPOOFCHECKER_ERROR_CODE_P(co)); if (U_FAILURE(INTL_DATA_ERROR_CODE(co))) { zend_throw_exception(IntlException_ce_ptr, From e121cb26ec23b0b74e03a82aec177fb039fb630a Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Mon, 22 Jun 2026 14:13:44 +0800 Subject: [PATCH 4/4] [skip ci] Fix NEWS entry for several Intl fixes into the latest version section Move the Locale::lookup() and double-construction leak NEWS entries from the previous release section to the current unreleased section. These fixes landed after the previous release and should be listed under the next one. --- NEWS | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index f880b5ca33c8..8ee746eadee2 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,12 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.24 +- Intl: + . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the + fallback locale when a language tag cannot be canonicalized. (Weilin Du) + . Fixed memory leaks when calling Collator::__construct() or + Spoofchecker::__construct() twice. (Weilin Du) + - Reflection: . Fixed bug GH-22324 (Ignore leading namespace separator in ReflectionParameter::__construct()). (jorgsowa) @@ -52,10 +58,6 @@ PHP NEWS for invalid display types. (Weilin Du) . Fixed Spoofchecker restriction-level APIs to only be exposed with ICU 53 and later. (Graham Campbell) - . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the - fallback locale when a language tag cannot be canonicalized. (Weilin Du) - . Fixed memory leaks when calling Collator::__construct() or - Spoofchecker::__construct() twice. (Weilin Du) - mysqli: . Fix stmt->query leak in mysqli_execute_query() validation errors.