Skip to content

Commit 5111ca5

Browse files
committed
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
1 parent 084e409 commit 5111ca5

File tree

4 files changed

+67
-49
lines changed

4 files changed

+67
-49
lines changed

Zend/zend.c

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ static char *zend_version_info;
8080
static uint32_t zend_version_info_length;
8181
#define ZEND_CORE_VERSION_INFO "Zend Engine v" ZEND_VERSION ", Copyright (c) Zend Technologies\n"
8282
#define PRINT_ZVAL_INDENT 4
83+
#define ZEND_ERR_BUF_SIZE(capacity) \
84+
(XtOffsetOf(struct zend_err_buf, buf) + sizeof(zend_error_info *) * (capacity))
8385

8486
/* true multithread-shared globals */
8587
ZEND_API zend_class_entry *zend_standard_class_def = NULL;
@@ -642,6 +644,13 @@ static FILE *zend_fopen_wrapper(zend_string *filename, zend_string **opened_path
642644
}
643645
/* }}} */
644646

647+
static void zend_init_errors_buf(zend_executor_globals *eg)
648+
{
649+
eg->errors = pemalloc(ZEND_ERR_BUF_SIZE(2), true);
650+
eg->errors->capacity = 2;
651+
eg->errors->size = 0;
652+
}
653+
645654
#ifdef ZTS
646655
static bool short_tags_default = true;
647656
static uint32_t compiler_options_default = ZEND_COMPILE_DEFAULT;
@@ -833,8 +842,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
833842
#endif
834843
executor_globals->flags = EG_FLAGS_INITIAL;
835844
executor_globals->record_errors = false;
836-
executor_globals->num_errors = 0;
837-
executor_globals->errors = NULL;
845+
zend_init_errors_buf(executor_globals);
838846
executor_globals->filename_override = NULL;
839847
executor_globals->lineno_override = -1;
840848
#ifdef ZEND_CHECK_STACK_LIMIT
@@ -869,6 +877,8 @@ static void executor_globals_dtor(zend_executor_globals *executor_globals) /* {{
869877
zend_hash_destroy(executor_globals->zend_constants);
870878
free(executor_globals->zend_constants);
871879
}
880+
881+
pefree(executor_globals->errors, true);
872882
}
873883
/* }}} */
874884

@@ -1065,6 +1075,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
10651075
zend_init_rsrc_plist();
10661076
zend_init_exception_op();
10671077
zend_init_call_trampoline_op();
1078+
zend_init_errors_buf(&executor_globals);
10681079
#endif
10691080

10701081
zend_ini_startup();
@@ -1147,6 +1158,7 @@ zend_result zend_post_startup(void) /* {{{ */
11471158
free(EG(zend_constants));
11481159
EG(zend_constants) = NULL;
11491160

1161+
pefree(executor_globals->errors, true);
11501162
executor_globals_ctor(executor_globals);
11511163
global_persistent_list = &EG(persistent_list);
11521164
zend_copy_ini_directives();
@@ -1224,6 +1236,7 @@ void zend_shutdown(void) /* {{{ */
12241236
pefree(CG(internal_run_time_cache), 1);
12251237
CG(internal_run_time_cache) = NULL;
12261238
}
1239+
pefree(EG(errors), true);
12271240
#endif
12281241
zend_map_ptr_static_last = 0;
12291242
zend_map_ptr_static_size = 0;
@@ -1446,8 +1459,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
14461459
zend_stack delayed_oplines_stack;
14471460
int type = orig_type & E_ALL;
14481461
bool orig_record_errors;
1449-
uint32_t orig_num_errors;
1450-
zend_error_info **orig_errors;
1462+
uint32_t orig_num_errors = 0;
1463+
uint32_t orig_cap_errors = 0;
14511464
zend_result res;
14521465

14531466
/* 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(
14591472
}
14601473

14611474
/* Emit any delayed error before handling fatal error */
1462-
if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(num_errors)) {
1463-
uint32_t num_errors = EG(num_errors);
1464-
zend_error_info **errors = EG(errors);
1465-
EG(num_errors) = 0;
1466-
EG(errors) = NULL;
1475+
if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors->size)) {
1476+
uint32_t num_errors = EG(errors->size);
1477+
uint32_t cap_errors = EG(errors->capacity);
1478+
zend_error_info **errors = EG(errors->buf);
1479+
EG(errors->size) = 0;
14671480

14681481
bool orig_record_errors = EG(record_errors);
14691482
EG(record_errors) = false;
@@ -1477,8 +1490,8 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
14771490

14781491
EG(user_error_handler_error_reporting) = orig_user_error_handler_error_reporting;
14791492
EG(record_errors) = orig_record_errors;
1480-
EG(num_errors) = num_errors;
1481-
EG(errors) = errors;
1493+
EG(errors->size) = num_errors;
1494+
EG(errors->capacity) = cap_errors;
14821495
}
14831496

14841497
if (EG(record_errors)) {
@@ -1487,12 +1500,14 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
14871500
info->lineno = error_lineno;
14881501
info->filename = zend_string_copy(error_filename);
14891502
info->message = zend_string_copy(message);
1490-
1491-
/* This is very inefficient for a large number of errors.
1492-
* Use pow2 realloc if it becomes a problem. */
1493-
EG(num_errors)++;
1494-
EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors));
1495-
EG(errors)[EG(num_errors)-1] = info;
1503+
EG(errors->size)++;
1504+
if (EG(errors->size) >= EG(errors->capacity)) {
1505+
// not sure we can get high number of errors so safe `might be` over cautious here
1506+
uint32_t capacity = EG(errors->capacity) + (EG(errors->capacity) >> 1);
1507+
EG(errors) = safe_perealloc(EG(errors), sizeof(zend_error_info *), capacity, XtOffsetOf(struct zend_err_buf, buf), true);
1508+
EG(errors->capacity) = capacity;
1509+
}
1510+
EG(errors->buf)[EG(errors->size)-1] = info;
14961511

14971512
/* Do not process non-fatal recorded error */
14981513
if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) {
@@ -1575,17 +1590,18 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
15751590
}
15761591

15771592
orig_record_errors = EG(record_errors);
1578-
orig_num_errors = EG(num_errors);
1579-
orig_errors = EG(errors);
15801593
EG(record_errors) = false;
1581-
EG(num_errors) = 0;
1582-
EG(errors) = NULL;
1594+
1595+
orig_num_errors = EG(errors->size);
1596+
orig_cap_errors = EG(errors->capacity);
1597+
EG(errors->size) = 0;
15831598

15841599
res = call_user_function(CG(function_table), NULL, &orig_user_error_handler, &retval, 4, params);
15851600

15861601
EG(record_errors) = orig_record_errors;
1587-
EG(num_errors) = orig_num_errors;
1588-
EG(errors) = orig_errors;
1602+
1603+
EG(errors->capacity) = orig_cap_errors;
1604+
EG(errors->size) = orig_num_errors;
15891605

15901606
if (res == SUCCESS) {
15911607
if (Z_TYPE(retval) != IS_UNDEF) {
@@ -1780,8 +1796,7 @@ ZEND_API void zend_begin_record_errors(void)
17801796
{
17811797
ZEND_ASSERT(!EG(record_errors) && "Error recording already enabled");
17821798
EG(record_errors) = true;
1783-
EG(num_errors) = 0;
1784-
EG(errors) = NULL;
1799+
EG(errors->size) = 0;
17851800
}
17861801

17871802
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
17951810
ZEND_API void zend_emit_recorded_errors(void)
17961811
{
17971812
EG(record_errors) = false;
1798-
zend_emit_recorded_errors_ex(EG(num_errors), EG(errors));
1813+
zend_emit_recorded_errors_ex(EG(errors->size), EG(errors->buf));
17991814
}
18001815

18011816
ZEND_API void zend_free_recorded_errors(void)
18021817
{
1803-
if (!EG(num_errors)) {
1818+
if (!EG(errors->size)) {
18041819
return;
18051820
}
18061821

1807-
for (uint32_t i = 0; i < EG(num_errors); i++) {
1808-
zend_error_info *info = EG(errors)[i];
1822+
for (uint32_t i = 0; i < EG(errors->size); i++) {
1823+
zend_error_info *info = EG(errors->buf)[i];
18091824
zend_string_release(info->filename);
18101825
zend_string_release(info->message);
1811-
efree(info);
1826+
efree_size(info, sizeof(zend_error_info));
18121827
}
1813-
efree(EG(errors));
1814-
EG(errors) = NULL;
1815-
EG(num_errors) = 0;
1828+
EG(errors->size) = 0;
18161829
}
18171830

18181831
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
20202033
}
20212034
/* }}} */
20222035

2023-
#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%d) : %s"
2036+
#define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%u) : %s"
20242037

20252038
ZEND_API char *zend_make_compiled_string_description(const char *name) /* {{{ */
20262039
{
20272040
const char *cur_filename;
2028-
int cur_lineno;
2041+
uint32_t cur_lineno;
20292042
char *compiled_string_description;
20302043

20312044
if (zend_is_compiling()) {

Zend/zend_execute_API.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ void init_executor(void) /* {{{ */
192192
EG(get_gc_buffer).start = EG(get_gc_buffer).end = EG(get_gc_buffer).cur = NULL;
193193

194194
EG(record_errors) = false;
195-
EG(num_errors) = 0;
196-
EG(errors) = NULL;
195+
EG(errors->size) = 0;
197196

198197
EG(filename_override) = NULL;
199198
EG(lineno_override) = -1;

Zend/zend_globals.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,20 @@ typedef struct _zend_vm_stack *zend_vm_stack;
7474
typedef struct _zend_ini_entry zend_ini_entry;
7575
typedef struct _zend_fiber_context zend_fiber_context;
7676
typedef struct _zend_fiber zend_fiber;
77+
typedef struct _zend_error_info zend_error_info;
7778

7879
typedef enum {
7980
ZEND_MEMOIZE_NONE,
8081
ZEND_MEMOIZE_COMPILE,
8182
ZEND_MEMOIZE_FETCH,
8283
} zend_memoize_mode;
8384

85+
struct zend_err_buf {
86+
uint32_t size;
87+
uint32_t capacity;
88+
zend_error_info *buf[1];
89+
};
90+
8491
struct _zend_compiler_globals {
8592
zend_stack loop_var_stack;
8693

@@ -298,8 +305,6 @@ struct _zend_executor_globals {
298305
* and their processing is delayed until zend_emit_recorded_errors()
299306
* is called or a fatal diagnostic is emitted. */
300307
bool record_errors;
301-
uint32_t num_errors;
302-
zend_error_info **errors;
303308

304309
/* Override filename or line number of thrown errors and exceptions */
305310
zend_string *filename_override;
@@ -322,6 +327,7 @@ struct _zend_executor_globals {
322327
HashTable callable_convert_cache;
323328

324329
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
330+
struct zend_err_buf *errors;
325331
};
326332

327333
#define EG_FLAGS_INITIAL (0)

ext/opcache/ZendAccelerator.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,8 +1974,8 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int
19741974

19751975
if (persistent_script) {
19761976
if (ZCG(accel_directives).record_warnings) {
1977-
persistent_script->num_warnings = EG(num_errors);
1978-
persistent_script->warnings = EG(errors);
1977+
persistent_script->num_warnings = EG(errors->size);
1978+
persistent_script->warnings = EG(errors->buf);
19791979
}
19801980

19811981
from_memory = false;
@@ -2199,8 +2199,8 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type)
21992199
from_shared_memory = false;
22002200
if (persistent_script) {
22012201
if (ZCG(accel_directives).record_warnings) {
2202-
persistent_script->num_warnings = EG(num_errors);
2203-
persistent_script->warnings = EG(errors);
2202+
persistent_script->num_warnings = EG(errors->size);
2203+
persistent_script->warnings = EG(errors->buf);
22042204
}
22052205

22062206
/* See GH-17246: we disable GC so that user code cannot be executed during the optimizer run. */
@@ -2428,7 +2428,7 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce,
24282428
}
24292429
ZCG(current_persistent_script) = &dummy;
24302430
zend_persist_class_entry_calc(ce);
2431-
zend_persist_warnings_calc(EG(num_errors), EG(errors));
2431+
zend_persist_warnings_calc(EG(errors->size), EG(errors->buf));
24322432
size = dummy.size;
24332433

24342434
zend_shared_alloc_clear_xlat_table();
@@ -2498,8 +2498,8 @@ static zend_class_entry* zend_accel_inheritance_cache_add(zend_class_entry *ce,
24982498
JIT_G(on) = jit_on_old;
24992499
#endif
25002500

2501-
entry->num_warnings = EG(num_errors);
2502-
entry->warnings = zend_persist_warnings(EG(num_errors), EG(errors));
2501+
entry->num_warnings = EG(errors->size);
2502+
entry->warnings = zend_persist_warnings(EG(errors->size), EG(errors->buf));
25032503
entry->next = proto->inheritance_cache;
25042504
proto->inheritance_cache = entry;
25052505

@@ -4133,9 +4133,9 @@ static void preload_link(void)
41334133
/* Remember the last error. */
41344134
zend_error_cb = orig_error_cb;
41354135
EG(record_errors) = false;
4136-
ZEND_ASSERT(EG(num_errors) > 0);
4137-
zend_hash_update_ptr(&errors, key, EG(errors)[EG(num_errors)-1]);
4138-
EG(num_errors)--;
4136+
ZEND_ASSERT(EG(errors->size) > 0);
4137+
zend_hash_update_ptr(&errors, key, EG(errors->buf)[EG(errors->size)-1]);
4138+
EG(errors->size)--;
41394139
} zend_end_try();
41404140
CG(in_compilation) = false;
41414141
CG(compiled_filename) = NULL;

0 commit comments

Comments
 (0)