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)) {