From dbfc9628fe72c1a57fe79990884141bdbb68f7ca Mon Sep 17 00:00:00 2001 From: Ayke Halder Date: Sun, 8 Nov 2026 19:38:44 +0100 Subject: [PATCH 1/2] Read and add flags in an atomic way --- Zend/zend_string.h | 5 ++++- Zend/zend_types.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 5bd805aa8ddb..2d821522d881 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -85,7 +85,10 @@ static zend_always_inline bool ZSTR_IS_INTERNED(const zend_string *s) { } static inline bool ZSTR_IS_VALID_UTF8(const zend_string *s) { - return GC_FLAGS(s) & IS_STR_VALID_UTF8; + /* If string is persistent the check for flags must be atomic. + * Not checking for persistence first, as this operation must also be run atomic. + * So uses atomic operation directly to check valid utf8 instead. */ + return GC_FLAGS_ATOMIC(s) & IS_STR_VALID_UTF8; } /* These are properties, encoded as flags, that will hold on the resulting string diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 465798a4e6ce..e489011da87e 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -788,6 +788,49 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { GC_TYPE_INFO(p) &= ~((flags) << GC_FLAGS_SHIFT); \ } while (0) +#if defined(__cplusplus) || !defined(ATOMIC_INT_LOCK_FREE) || ATOMIC_INT_LOCK_FREE != 2 + /* Fallback path for C++ compilation units or non-lock-free architectures */ + #if defined(_MSC_VER) + #include + #define GC_FLAGS_ATOMIC(p) zval_gc_flags(*(const volatile uint32_t *)&GC_TYPE_INFO(p)) + #define GC_ADD_FLAGS_ATOMIC(p, flags) do { \ + _InterlockedOr((volatile LONG *)&GC_TYPE_INFO(p), ((uint32_t)(flags)) << GC_FLAGS_SHIFT); \ + } while (0) + #else + #define GC_FLAGS_ATOMIC(p) zval_gc_flags(*(const volatile uint32_t *)&GC_TYPE_INFO(p)) + #define GC_ADD_FLAGS_ATOMIC(p, flags) do { \ + uint32_t _old = *(volatile uint32_t *)&GC_TYPE_INFO(p); \ + uint32_t _new; \ + do { \ + _new = _old | (((uint32_t)(flags)) << GC_FLAGS_SHIFT); \ + } while (!_VAL_ATOMIC_CAS(&GC_TYPE_INFO(p), &_old, _new)); \ + } while (0) + #endif +#else + /* Native C11 lock-free atomic path */ + #if defined(_MSC_VER) + #include + #define GC_FLAGS_ATOMIC(p) zval_gc_flags(*(const volatile uint32_t *)&GC_TYPE_INFO(p)) + #define GC_ADD_FLAGS_ATOMIC(p, flags) \ + _InterlockedOr((volatile LONG *)&GC_TYPE_INFO(p), ((uint32_t)(flags)) << GC_FLAGS_SHIFT) + #elif defined(__GNUC__) || defined(__clang__) + #define GC_FLAGS_ATOMIC(p) zval_gc_flags(__atomic_load_n(&GC_TYPE_INFO(p), __ATOMIC_RELAXED)) + #define GC_ADD_FLAGS_ATOMIC(p, flags) \ + __atomic_fetch_or(&GC_TYPE_INFO(p), ((uint32_t)(flags)) << GC_FLAGS_SHIFT, __ATOMIC_RELAXED) + #else + #include + #define GC_FLAGS_ATOMIC(p) zval_gc_flags( \ + atomic_load_explicit((_Atomic(uint32_t) *)&GC_TYPE_INFO(p), memory_order_relaxed) \ + ) + #define GC_ADD_FLAGS_ATOMIC(p, flags) \ + atomic_fetch_or_explicit( \ + (_Atomic(uint32_t) *)&GC_TYPE_INFO(p), \ + ((uint32_t)(flags)) << GC_FLAGS_SHIFT, \ + memory_order_relaxed \ + ) + #endif +#endif + #ifndef ZEND_RC_DEBUG # define ZEND_RC_DEBUG 0 #endif From c33c2ce83851e62beeaa159f96bf913fd83d8f18 Mon Sep 17 00:00:00 2001 From: Ayke Halder Date: Sun, 8 Nov 2026 19:39:32 +0100 Subject: [PATCH 2/2] Flag already interned strings utf8-valid --- ext/mbstring/mbstring.c | 28 +++++++++++++++++++++------- ext/pcre/php_pcre.c | 9 ++++++--- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 16e969a3ea32..84537e6ccbb0 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -2246,7 +2246,9 @@ static zend_string* mb_get_substr(zend_string *input, size_t from, size_t len, c } } zend_string *result = zend_string_init_fast((const char*)start, stop - start); - if (!ZSTR_IS_INTERNED(result)) { + if (ZSTR_IS_INTERNED(result)) { + GC_ADD_FLAGS_ATOMIC(result, IS_STR_VALID_UTF8); + } else { GC_ADD_FLAGS(result, IS_STR_VALID_UTF8); } return result; @@ -2364,8 +2366,12 @@ PHP_FUNCTION(mb_substr_count) } else { unsigned int num_errors = 0; haystack_u8 = mb_fast_convert((unsigned char*)ZSTR_VAL(haystack), ZSTR_LEN(haystack), enc, &mbfl_encoding_utf8, 0, MBFL_OUTPUTFILTER_ILLEGAL_MODE_BADUTF8, &num_errors); - if (!num_errors && !ZSTR_IS_INTERNED(haystack)) { - GC_ADD_FLAGS(haystack, IS_STR_VALID_UTF8); + if (!num_errors) { + if (ZSTR_IS_INTERNED(haystack)) { + GC_ADD_FLAGS_ATOMIC(haystack, IS_STR_VALID_UTF8); + } else { + GC_ADD_FLAGS(haystack, IS_STR_VALID_UTF8); + } } } @@ -2374,8 +2380,12 @@ PHP_FUNCTION(mb_substr_count) } else { unsigned int num_errors = 0; needle_u8 = mb_fast_convert((unsigned char*)ZSTR_VAL(needle), ZSTR_LEN(needle), enc, &mbfl_encoding_utf8, 0, MBFL_OUTPUTFILTER_ILLEGAL_MODE_BADUTF8, &num_errors); - if (!num_errors && !ZSTR_IS_INTERNED(needle)) { - GC_ADD_FLAGS(needle, IS_STR_VALID_UTF8); + if (!num_errors) { + if (ZSTR_IS_INTERNED(needle)) { + GC_ADD_FLAGS_ATOMIC(needle, IS_STR_VALID_UTF8); + } else { + GC_ADD_FLAGS(needle, IS_STR_VALID_UTF8); + } } } } else { @@ -5756,8 +5766,12 @@ static bool mb_check_str_encoding(zend_string *str, const mbfl_encoding *encodin return true; } bool result = mb_fast_check_utf8(str); - if (result && !ZSTR_IS_INTERNED(str)) { - GC_ADD_FLAGS(str, IS_STR_VALID_UTF8); + if (result) { + if (ZSTR_IS_INTERNED(str)) { + GC_ADD_FLAGS_ATOMIC(str, IS_STR_VALID_UTF8); + } else if(!ZSTR_IS_INTERNED(str)) { + GC_ADD_FLAGS(str, IS_STR_VALID_UTF8); + } } return result; } else { diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 4f49b6fbf696..c8564f5697da 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -1508,9 +1508,12 @@ PHPAPI void php_pcre_match_impl(pcre_cache_entry *pce, zend_string *subject_str, if (PCRE_G(error_code) == PHP_PCRE_NO_ERROR) { /* If there was no error and we're in /u mode, remember that the string is valid UTF-8. */ - if ((pce->compile_options & PCRE2_UTF) - && !ZSTR_IS_INTERNED(subject_str) && orig_start_offset == 0) { - GC_ADD_FLAGS(subject_str, IS_STR_VALID_UTF8); + if ((pce->compile_options & PCRE2_UTF) && orig_start_offset == 0) { + if (ZSTR_IS_INTERNED(subject_str)) { + GC_ADD_FLAGS_ATOMIC(subject_str, IS_STR_VALID_UTF8); + } else { + GC_ADD_FLAGS(subject_str, IS_STR_VALID_UTF8); + } } RETVAL_LONG(matched);