From cef4d30ac18f47fdb88c9d9a54489d520819f1d5 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 23 Nov 2025 08:57:46 +0000 Subject: [PATCH 1/4] Zend: Preallocate error buffer with capacity tracking Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error. - Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper - Allocate error buffer in zend_startup for NTS builds - Remove redundant NULL checks since buffer is always allocated --- Zend/zend.c | 83 ++++++++++++++++++++--------------- Zend/zend_execute_API.c | 3 +- Zend/zend_globals.h | 10 ++++- ext/opcache/ZendAccelerator.c | 20 ++++----- 4 files changed, 67 insertions(+), 49 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index af0013220f20..43d343e2a25c 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -80,6 +80,8 @@ static char *zend_version_info; static uint32_t zend_version_info_length; #define ZEND_CORE_VERSION_INFO "Zend Engine v" ZEND_VERSION ", Copyright (c) Zend Technologies\n" #define PRINT_ZVAL_INDENT 4 +#define ZEND_ERR_BUF_SIZE(capacity) \ + (XtOffsetOf(struct zend_err_buf, buf) + sizeof(zend_error_info *) * (capacity)) /* true multithread-shared globals */ ZEND_API zend_class_entry *zend_standard_class_def = NULL; @@ -643,6 +645,13 @@ static FILE *zend_fopen_wrapper(zend_string *filename, zend_string **opened_path } /* }}} */ +static void zend_init_errors_buf(zend_executor_globals *eg) +{ + eg->errors = pemalloc(ZEND_ERR_BUF_SIZE(2), true); + eg->errors->capacity = 2; + eg->errors->size = 0; +} + #ifdef ZTS static bool short_tags_default = true; static uint32_t compiler_options_default = ZEND_COMPILE_DEFAULT; @@ -833,8 +842,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ #endif executor_globals->flags = EG_FLAGS_INITIAL; executor_globals->record_errors = false; - executor_globals->num_errors = 0; - executor_globals->errors = NULL; + zend_init_errors_buf(executor_globals); executor_globals->filename_override = NULL; executor_globals->lineno_override = -1; #ifdef ZEND_CHECK_STACK_LIMIT @@ -869,6 +877,8 @@ static void executor_globals_dtor(zend_executor_globals *executor_globals) /* {{ zend_hash_destroy(executor_globals->zend_constants); free(executor_globals->zend_constants); } + + pefree(executor_globals->errors, true); } /* }}} */ @@ -1065,6 +1075,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ zend_init_rsrc_plist(); zend_init_exception_op(); zend_init_call_trampoline_op(); + zend_init_errors_buf(&executor_globals); #endif zend_ini_startup(); @@ -1147,6 +1158,7 @@ zend_result zend_post_startup(void) /* {{{ */ free(EG(zend_constants)); EG(zend_constants) = NULL; + pefree(executor_globals->errors, true); executor_globals_ctor(executor_globals); global_persistent_list = &EG(persistent_list); zend_copy_ini_directives(); @@ -1224,6 +1236,7 @@ void zend_shutdown(void) /* {{{ */ pefree(CG(internal_run_time_cache), 1); CG(internal_run_time_cache) = NULL; } + pefree(EG(errors), true); #endif zend_map_ptr_static_last = 0; zend_map_ptr_static_size = 0; @@ -1446,8 +1459,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( zend_stack delayed_oplines_stack; int type = orig_type & E_ALL; bool orig_record_errors; - uint32_t orig_num_errors; - zend_error_info **orig_errors; + uint32_t orig_num_errors = 0; + uint32_t orig_cap_errors = 0; zend_result res; /* If we're executing a function during SCCP, count any warnings that may be emitted, @@ -1459,11 +1472,11 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } /* Emit any delayed error before handling fatal error */ - if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(num_errors)) { - uint32_t num_errors = EG(num_errors); - zend_error_info **errors = EG(errors); - EG(num_errors) = 0; - EG(errors) = NULL; + if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors->size)) { + uint32_t num_errors = EG(errors->size); + uint32_t cap_errors = EG(errors->capacity); + zend_error_info **errors = EG(errors->buf); + EG(errors->size) = 0; bool orig_record_errors = EG(record_errors); EG(record_errors) = false; @@ -1477,8 +1490,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( EG(user_error_handler_error_reporting) = orig_user_error_handler_error_reporting; EG(record_errors) = orig_record_errors; - EG(num_errors) = num_errors; - EG(errors) = errors; + EG(errors->size) = num_errors; + EG(errors->capacity) = cap_errors; } if (EG(record_errors)) { @@ -1487,12 +1500,14 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( info->lineno = error_lineno; info->filename = zend_string_copy(error_filename); info->message = zend_string_copy(message); - - /* This is very inefficient for a large number of errors. - * Use pow2 realloc if it becomes a problem. */ - EG(num_errors)++; - EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors)); - EG(errors)[EG(num_errors)-1] = info; + EG(errors->size)++; + if (EG(errors->size) >= EG(errors->capacity)) { + // not sure we can get high number of errors so safe `might be` over cautious here + uint32_t capacity = EG(errors->capacity) + (EG(errors->capacity) >> 1); + EG(errors) = safe_perealloc(EG(errors), sizeof(zend_error_info *), capacity, XtOffsetOf(struct zend_err_buf, buf), true); + EG(errors->capacity) = capacity; + } + EG(errors->buf)[EG(errors->size)-1] = info; /* Do not process non-fatal recorded error */ if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) { @@ -1575,17 +1590,18 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } orig_record_errors = EG(record_errors); - orig_num_errors = EG(num_errors); - orig_errors = EG(errors); EG(record_errors) = false; - EG(num_errors) = 0; - EG(errors) = NULL; + + orig_num_errors = EG(errors->size); + orig_cap_errors = EG(errors->capacity); + EG(errors->size) = 0; res = call_user_function(CG(function_table), NULL, &orig_user_error_handler, &retval, 4, params); EG(record_errors) = orig_record_errors; - EG(num_errors) = orig_num_errors; - EG(errors) = orig_errors; + + EG(errors->capacity) = orig_cap_errors; + EG(errors->size) = orig_num_errors; if (res == SUCCESS) { if (Z_TYPE(retval) != IS_UNDEF) { @@ -1780,8 +1796,7 @@ ZEND_API void zend_begin_record_errors(void) { ZEND_ASSERT(!EG(record_errors) && "Error recording already enabled"); EG(record_errors) = true; - EG(num_errors) = 0; - EG(errors) = NULL; + EG(errors->size) = 0; } ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info **errors) @@ -1795,24 +1810,22 @@ ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info ZEND_API void zend_emit_recorded_errors(void) { EG(record_errors) = false; - zend_emit_recorded_errors_ex(EG(num_errors), EG(errors)); + zend_emit_recorded_errors_ex(EG(errors->size), EG(errors->buf)); } ZEND_API void zend_free_recorded_errors(void) { - if (!EG(num_errors)) { + if (!EG(errors->size)) { return; } - for (uint32_t i = 0; i < EG(num_errors); i++) { - zend_error_info *info = EG(errors)[i]; + for (uint32_t i = 0; i < EG(errors->size); i++) { + zend_error_info *info = EG(errors->buf)[i]; zend_string_release(info->filename); zend_string_release(info->message); - efree(info); + efree_size(info, sizeof(zend_error_info)); } - efree(EG(errors)); - EG(errors) = NULL; - EG(num_errors) = 0; + EG(errors->size) = 0; } ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */ @@ -2020,12 +2033,12 @@ ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count } /* }}} */ -#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%d) : %s" +#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%u) : %s" ZEND_API char *zend_make_compiled_string_description(const char *name) /* {{{ */ { const char *cur_filename; - int cur_lineno; + uint32_t cur_lineno; char *compiled_string_description; if (zend_is_compiling()) { diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index dbd2a9039cfc..b5e0ab779702 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -192,8 +192,7 @@ void init_executor(void) /* {{{ */ EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL; EG(record_errors) = false; - EG(num_errors) = 0; - EG(errors) = NULL; + EG(errors->size) = 0; EG(filename_override) = NULL; EG(lineno_override) = -1; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 31f54cd8284b..9facf9bbe844 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -74,6 +74,7 @@ typedef struct _zend_vm_stack *zend_vm_stack; typedef struct _zend_ini_entry zend_ini_entry; typedef struct _zend_fiber_context zend_fiber_context; typedef struct _zend_fiber zend_fiber; +typedef struct _zend_error_info zend_error_info; typedef enum { ZEND_MEMOIZE_NONE, @@ -81,6 +82,12 @@ typedef enum { ZEND_MEMOIZE_FETCH, } zend_memoize_mode; +struct zend_err_buf { + uint32_t size; + uint32_t capacity; + zend_error_info *buf[1]; +}; + struct _zend_compiler_globals { zend_stack loop_var_stack; @@ -298,8 +305,6 @@ struct _zend_executor_globals { * and their processing is delayed until zend_emit_recorded_errors() * is called or a fatal diagnostic is emitted. */ bool record_errors; - uint32_t num_errors; - zend_error_info **errors; /* Override filename or line number of thrown errors and exceptions */ zend_string *filename_override; @@ -322,6 +327,7 @@ struct _zend_executor_globals { HashTable callable_convert_cache; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; + struct zend_err_buf *errors; }; #define EG_FLAGS_INITIAL (0) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 3d6923d12158..2905a79a7c50 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1968,8 +1968,8 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int if (persistent_script) { if (ZCG(accel_directives).record_warnings) { - persistent_script->num_warnings = EG(num_errors); - persistent_script->warnings = EG(errors); + persistent_script->num_warnings = EG(errors->size); + persistent_script->warnings = EG(errors->buf); } from_memory = false; @@ -2193,8 +2193,8 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) from_shared_memory = false; if (persistent_script) { if (ZCG(accel_directives).record_warnings) { - persistent_script->num_warnings = EG(num_errors); - persistent_script->warnings = EG(errors); + persistent_script->num_warnings = EG(errors->size); + persistent_script->warnings = EG(errors->buf); } /* See GH-17246: we disable GC so that user code cannot be executed during the optimizer run. */ @@ -2421,7 +2421,7 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, } ZCG(current_persistent_script) = &dummy; zend_persist_class_entry_calc(ce); - zend_persist_warnings_calc(EG(num_errors), EG(errors)); + zend_persist_warnings_calc(EG(errors->size), EG(errors->buf)); size = dummy.size; zend_shared_alloc_clear_xlat_table(); @@ -2491,8 +2491,8 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, JIT_G(on) = jit_on_old; #endif - entry->num_warnings = EG(num_errors); - entry->warnings = zend_persist_warnings(EG(num_errors), EG(errors)); + entry->num_warnings = EG(errors->size); + entry->warnings = zend_persist_warnings(EG(errors->size), EG(errors->buf)); entry->next = proto->inheritance_cache; proto->inheritance_cache = entry; @@ -4123,9 +4123,9 @@ static void preload_link(void) /* Remember the last error. */ zend_error_cb = orig_error_cb; EG(record_errors) = false; - ZEND_ASSERT(EG(num_errors) > 0); - zend_hash_update_ptr(&errors, key, EG(errors)[EG(num_errors)-1]); - EG(num_errors)--; + ZEND_ASSERT(EG(errors->size) > 0); + zend_hash_update_ptr(&errors, key, EG(errors->buf)[EG(errors->size)-1]); + EG(errors->size)--; } zend_end_try(); CG(in_compilation) = false; CG(compiled_filename) = NULL; From 155e86f8bab427dc5b2420d2bb8cb5cfd0685b21 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 24 Jan 2026 22:47:54 +0000 Subject: [PATCH 2/4] review --- Zend/zend.c | 1 - Zend/zend_globals.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 43d343e2a25c..d4e69b353cf7 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1502,7 +1502,6 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( info->message = zend_string_copy(message); EG(errors->size)++; if (EG(errors->size) >= EG(errors->capacity)) { - // not sure we can get high number of errors so safe `might be` over cautious here uint32_t capacity = EG(errors->capacity) + (EG(errors->capacity) >> 1); EG(errors) = safe_perealloc(EG(errors), sizeof(zend_error_info *), capacity, XtOffsetOf(struct zend_err_buf, buf), true); EG(errors->capacity) = capacity; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 9facf9bbe844..89ef710da370 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -305,6 +305,7 @@ struct _zend_executor_globals { * and their processing is delayed until zend_emit_recorded_errors() * is called or a fatal diagnostic is emitted. */ bool record_errors; + struct zend_err_buf *errors; /* Override filename or line number of thrown errors and exceptions */ zend_string *filename_override; @@ -327,7 +328,6 @@ struct _zend_executor_globals { HashTable callable_convert_cache; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; - struct zend_err_buf *errors; }; #define EG_FLAGS_INITIAL (0) From 49c8d3414956b92d97e43087c9a2914198e3be87 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 30 Mar 2026 12:53:26 +0100 Subject: [PATCH 3/4] Address review feedback: embed zend_err_buf struct directly in EG. - Change EG(errors) from pointer to embedded struct (arnaud-lb) - Replace flexible array member with plain pointer (arnaud-lb) - Use EG(errors).field syntax instead of EG(errors->field) (ndossche) - Remove persistent allocation, use erealloc for the inner array - Remove zend_init_errors_buf/ZEND_ERR_BUF_SIZE, zeroing suffices - Free EG(errors).errors buffer in zend_free_recorded_errors - Save/restore full EG(errors) state in error handler paths - Revert unrelated int->uint32_t change (arnaud-lb) --- Zend/zend.c | 76 +++++++++++++++-------------------- Zend/zend_execute_API.c | 2 +- Zend/zend_globals.h | 4 +- ext/opcache/ZendAccelerator.c | 20 ++++----- 4 files changed, 46 insertions(+), 56 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index d4e69b353cf7..275a10b50359 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -80,8 +80,6 @@ static char *zend_version_info; static uint32_t zend_version_info_length; #define ZEND_CORE_VERSION_INFO "Zend Engine v" ZEND_VERSION ", Copyright (c) Zend Technologies\n" #define PRINT_ZVAL_INDENT 4 -#define ZEND_ERR_BUF_SIZE(capacity) \ - (XtOffsetOf(struct zend_err_buf, buf) + sizeof(zend_error_info *) * (capacity)) /* true multithread-shared globals */ ZEND_API zend_class_entry *zend_standard_class_def = NULL; @@ -645,12 +643,6 @@ static FILE *zend_fopen_wrapper(zend_string *filename, zend_string **opened_path } /* }}} */ -static void zend_init_errors_buf(zend_executor_globals *eg) -{ - eg->errors = pemalloc(ZEND_ERR_BUF_SIZE(2), true); - eg->errors->capacity = 2; - eg->errors->size = 0; -} #ifdef ZTS static bool short_tags_default = true; @@ -842,7 +834,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ #endif executor_globals->flags = EG_FLAGS_INITIAL; executor_globals->record_errors = false; - zend_init_errors_buf(executor_globals); + memset(&executor_globals->errors, 0, sizeof(executor_globals->errors)); executor_globals->filename_override = NULL; executor_globals->lineno_override = -1; #ifdef ZEND_CHECK_STACK_LIMIT @@ -877,8 +869,6 @@ static void executor_globals_dtor(zend_executor_globals *executor_globals) /* {{ zend_hash_destroy(executor_globals->zend_constants); free(executor_globals->zend_constants); } - - pefree(executor_globals->errors, true); } /* }}} */ @@ -1075,7 +1065,6 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ zend_init_rsrc_plist(); zend_init_exception_op(); zend_init_call_trampoline_op(); - zend_init_errors_buf(&executor_globals); #endif zend_ini_startup(); @@ -1158,7 +1147,6 @@ zend_result zend_post_startup(void) /* {{{ */ free(EG(zend_constants)); EG(zend_constants) = NULL; - pefree(executor_globals->errors, true); executor_globals_ctor(executor_globals); global_persistent_list = &EG(persistent_list); zend_copy_ini_directives(); @@ -1236,7 +1224,6 @@ void zend_shutdown(void) /* {{{ */ pefree(CG(internal_run_time_cache), 1); CG(internal_run_time_cache) = NULL; } - pefree(EG(errors), true); #endif zend_map_ptr_static_last = 0; zend_map_ptr_static_size = 0; @@ -1461,6 +1448,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( bool orig_record_errors; uint32_t orig_num_errors = 0; uint32_t orig_cap_errors = 0; + zend_error_info **orig_errors = NULL; zend_result res; /* If we're executing a function during SCCP, count any warnings that may be emitted, @@ -1472,11 +1460,11 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( } /* Emit any delayed error before handling fatal error */ - if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors->size)) { - uint32_t num_errors = EG(errors->size); - uint32_t cap_errors = EG(errors->capacity); - zend_error_info **errors = EG(errors->buf); - EG(errors->size) = 0; + if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors).size) { + uint32_t num_errors = EG(errors).size; + uint32_t cap_errors = EG(errors).capacity; + zend_error_info **errors = EG(errors).errors; + EG(errors).size = 0; bool orig_record_errors = EG(record_errors); EG(record_errors) = false; @@ -1490,8 +1478,9 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( EG(user_error_handler_error_reporting) = orig_user_error_handler_error_reporting; EG(record_errors) = orig_record_errors; - EG(errors->size) = num_errors; - EG(errors->capacity) = cap_errors; + EG(errors).size = num_errors; + EG(errors).capacity = cap_errors; + EG(errors).errors = errors; } if (EG(record_errors)) { @@ -1500,13 +1489,13 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( info->lineno = error_lineno; info->filename = zend_string_copy(error_filename); info->message = zend_string_copy(message); - EG(errors->size)++; - if (EG(errors->size) >= EG(errors->capacity)) { - uint32_t capacity = EG(errors->capacity) + (EG(errors->capacity) >> 1); - EG(errors) = safe_perealloc(EG(errors), sizeof(zend_error_info *), capacity, XtOffsetOf(struct zend_err_buf, buf), true); - EG(errors->capacity) = capacity; + EG(errors).size++; + if (EG(errors).size > EG(errors).capacity) { + uint32_t capacity = EG(errors).capacity ? EG(errors).capacity + (EG(errors).capacity >> 1) : 2; + EG(errors).errors = erealloc(EG(errors).errors, sizeof(zend_error_info *) * capacity); + EG(errors).capacity = capacity; } - EG(errors->buf)[EG(errors->size)-1] = info; + EG(errors).errors[EG(errors).size - 1] = info; /* Do not process non-fatal recorded error */ if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) { @@ -1591,16 +1580,18 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( orig_record_errors = EG(record_errors); EG(record_errors) = false; - orig_num_errors = EG(errors->size); - orig_cap_errors = EG(errors->capacity); - EG(errors->size) = 0; + orig_num_errors = EG(errors).size; + orig_cap_errors = EG(errors).capacity; + orig_errors = EG(errors).errors; + memset(&EG(errors), 0, sizeof(EG(errors))); res = call_user_function(CG(function_table), NULL, &orig_user_error_handler, &retval, 4, params); EG(record_errors) = orig_record_errors; - EG(errors->capacity) = orig_cap_errors; - EG(errors->size) = orig_num_errors; + EG(errors).capacity = orig_cap_errors; + EG(errors).size = orig_num_errors; + EG(errors).errors = orig_errors; if (res == SUCCESS) { if (Z_TYPE(retval) != IS_UNDEF) { @@ -1795,7 +1786,7 @@ ZEND_API void zend_begin_record_errors(void) { ZEND_ASSERT(!EG(record_errors) && "Error recording already enabled"); EG(record_errors) = true; - EG(errors->size) = 0; + EG(errors).size = 0; } ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info **errors) @@ -1809,22 +1800,21 @@ ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info ZEND_API void zend_emit_recorded_errors(void) { EG(record_errors) = false; - zend_emit_recorded_errors_ex(EG(errors->size), EG(errors->buf)); + zend_emit_recorded_errors_ex(EG(errors).size, EG(errors).errors); } ZEND_API void zend_free_recorded_errors(void) { - if (!EG(errors->size)) { - return; - } - - for (uint32_t i = 0; i < EG(errors->size); i++) { - zend_error_info *info = EG(errors->buf)[i]; + for (uint32_t i = 0; i < EG(errors).size; i++) { + zend_error_info *info = EG(errors).errors[i]; zend_string_release(info->filename); zend_string_release(info->message); efree_size(info, sizeof(zend_error_info)); } - EG(errors->size) = 0; + if (EG(errors).errors) { + efree(EG(errors).errors); + } + memset(&EG(errors), 0, sizeof(EG(errors))); } ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */ @@ -2032,12 +2022,12 @@ ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count } /* }}} */ -#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%u) : %s" +#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%d) : %s" ZEND_API char *zend_make_compiled_string_description(const char *name) /* {{{ */ { const char *cur_filename; - uint32_t cur_lineno; + int cur_lineno; char *compiled_string_description; if (zend_is_compiling()) { diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index b5e0ab779702..30ed4f5914cf 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -192,7 +192,7 @@ void init_executor(void) /* {{{ */ EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL; EG(record_errors) = false; - EG(errors->size) = 0; + memset(&EG(errors), 0, sizeof(EG(errors))); EG(filename_override) = NULL; EG(lineno_override) = -1; diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 89ef710da370..db5d7518470a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -85,7 +85,7 @@ typedef enum { struct zend_err_buf { uint32_t size; uint32_t capacity; - zend_error_info *buf[1]; + zend_error_info **errors; }; struct _zend_compiler_globals { @@ -305,7 +305,7 @@ struct _zend_executor_globals { * and their processing is delayed until zend_emit_recorded_errors() * is called or a fatal diagnostic is emitted. */ bool record_errors; - struct zend_err_buf *errors; + struct zend_err_buf errors; /* Override filename or line number of thrown errors and exceptions */ zend_string *filename_override; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 2905a79a7c50..7aa0e67c24a5 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1968,8 +1968,8 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int if (persistent_script) { if (ZCG(accel_directives).record_warnings) { - persistent_script->num_warnings = EG(errors->size); - persistent_script->warnings = EG(errors->buf); + persistent_script->num_warnings = EG(errors).size; + persistent_script->warnings = EG(errors).errors; } from_memory = false; @@ -2193,8 +2193,8 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) from_shared_memory = false; if (persistent_script) { if (ZCG(accel_directives).record_warnings) { - persistent_script->num_warnings = EG(errors->size); - persistent_script->warnings = EG(errors->buf); + persistent_script->num_warnings = EG(errors).size; + persistent_script->warnings = EG(errors).errors; } /* See GH-17246: we disable GC so that user code cannot be executed during the optimizer run. */ @@ -2421,7 +2421,7 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, } ZCG(current_persistent_script) = &dummy; zend_persist_class_entry_calc(ce); - zend_persist_warnings_calc(EG(errors->size), EG(errors->buf)); + zend_persist_warnings_calc(EG(errors).size, EG(errors).errors); size = dummy.size; zend_shared_alloc_clear_xlat_table(); @@ -2491,8 +2491,8 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce, JIT_G(on) = jit_on_old; #endif - entry->num_warnings = EG(errors->size); - entry->warnings = zend_persist_warnings(EG(errors->size), EG(errors->buf)); + entry->num_warnings = EG(errors).size; + entry->warnings = zend_persist_warnings(EG(errors).size, EG(errors).errors); entry->next = proto->inheritance_cache; proto->inheritance_cache = entry; @@ -4123,9 +4123,9 @@ static void preload_link(void) /* Remember the last error. */ zend_error_cb = orig_error_cb; EG(record_errors) = false; - ZEND_ASSERT(EG(errors->size) > 0); - zend_hash_update_ptr(&errors, key, EG(errors->buf)[EG(errors->size)-1]); - EG(errors->size)--; + ZEND_ASSERT(EG(errors).size > 0); + zend_hash_update_ptr(&errors, key, EG(errors).errors[EG(errors).size-1]); + EG(errors).size--; } zend_end_try(); CG(in_compilation) = false; CG(compiled_filename) = NULL; From cbddab7108965ddfd1d1977cd30bbe2fb255984b Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 30 Mar 2026 17:17:39 +0100 Subject: [PATCH 4/4] Address review feedback: use struct copy for save/restore, cleanup. --- Zend/zend.c | 32 +++++++++++--------------------- Zend/zend_globals.h | 6 +++--- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 275a10b50359..2a5988273bcd 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -643,7 +643,6 @@ static FILE *zend_fopen_wrapper(zend_string *filename, zend_string **opened_path } /* }}} */ - #ifdef ZTS static bool short_tags_default = true; static uint32_t compiler_options_default = ZEND_COMPILE_DEFAULT; @@ -1446,9 +1445,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( zend_stack delayed_oplines_stack; int type = orig_type & E_ALL; bool orig_record_errors; - uint32_t orig_num_errors = 0; - uint32_t orig_cap_errors = 0; - zend_error_info **orig_errors = NULL; + zend_err_buf orig_errors_buf; zend_result res; /* If we're executing a function during SCCP, count any warnings that may be emitted, @@ -1461,9 +1458,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( /* Emit any delayed error before handling fatal error */ if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors).size) { - uint32_t num_errors = EG(errors).size; - uint32_t cap_errors = EG(errors).capacity; - zend_error_info **errors = EG(errors).errors; + zend_err_buf errors_buf = EG(errors); EG(errors).size = 0; bool orig_record_errors = EG(record_errors); @@ -1474,13 +1469,11 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( int orig_user_error_handler_error_reporting = EG(user_error_handler_error_reporting); EG(user_error_handler_error_reporting) = 0; - zend_emit_recorded_errors_ex(num_errors, errors); + zend_emit_recorded_errors_ex(errors_buf.size, errors_buf.errors); EG(user_error_handler_error_reporting) = orig_user_error_handler_error_reporting; EG(record_errors) = orig_record_errors; - EG(errors).size = num_errors; - EG(errors).capacity = cap_errors; - EG(errors).errors = errors; + EG(errors) = errors_buf; } if (EG(record_errors)) { @@ -1580,18 +1573,13 @@ ZEND_API ZEND_COLD void zend_error_zstr_at( orig_record_errors = EG(record_errors); EG(record_errors) = false; - orig_num_errors = EG(errors).size; - orig_cap_errors = EG(errors).capacity; - orig_errors = EG(errors).errors; + orig_errors_buf = EG(errors); memset(&EG(errors), 0, sizeof(EG(errors))); res = call_user_function(CG(function_table), NULL, &orig_user_error_handler, &retval, 4, params); EG(record_errors) = orig_record_errors; - - EG(errors).capacity = orig_cap_errors; - EG(errors).size = orig_num_errors; - EG(errors).errors = orig_errors; + EG(errors) = orig_errors_buf; if (res == SUCCESS) { if (Z_TYPE(retval) != IS_UNDEF) { @@ -1805,15 +1793,17 @@ ZEND_API void zend_emit_recorded_errors(void) ZEND_API void zend_free_recorded_errors(void) { + if (!EG(errors).size) { + return; + } + for (uint32_t i = 0; i < EG(errors).size; i++) { zend_error_info *info = EG(errors).errors[i]; zend_string_release(info->filename); zend_string_release(info->message); efree_size(info, sizeof(zend_error_info)); } - if (EG(errors).errors) { - efree(EG(errors).errors); - } + efree(EG(errors).errors); memset(&EG(errors), 0, sizeof(EG(errors))); } diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index db5d7518470a..db202dda66cb 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -82,11 +82,11 @@ typedef enum { ZEND_MEMOIZE_FETCH, } zend_memoize_mode; -struct zend_err_buf { +typedef struct zend_err_buf { uint32_t size; uint32_t capacity; zend_error_info **errors; -}; +} zend_err_buf; struct _zend_compiler_globals { zend_stack loop_var_stack; @@ -305,7 +305,7 @@ struct _zend_executor_globals { * and their processing is delayed until zend_emit_recorded_errors() * is called or a fatal diagnostic is emitted. */ bool record_errors; - struct zend_err_buf errors; + zend_err_buf errors; /* Override filename or line number of thrown errors and exceptions */ zend_string *filename_override;