Commit 3913e039656 for php

commit 3913e039656d82d96b4d7062f439289c4dd64268
Merge: 2290177565d 906356642d8
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Oct 5 09:01:30 2026 -0400

    Merge branch 'PHP-8.5' into PHP-8.6

    * PHP-8.5:
      ext/intl: Use byte offsets in IntlDateFormatter parsing

diff --cc NEWS
index 30869309af8,4d949ee7420..e4ce7f3f7cf
--- a/NEWS
+++ b/NEWS
@@@ -56,7 -77,25 +56,10 @@@ PH
      haystacks. (Weilin Du)
    . Fixed grapheme_strrpos() and grapheme_strripos() skipping overlapping matches
      when using a negative offset. (Weilin Du)
+   . Fixed IntlDateFormatter::parse(), localtime() and parseToCalendar()
+     treating the offset as UTF-16 code units instead of bytes.
+     (Ilia Alshanetsky)

 -- Lexbor:
 -  . Merge patches lexbor/lexbor@8a14bc0 and lexbor/lexbor@f67ce4b, fixing a
 -    heap buffer overflow in :lexbor-contains() parsing and buffer overflows
 -    in malformed decode replay. (alexandre-daubois)
 -
 -- MBString:
 -  . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
 -    a truncated UTF-8 sequence). (Lazizbek Ergashev)
 -  . Fixed mbstring functions emitting surrogates in UTF-8 output and flagging
 -    it as valid UTF-8. (Nicolas Grekas)
 -
 -- MySQLi:
 -  . Fix GH-22854: Fixed failed assertion when accessing mysqli property after
 -    failed reconnection. (Kamil Tekiela)
 -
  - MySQLnd:
    . Fixed field_count not resetting on OK packet. (Kamil Tekiela)
    . Fixed memory leak when closing a prepared statement after its connection
diff --cc ext/intl/formatter/formatter_parse.cpp
index 7a233f012d4,2b53ea33a68..db7463030b7
--- a/ext/intl/formatter/formatter_parse.cpp
+++ b/ext/intl/formatter/formatter_parse.cpp
@@@ -16,65 -16,29 +16,30 @@@
  #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"
 +
 +extern "C" {
 +#include "php_intl.h"
+ #include "intl_convert.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(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 icu::UnicodeString &str, int32_t position)
- {
- 	int32_t utf8_position;
- 	UErrorCode status = U_ZERO_ERROR;
-
- 	if (position < 0 || position > str.length()) {
- 		return position;
- 	}
-
- 	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;
@@@ -99,10 -59,10 +64,10 @@@
  	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))) {
+ 	if (zposition && !intl_convert_utf8_offset_to_utf16(str, str_len, &position, &INTL_DATA_ERROR_CODE(nfo))) {
 -		efree(sstr);
  		INTL_METHOD_CHECK_STATUS(nfo, "Invalid UTF-8 offset");
  	}

@@@ -162,7 -105,7 +127,7 @@@
  	}

  	if (zposition) {
- 		position = numfmt_utf16_offset_to_utf8(ustr, position);
 -		position = intl_convert_utf16_offset_to_utf8(sstr, sstr_len, position);
++		position = intl_convert_utf16_offset_to_utf8(ustr.getBuffer(), ustr.length(), position);
  		ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
  	}

@@@ -197,43 -150,32 +162,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))) {
+ 		if (!intl_convert_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) {
- 		position = numfmt_utf16_offset_to_utf8(ustr, pp.getIndex());
 -		position = intl_convert_utf16_offset_to_utf8(sstr, sstr_len, position);
++		position = intl_convert_utf16_offset_to_utf8(ustr.getBuffer(), ustr.length(), 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 );
  }