Commit 22a2c2ed2f8 for php.net

commit 22a2c2ed2f8efeb20aa913800665271dffa1bd13
Author: Weilin Du <weilindu@php.net>
Date:   Thu Jun 25 14:37:20 2026 +0800

    Zend: Introduce inline helpers for raw C-string concatenation (#21607)

    This commit introduces three new zend_always_inline helpers to
    Zend/zend_string.hfor concatenating raw C strings (char *),
    which is discussed before in #21597.

    - zend_str_append_char_to_raw
    - zend_str_concat_to_raw
    - zend_str_concat3_to_raw

    Now, several places in the codebase has manual emalloc +
    memcpy + \0 boilerplate to generate temporary char *. This
    manual approach is verbose, and harder to read. In some cases
    when we actually need a zend_string instead of char *, we
    already has some refactors (#21564 and #21567) by using
    zend_string_concat* API, but when it comes to char *, the API
    forces a 24-byte struct allocation, which is immediately
    discarded, and will unnecessarily lower performance. In order
    to solve this, I think we can introduce three APIs focusing on
    dealing with chars * refactoring.

diff --git a/Zend/zend_string.h b/Zend/zend_string.h
index 930d733307f..971902cabd2 100644
--- a/Zend/zend_string.h
+++ b/Zend/zend_string.h
@@ -22,8 +22,14 @@
 #include "zend_gc.h"
 #include "zend_alloc.h"

+#include "zend_errors.h"
+
 BEGIN_EXTERN_C()

+ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
+
+#include "zend_multiply.h"
+
 typedef void (*zend_string_copy_storage_func_t)(void);
 typedef zend_string *(ZEND_FASTCALL *zend_new_interned_string_func_t)(zend_string *str);
 typedef zend_string *(ZEND_FASTCALL *zend_string_init_interned_func_t)(const char *str, size_t size, bool permanent);
@@ -331,6 +337,48 @@ static zend_always_inline zend_string *zend_string_safe_realloc(zend_string *s,
 	return ret;
 }

+static zend_always_inline char *zend_cstr_append_char(const char *str, size_t len, char c) {
+	char *res = (char *)safe_emalloc(len, 1, 2);
+	if (len > 0) {
+		memcpy(res, str, len);
+	}
+	res[len] = c;
+	res[len + 1] = '\0';
+	return res;
+}
+
+static zend_always_inline char *zend_cstr_concat(const char *s1, size_t len1, const char *s2, size_t len2) {
+	size_t size = zend_safe_address_guarded(1, len1, len2);
+	size = zend_safe_address_guarded(1, size, 1);
+	char *res = (char *)emalloc(size);
+	if (len1 > 0) {
+		memcpy(res, s1, len1);
+	}
+	if (len2 > 0) {
+		memcpy(res + len1, s2, len2);
+	}
+	res[len1 + len2] = '\0';
+	return res;
+}
+
+static zend_always_inline char *zend_cstr_concat3(const char *s1, size_t len1, const char *s2, size_t len2, const char *s3, size_t len3) {
+	size_t size = zend_safe_address_guarded(1, len1, len2);
+	size = zend_safe_address_guarded(1, size, len3);
+	size = zend_safe_address_guarded(1, size, 1);
+	char *res = (char *)emalloc(size);
+	if (len1 > 0) {
+		memcpy(res, s1, len1);
+	}
+	if (len2 > 0) {
+		memcpy(res + len1, s2, len2);
+	}
+	if (len3 > 0) {
+		memcpy(res + len1 + len2, s3, len3);
+	}
+	res[len1 + len2 + len3] = '\0';
+	return res;
+}
+
 static zend_always_inline void zend_string_free(zend_string *s)
 {
 	if (!ZSTR_IS_INTERNED(s)) {