[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

Alex Dowad <[email protected]> Thu, 23 Jul 2026 05:53:57 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Alex Dowad (alexdowad)
Date: 2026-07-23T14:53:28+09:00

Commit: https://github.com/php/php-src/commit/e0a99401346c3f1a1d578d5d5bbab0606543c657
Raw diff: https://github.com/php/php-src/commit/e0a99401346c3f1a1d578d5d5bbab0606543c657.diff

Merge branch 'PHP-8.4' into PHP-8.5

Changed paths:
  A  ext/mbstring/tests/gh22779.phpt
  M  NEWS
  M  ext/mbstring/mbstring.c


Diff:

diff --git a/NEWS b/NEWS
index 0d4d5dfa1670..6445c30f1b48 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-22763 (JIT fails to clear ZREG_TYPE_ONLY after setting reg).
     (Arnaud)
 
+- MBString:
+  . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative
+    offset in a non-UTF-8 encoding). (Eyüp Can Akman)
+
 - Sockets:
   . Fixed socket_set_option() validation error messages for UDP_SEGMENT and
     SO_LINGER options. (Weilin Du)
diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c
index fad2bf3fa3d9..0979231ef286 100644
--- a/ext/mbstring/mbstring.c
+++ b/ext/mbstring/mbstring.c
@@ -1937,7 +1937,7 @@ static size_t mb_find_strpos(zend_string *haystack, zend_string *needle, const m
 	} else if (offset >= 0) {
 		found_pos = zend_memnrstr((const char*)offset_pointer, ZSTR_VAL(needle_u8), ZSTR_LEN(needle_u8), ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8));
 	} else {
-		size_t needle_len = pointer_to_offset_utf8((unsigned char*)ZSTR_VAL(needle), (unsigned char*)ZSTR_VAL(needle) + ZSTR_LEN(needle));
+		size_t needle_len = pointer_to_offset_utf8((unsigned char*)ZSTR_VAL(needle_u8), (unsigned char*)ZSTR_VAL(needle_u8) + ZSTR_LEN(needle_u8));
 		offset_pointer = offset_to_pointer_utf8(offset_pointer, (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8), needle_len);
 		if (!offset_pointer) {
 			offset_pointer = (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8);
diff --git a/ext/mbstring/tests/gh22779.phpt b/ext/mbstring/tests/gh22779.phpt
new file mode 100644
index 000000000000..2983f4e1194a
--- /dev/null
+++ b/ext/mbstring/tests/gh22779.phpt
@@ -0,0 +1,34 @@
+--TEST--
+GH-22779: mb_strrpos() wrong result for a negative offset in a non-UTF-8 encoding
+--EXTENSIONS--
+mbstring
+--FILE--
+<?php
+/* mb_strrpos() with a negative offset must return the correct position in non-UTF-8 encodings. */
+$haystack = "\xA9\xA9X";
+$needle = "\xA9";
+foreach ([-1, -2, -3] as $offset) {
+    var_dump(mb_strrpos($haystack, $needle, $offset, 'ISO-8859-1'));
+}
+var_dump(mb_strrpos("X\xA9", "\xA9", -1, 'ISO-8859-1'));
+var_dump(mb_strrpos("\x95Z\x95Z", "\x95", -1, 'Windows-1252'));
+var_dump(mb_strrpos("\x95Z\x95Z", "\x95", -2, 'Windows-1252'));
+// Two-byte needle in a single-byte encoding.
+var_dump(mb_strrpos("\xA9\xB0\xA9\xB0Z", "\xA9\xB0", -2, 'ISO-8859-1'));
+// Multibyte: "ああA" in Shift_JIS, needle "あ" (\x82\xA0).
+var_dump(mb_strrpos("\x82\xA0\x82\xA0A", "\x82\xA0", -2, 'SJIS'));
+var_dump(mb_strrpos("\x82\xA0\x82\xA0A", "\x82\xA0", -3, 'SJIS'));
+// UTF-16: an ASCII needle miscounts the other way.
+var_dump(mb_strrpos("\x00A\x00X\x00A", "\x00A", -2, 'UTF-16BE'));
+?>
+--EXPECT--
+int(1)
+int(1)
+int(0)
+int(1)
+int(2)
+int(2)
+int(2)
+int(1)
+int(0)
+int(0)