Commit 1f2319e42cb for php.net
commit 1f2319e42cbd99727c403c01ec22614f2356fe38
Merge: c621cbe27ff 8bc0b1e077c
Author: Weilin Du <weilindu@php.net>
Date: Tue Aug 18 17:51:12 2026 +0800
Merge branch 'PHP-8.5'
* PHP-8.5:
Fix GH-23094: Use byte offsets in NumberFormatter parsing (#23318)
diff --cc NEWS
index 3db6bc520e8,79171a05fe1..9690787e59b
--- a/NEWS
+++ b/NEWS
@@@ -22,26 -12,18 +22,28 @@@ PH
. Fixed a crash in DOMXPath when a php:function callback receives a nodeset
and a later callback returns a node from another document. (iliaal)
+- PDO_PGSQL:
+ . Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
+ loop when cleaning up a fetch left in a COPY, a use-after-free when a
+ statement with emulated or disabled prepares is destroyed, a connection
+ left busy for the next fetch, and rows delivered from a result another
+ statement took over. (KentarouTakeda)
+
- Intl:
+ . Fixed grapheme_strrev() treating UBRK_DONE as a byte index and leaving
+ the result without a terminating NUL. (iliaal)
. Fixed a double-free when IntlGregorianCalendar construction fails after
the ICU constructor adopts the TimeZone. (iliaal)
+ . Fixed bug GH-23094 (NumberFormatter parsing offsets use UTF-16 positions
+ for UTF-8 strings). (ColumbusLabs)
-- Opcache:
- . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
+- Phar:
+ . Fixed Phar archives being automatically detected when ".phar" only occurs
+ in a directory name or is not a filename extension in an included file's
+ path. (Weilin Du)
- Readline:
- . Fixed the interactive shell not waiting for the pager process to exit.
- (Weilin Du)
+ . Fixed class constant completion in the interactive shell. (Weilin Du)
- Zip:
. Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive
diff --cc ext/intl/formatter/formatter_parse.cpp
index 40107229e0a,f95696225e4..7a233f012d4
--- a/ext/intl/formatter/formatter_parse.cpp
+++ b/ext/intl/formatter/formatter_parse.cpp
@@@ -16,28 -16,65 +16,65 @@@
#include <config.h>
#endif
-#include "php_intl.h"
-
+#include <unicode/fmtable.h>
+#include <unicode/curramt.h>
+ #include <unicode/ustring.h>
-#include <locale.h>
-
+#include "../intl_convertcpp.h"
#include "formatter_class.h"
#include "formatter_format.h"
-#include "intl_convert.h"
+
+extern "C" {
+#include "php_intl.h"
+}
+
+#include <locale.h>
+#include <memory>
#define ICU_LOCALE_BUG 1
+ static bool numfmt_utf8_offset_to_utf16(const char *str, size_t str_len, int32_t *position, UErrorCode *status)
+ {
+ int32_t utf16_position;
+
+ if (*position < 0 || (size_t) *position > str_len) {
+ return true;
+ }
+
+ *status = U_ZERO_ERROR;
- u_strFromUTF8(NULL, 0, &utf16_position, str, *position, status);
++ u_strFromUTF8(nullptr, 0, &utf16_position, str, *position, status);
+ if (*status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(*status)) {
+ return false;
+ }
+ *status = U_ZERO_ERROR;
+
+ *position = utf16_position;
+ return true;
+ }
+
-static int32_t numfmt_utf16_offset_to_utf8(const UChar *str, int32_t str_len, int32_t position)
++static int32_t numfmt_utf16_offset_to_utf8(const icu::UnicodeString &str, int32_t position)
+ {
+ int32_t utf8_position;
+ UErrorCode status = U_ZERO_ERROR;
+
- if (position < 0 || position > str_len) {
++ if (position < 0 || position > str.length()) {
+ return position;
+ }
+
- u_strToUTF8(NULL, 0, &utf8_position, str, position, &status);
++ u_strToUTF8(nullptr, 0, &utf8_position, str.getBuffer(), position, &status);
+ if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) {
+ return position;
+ }
+
+ return utf8_position;
+ }
+
/* {{{ Parse a number. */
-PHP_FUNCTION( numfmt_parse )
+U_CFUNC PHP_FUNCTION( numfmt_parse )
{
zend_long type = FORMAT_TYPE_DOUBLE;
- UChar* sstr = NULL;
- int32_t sstr_len = 0;
char* str = NULL;
size_t str_len;
- int32_t val32, position = 0;
- int64_t val64;
- double val_double;
- int32_t* position_p = NULL;
+ int32_t position = 0;
zval *zposition = NULL;
char *oldlocale;
FORMATTER_METHOD_INIT_VARS;
@@@ -62,9 -95,12 +99,12 @@@
FORMATTER_METHOD_FETCH_OBJECT;
/* Convert given string to UTF-16. */
- intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
+ icu::UnicodeString ustr;
+ intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );
+ if (zposition && !numfmt_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) {
- efree(sstr);
+ INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset");
+ }
#if ICU_LOCALE_BUG && defined(LC_NUMERIC)
/* need to copy here since setlocale may change it later */
@@@ -122,6 -141,7 +162,7 @@@
}
if (zposition) {
- position = numfmt_utf16_offset_to_utf8(sstr, sstr_len, position);
++ position = numfmt_utf16_offset_to_utf8(ustr, position);
ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
}
@@@ -156,39 -186,32 +197,43 @@@ U_CFUNC PHP_FUNCTION( numfmt_parse_curr
FORMATTER_METHOD_FETCH_OBJECT;
/* Convert given string to UTF-16. */
- intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
+ icu::UnicodeString ustr;
+ intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );
- if(zposition) {
- position = (int32_t) zval_get_long(zposition);
+ if (zposition) {
+ zend_long long_position = zval_get_long(zposition);
+ if (UNEXPECTED(long_position < INT32_MIN || long_position > INT32_MAX)) {
+ zend_argument_value_error(hasThis() ? 3 : 4, "must be between %d and %d", INT32_MIN, INT32_MAX);
+ RETURN_THROWS();
+ }
+ position = (int32_t) long_position;
+ if (!numfmt_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) {
- efree(sstr);
+ INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset");
+ }
- position_p = &position;
}
- number = unum_parseDoubleCurrency(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, currency, &INTL_DATA_ERROR_CODE(nfo));
+ icu::ParsePosition pp(position);
+ std::unique_ptr<icu::CurrencyAmount> currAmt(FORMATTER_OBJECT(nfo)->parseCurrency(ustr, pp));
+
+ if (currAmt == nullptr || pp.getErrorIndex() >= 0) {
+ INTL_DATA_ERROR_CODE(nfo) = U_PARSE_ERROR;
+ INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
+ }
+
if(zposition) {
- ZEND_TRY_ASSIGN_REF_LONG(zposition, pp.getIndex());
- position = numfmt_utf16_offset_to_utf8(sstr, sstr_len, position);
++ position = numfmt_utf16_offset_to_utf8(ustr, pp.getIndex());
+ ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
}
- if (sstr) {
- efree(sstr);
- }
- INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
+
+ const double number = currAmt->getNumber().getDouble(INTL_DATA_ERROR_CODE(nfo));
/* Convert parsed currency to UTF-8 and pass it back to caller. */
- u8str = intl_convert_utf16_to_utf8(currency, u_strlen(currency), &INTL_DATA_ERROR_CODE(nfo));
+ icu::UnicodeString ucurrency(currAmt->getISOCurrency());
+
+ zend_string *u8str = intl_charFromString(ucurrency, &INTL_DATA_ERROR_CODE(nfo));
INTL_METHOD_CHECK_STATUS( nfo, "Currency conversion to UTF-8 failed" );
- ZEND_TRY_ASSIGN_REF_NEW_STR(zcurrency, u8str);
+ ZEND_TRY_ASSIGN_REF_STR(zcurrency, u8str);
RETVAL_DOUBLE( number );
}