Commit 0ada938f88d for php.net
commit 0ada938f88d01c7b708981702e2b6dd38d6f76b8
Author: David CARLIER <devnexen@gmail.com>
Date: Mon Mar 30 18:36:01 2026 +0100
Zend: Preallocate error buffer with capacity tracking (#20565)
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.
diff --git a/Zend/zend.c b/Zend/zend.c
index af0013220f2..2a5988273bc 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -833,8 +833,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;
+ memset(&executor_globals->errors, 0, sizeof(executor_globals->errors));
executor_globals->filename_override = NULL;
executor_globals->lineno_override = -1;
#ifdef ZEND_CHECK_STACK_LIMIT
@@ -1446,8 +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;
- zend_error_info **orig_errors;
+ zend_err_buf orig_errors_buf;
zend_result res;
/* If we're executing a function during SCCP, count any warnings that may be emitted,
@@ -1459,11 +1457,9 @@ 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) {
+ zend_err_buf errors_buf = EG(errors);
+ EG(errors).size = 0;
bool orig_record_errors = EG(record_errors);
EG(record_errors) = false;
@@ -1473,12 +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(num_errors) = num_errors;
- EG(errors) = errors;
+ EG(errors) = errors_buf;
}
if (EG(record_errors)) {
@@ -1487,12 +1482,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);
-
- /* 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) {
+ 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).errors[EG(errors).size - 1] = info;
/* Do not process non-fatal recorded error */
if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) {
@@ -1575,17 +1571,15 @@ 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_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(num_errors) = orig_num_errors;
- EG(errors) = orig_errors;
+ EG(errors) = orig_errors_buf;
if (res == SUCCESS) {
if (Z_TYPE(retval) != IS_UNDEF) {
@@ -1780,8 +1774,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 +1788,23 @@ 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).errors);
}
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).errors[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;
+ 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, ...) /* {{{ */
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index dbd2a9039cf..30ed4f5914c 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;
+ 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 31f54cd8284..db202dda66c 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;
+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;
@@ -298,8 +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;
- uint32_t num_errors;
- zend_error_info **errors;
+ 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 3d6923d1215..7aa0e67c24a 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).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(num_errors);
- persistent_script->warnings = EG(errors);
+ 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(num_errors), EG(errors));
+ 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(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).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(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).errors[EG(errors).size-1]);
+ EG(errors).size--;
} zend_end_try();
CG(in_compilation) = false;
CG(compiled_filename) = NULL;