From 946d687d8d5a18c65a547a8fbb80b93dbe3abcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLamentXU123=E2=80=9D?= <108666168+LamentXU123@users.noreply.github.com> Date: Fri, 22 May 2026 18:28:58 +0800 Subject: [PATCH 1/4] ext/intl: fix IntlListFormatter object error state after format() failures close GH-22119 fix UPGRADING --- NEWS | 2 ++ UPGRADING | 2 ++ ext/intl/listformatter/listformatter_class.c | 10 +++++--- .../listformatter_get_error.phpt | 25 +++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 ext/intl/tests/listformatter/listformatter_get_error.phpt diff --git a/NEWS b/NEWS index 595b7c8096f2..ae3cae760320 100644 --- a/NEWS +++ b/NEWS @@ -388,6 +388,8 @@ PHP NEWS when pos.size < 2). (Oblivionsage) - Intl: + . Fixed IntlListFormatter::getErrorCode() and getErrorMessage() not + reflecting format() failures. (Weilin Du) . Fix leak in umsg_format_helper(). (ndossche) - LDAP: diff --git a/UPGRADING b/UPGRADING index 80b6a5ff38d5..8b6e0c57cb1e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -846,6 +846,8 @@ PHP 8.5 UPGRADE NOTES indicates more accurately which call site caused what error. Moreover, some ext/date exceptions have been wrapped inside a IntlException now. + . IntlListFormatter::getErrorCode() and getErrorMessage() now reflect + IntlListFormatter::format() failures. - Lexbor: . An always enabled lexbor extension is added. It contains the lexbor diff --git a/ext/intl/listformatter/listformatter_class.c b/ext/intl/listformatter/listformatter_class.c index 1aa849370ab8..e4f8b18d7dd6 100644 --- a/ext/intl/listformatter/listformatter_class.c +++ b/ext/intl/listformatter/listformatter_class.c @@ -124,6 +124,8 @@ PHP_METHOD(IntlListFormatter, format) Z_PARAM_ARRAY_HT(ht) ZEND_PARSE_PARAMETERS_END(); + intl_errors_reset(LISTFORMATTER_ERROR_P(obj)); + uint32_t count = zend_hash_num_elements(ht); if (count == 0) { RETURN_EMPTY_STRING(); @@ -154,7 +156,7 @@ PHP_METHOD(IntlListFormatter, format) } efree(items); efree(itemLengths); - intl_error_set(NULL, status, "Failed to convert string to UTF-16"); + intl_errors_set(LISTFORMATTER_ERROR_P(obj), status, "Failed to convert string to UTF-16"); RETURN_FALSE; } @@ -170,7 +172,7 @@ PHP_METHOD(IntlListFormatter, format) resultLength = ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, NULL, 0, &status); if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) { - intl_error_set(NULL, status, "Failed to format list"); + intl_errors_set(LISTFORMATTER_ERROR_P(obj), status, "Failed to format list"); RETVAL_FALSE; goto cleanup; } @@ -184,7 +186,7 @@ PHP_METHOD(IntlListFormatter, format) if (result) { efree(result); } - intl_error_set(NULL, status, "Failed to format list"); + intl_errors_set(LISTFORMATTER_ERROR_P(obj), status, "Failed to format list"); RETVAL_FALSE; goto cleanup; } @@ -194,7 +196,7 @@ PHP_METHOD(IntlListFormatter, format) efree(result); if (!ret) { - intl_error_set(NULL, status, "Failed to convert result to UTF-8"); + intl_errors_set(LISTFORMATTER_ERROR_P(obj), status, "Failed to convert result to UTF-8"); RETVAL_FALSE; } else { RETVAL_NEW_STR(ret); diff --git a/ext/intl/tests/listformatter/listformatter_get_error.phpt b/ext/intl/tests/listformatter/listformatter_get_error.phpt new file mode 100644 index 000000000000..a2a23b3473d2 --- /dev/null +++ b/ext/intl/tests/listformatter/listformatter_get_error.phpt @@ -0,0 +1,25 @@ +--TEST-- +IntlListFormatter getErrorCode()/getErrorMessage() reflect format() failures +--EXTENSIONS-- +intl +--FILE-- +format(["\x80"])); +var_dump($formatter->getErrorCode() === U_INVALID_CHAR_FOUND); +var_dump($formatter->getErrorMessage()); + +var_dump($formatter->format(['a', 'b'])); +var_dump($formatter->getErrorCode() === U_ZERO_ERROR); +var_dump($formatter->getErrorMessage()); + +?> +--EXPECT-- +bool(false) +bool(true) +string(85) "IntlListFormatter::format(): Failed to convert string to UTF-16: U_INVALID_CHAR_FOUND" +string(7) "a and b" +bool(true) +string(12) "U_ZERO_ERROR" From eeb12a4e288f774c48f3022fd7572771ba0bde9a Mon Sep 17 00:00:00 2001 From: Weilin Du <108666168+LamentXU123@users.noreply.github.com> Date: Sun, 24 May 2026 17:41:48 +0800 Subject: [PATCH 2/4] ext/Intl: Return PHP_INT_MIN as int from MessageFormatter::parse() on 64-bit (#22131) --- NEWS | 2 ++ UPGRADING | 2 ++ ext/intl/msgformat/msgformat_helpers.cpp | 2 +- ext/intl/tests/msgfmt_parse_int64_min_64.phpt | 26 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 ext/intl/tests/msgfmt_parse_int64_min_64.phpt diff --git a/NEWS b/NEWS index e37effff3afe..3bb752f5c8b3 100644 --- a/NEWS +++ b/NEWS @@ -69,6 +69,8 @@ PHP NEWS argument handling now raises TypeError instead of Error. (Weilin Du) . IntlBreakIterator::getLocale() now raises ValueError for invalid locale types. (Weilin Du) + . Fixed MessageFormatter::parse() and parseMessage() returning PHP_INT_MIN + as float rather than int on 64-bit platforms. (Weilin Du) - JSON: . Enriched JSON last error / exception message with error location. diff --git a/UPGRADING b/UPGRADING index a3b22d8c0560..7f805496db74 100644 --- a/UPGRADING +++ b/UPGRADING @@ -40,6 +40,8 @@ PHP 8.6 UPGRADE NOTES . IntlBreakIterator::getLocale() now raises a ValueError when the type is neither Locale::ACTUAL_LOCALE nor Locale::VALID_LOCALE instead of returning false. + . MessageFormatter::parse() and parseMessage() now return PHP_INT_MIN as + int, rather than float, on 64-bit platforms when parsing integer values. - PCNTL: . pcntl_alarm() now raises a ValueError if the seconds argument is diff --git a/ext/intl/msgformat/msgformat_helpers.cpp b/ext/intl/msgformat/msgformat_helpers.cpp index f504ee50abcb..12e05af57219 100644 --- a/ext/intl/msgformat/msgformat_helpers.cpp +++ b/ext/intl/msgformat/msgformat_helpers.cpp @@ -649,7 +649,7 @@ U_CFUNC void umsg_parse_helper(UMessageFormat *fmt, int *count, zval **args, UCh case Formattable::kInt64: aInt64 = fargs[i].getInt64(); - if(aInt64 > ZEND_LONG_MAX || aInt64 < -ZEND_LONG_MAX) { + if(aInt64 > ZEND_LONG_MAX || aInt64 < ZEND_LONG_MIN) { ZVAL_DOUBLE(&(*args)[i], (double)aInt64); } else { ZVAL_LONG(&(*args)[i], (zend_long)aInt64); diff --git a/ext/intl/tests/msgfmt_parse_int64_min_64.phpt b/ext/intl/tests/msgfmt_parse_int64_min_64.phpt new file mode 100644 index 000000000000..2d003667813c --- /dev/null +++ b/ext/intl/tests/msgfmt_parse_int64_min_64.phpt @@ -0,0 +1,26 @@ +--TEST-- +MessageFormatter::parse() with PHP_INT_MIN on 64-bit platform +--EXTENSIONS-- +intl +--SKIPIF-- + +--FILE-- +parse('-9,223,372,036,854,775,808'); +var_dump($parsed); + +$parsed = MessageFormatter::parseMessage('en_US', '{0,number,integer}', '-9,223,372,036,854,775,808'); +var_dump($parsed); + +?> +--EXPECT-- +array(1) { + [0]=> + int(-9223372036854775808) +} +array(1) { + [0]=> + int(-9223372036854775808) +} From 6cc51fe27c5134fc2c2b2fcc6b89ad091b12b5ad Mon Sep 17 00:00:00 2001 From: Peter Kokot Date: Sun, 24 May 2026 12:35:07 +0200 Subject: [PATCH 3/4] ext/curl: Use CHECK_HEADER (#22106) CHECK_HEADER() doesn't define redundant compile definitions. See f17c5ad83b67a61ec33c893c2009c0c4aeb50be7 --- ext/curl/config.w32 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/curl/config.w32 b/ext/curl/config.w32 index c3c314f1009c..567699f3b748 100644 --- a/ext/curl/config.w32 +++ b/ext/curl/config.w32 @@ -14,7 +14,7 @@ if (PHP_CURL != "no") { CHECK_LIB("libssh2.lib", "curl", PHP_CURL) && CHECK_LIB("nghttp2.lib", "curl", PHP_CURL)) ) { - if (!(CHECK_HEADER_ADD_INCLUDE("brotli/decode.h", "CFLAGS_CURL") && + if (!(CHECK_HEADER("brotli/decode.h", "CFLAGS_CURL") && CHECK_LIB("brotlidec.lib;brotlidec-static.lib", "curl", PHP_CURL) && CHECK_LIB("brotlicommon.lib;brotlicommon-static.lib", "curl", PHP_CURL) )) { From 900797e54fb8d21a761205e5788b9275dc1c7c0e Mon Sep 17 00:00:00 2001 From: Weilin Du <108666168+LamentXU123@users.noreply.github.com> Date: Sun, 24 May 2026 19:00:02 +0800 Subject: [PATCH 4/4] ext/intl: various optimization (#22069) - use array_init_size for known-size arrays - reuse known string lengths --- ext/intl/calendar/calendar_methods.cpp | 2 +- ext/intl/dateformat/dateformat_parse.cpp | 2 +- ext/intl/locale/locale_methods.cpp | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/intl/calendar/calendar_methods.cpp b/ext/intl/calendar/calendar_methods.cpp index 03a9e6497d84..fe4749d6d62d 100644 --- a/ext/intl/calendar/calendar_methods.cpp +++ b/ext/intl/calendar/calendar_methods.cpp @@ -203,7 +203,7 @@ U_CFUNC PHP_FUNCTION(intlcal_get_available_locales) int32_t count; const Locale *availLocales = Calendar::getAvailableLocales(count); - array_init(return_value); + array_init_size(return_value, count); for (int i = 0; i < count; i++) { Locale locale = availLocales[i]; add_next_index_string(return_value, locale.getName()); diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp index 667bbf98ac62..13cf56ad7d82 100644 --- a/ext/intl/dateformat/dateformat_parse.cpp +++ b/ext/intl/dateformat/dateformat_parse.cpp @@ -108,7 +108,7 @@ static void internal_parse_to_localtime(IntlDateFormatter_object *dfo, char* tex INTL_METHOD_CHECK_STATUS( dfo, "Date parsing failed" ); - array_init( return_value ); + array_init_size( return_value, 9 ); /* Add entries from various fields of the obtained parsed_calendar */ add_to_localtime_arr( dfo, return_value, parsed_calendar, UCAL_SECOND, CALENDAR_SEC); add_to_localtime_arr( dfo, return_value, parsed_calendar, UCAL_MINUTE, CALENDAR_MIN); diff --git a/ext/intl/locale/locale_methods.cpp b/ext/intl/locale/locale_methods.cpp index e325e14e0d49..1ee32a2f094e 100644 --- a/ext/intl/locale/locale_methods.cpp +++ b/ext/intl/locale/locale_methods.cpp @@ -457,7 +457,7 @@ static zend_string* get_icu_value_internal( const char* loc_name , const char* t efree( mod_loc_name); } - tag_value->len = strlen(tag_value->val); + tag_value->len = buflen; return tag_value; } /* }}} */ @@ -736,7 +736,7 @@ U_CFUNC PHP_FUNCTION( locale_get_keywords ) Z_PARAM_PATH(loc_name, loc_name_len) ZEND_PARSE_PARAMETERS_END(); - INTL_CHECK_LOCALE_LEN(strlen(loc_name)); + INTL_CHECK_LOCALE_LEN(loc_name_len); if(loc_name_len == 0) { loc_name = (char *)intl_locale_get_default(); @@ -1127,7 +1127,7 @@ U_CFUNC PHP_FUNCTION(locale_parse) Z_PARAM_PATH(loc_name, loc_name_len) ZEND_PARSE_PARAMETERS_END(); - INTL_CHECK_LOCALE_LEN(strlen(loc_name)); + INTL_CHECK_LOCALE_LEN(loc_name_len); if(loc_name_len == 0) { loc_name = (char *)intl_locale_get_default(); @@ -1318,7 +1318,7 @@ U_CFUNC PHP_FUNCTION(locale_filter_matches) if( token && (token==cur_lang_tag) ){ /* check if the char. after match is SEPARATOR */ - chrcheck = token + (strlen(cur_loc_range)); + chrcheck = token + can_loc_range->len; if( isIDSeparator(*chrcheck) || isKeywordSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ efree( cur_lang_tag ); efree( cur_loc_range ); @@ -1350,14 +1350,14 @@ U_CFUNC PHP_FUNCTION(locale_filter_matches) } /* end of if isCanonical */ else{ /* Convert to lower case for case-insensitive comparison */ - cur_lang_tag = reinterpret_cast(ecalloc( 1, strlen(lang_tag ) + 1)); + cur_lang_tag = reinterpret_cast(ecalloc(1, lang_tag_len + 1)); result = strToMatch( lang_tag , cur_lang_tag); if( result == 0) { efree( cur_lang_tag ); RETURN_FALSE; } - cur_loc_range = reinterpret_cast(ecalloc( 1, strlen(loc_range ) + 1)); + cur_loc_range = reinterpret_cast(ecalloc(1, loc_range_len + 1)); result = strToMatch( loc_range , cur_loc_range ); if( result == 0) { efree( cur_lang_tag ); @@ -1370,7 +1370,7 @@ U_CFUNC PHP_FUNCTION(locale_filter_matches) if( token && (token==cur_lang_tag) ){ /* check if the char. after match is SEPARATOR */ - chrcheck = token + (strlen(cur_loc_range)); + chrcheck = token + loc_range_len; if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){ efree( cur_lang_tag ); efree( cur_loc_range );