Commit 0942ce474c8 for php

commit 0942ce474c853337dc834b4347435cd3930c1b1a
Author: Kamil Tekiela <tekiela246@gmail.com>
Date:   Tue Nov 3 11:55:13 2026 +0000

    ext/mbstring: Optimize valid UTF-8 in mb_substr() (#24080)

diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c
index 20e9dacaade..2182ae53ed2 100644
--- a/ext/mbstring/mbstring.c
+++ b/ext/mbstring/mbstring.c
@@ -1908,7 +1908,9 @@ static inline bool php_mb_is_no_encoding_utf8(enum mbfl_no_encoding no_enc)
 	return (no_enc >= mbfl_no_encoding_utf8 && no_enc <= mbfl_no_encoding_utf8_sb);
 }

-static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *end, ssize_t offset) {
+#define IS_UTF8_CONTINUATION_BYTE(c) (((c) & 0xC0) == 0x80)
+
+static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *end, ssize_t offset, bool is_valid_utf8) {
 	if (offset < 0) {
 		unsigned char *pos = end;
 		while (offset < 0) {
@@ -1917,7 +1919,7 @@ static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *
 			}

 			unsigned char c = *--pos;
-			if (c < 0x80 || (c & 0xC0) != 0x80) {
+			if (!IS_UTF8_CONTINUATION_BYTE(c)) {
 				offset++;
 			}
 		}
@@ -1925,6 +1927,21 @@ static unsigned char* offset_to_pointer_utf8(unsigned char *str, unsigned char *
 	} else {
 		const unsigned char *u8_tbl = mbfl_encoding_utf8.mblen_table;
 		unsigned char *pos = str;
+		if (is_valid_utf8) {
+			while (offset > 256 && (size_t)(end - pos) >= 256) {
+				offset -= mb_fast_strlen_utf8(pos, 256);
+				pos += 256;
+			}
+			for (; pos < end; pos++) {
+				if (!IS_UTF8_CONTINUATION_BYTE(*pos)) {
+					if (offset == 0) {
+						return pos;
+					}
+					offset--;
+				}
+			}
+			return offset == 0 ? end : NULL;
+		}
 		while (offset-- > 0) {
 			if (pos >= end) {
 				return NULL;
@@ -1957,7 +1974,7 @@ static size_t mb_find_strpos(zend_string *haystack, zend_string *needle, const m
 		needle_u8 = needle;
 	}

-	offset_pointer = offset_to_pointer_utf8((unsigned char*)ZSTR_VAL(haystack_u8), (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8), offset);
+	offset_pointer = offset_to_pointer_utf8((unsigned char*)ZSTR_VAL(haystack_u8), (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8), offset, ZSTR_IS_VALID_UTF8(haystack_u8));
 	if (!offset_pointer) {
 		result = MBFL_ERROR_OFFSET;
 		goto out;
@@ -1975,7 +1992,7 @@ static size_t mb_find_strpos(zend_string *haystack, zend_string *needle, const m
 		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_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);
+		offset_pointer = offset_to_pointer_utf8(offset_pointer, (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8), needle_len, ZSTR_IS_VALID_UTF8(haystack_u8));
 		if (!offset_pointer) {
 			offset_pointer = (unsigned char*)ZSTR_VAL(haystack_u8) + ZSTR_LEN(haystack_u8);
 		}
@@ -2214,6 +2231,27 @@ static zend_string* mb_get_substr(zend_string *input, size_t from, size_t len, c
 		return zend_string_init_fast((const char*)in, len);
 	}

+	if (enc == &mbfl_encoding_utf8 && ZSTR_IS_VALID_UTF8(input)) {
+		/* Valid UTF-8 can be sliced on codepoint boundaries without decoding it */
+		unsigned char *end = in + in_len;
+		unsigned char *start = offset_to_pointer_utf8(in, end, from, true);
+		if (!start) {
+			return zend_empty_string;
+		}
+		unsigned char *stop = end;
+		if (len < (size_t)(end - start)) {
+			stop = offset_to_pointer_utf8(start, end, len, true);
+			if (!stop) {
+				stop = end;
+			}
+		}
+		zend_string *result = zend_string_init_fast((const char*)start, stop - start);
+		if (!ZSTR_IS_INTERNED(result)) {
+			GC_ADD_FLAGS(result, IS_STR_VALID_UTF8);
+		}
+		return result;
+	}
+
 	return mb_get_substr_slow(in, in_len, from, len, enc);
 }