From 03d0260ac8f83d04b6ef5d3ec004b6777ef3b649 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 24 Jan 2026 12:13:21 +0000 Subject: [PATCH] Zend: move class autoloading from SPL to Zend The primary motivation for this change is that this sort of functionality should reside in core and not in an extension. The reason being is that this causes issues in regard to extension dependencies and resolution, something that prevents GH-14544. --- Zend/zend_autoload.c | 165 ++++++++++++++++++++++++++++++++++ Zend/zend_autoload.h | 31 +++++++ Zend/zend_builtin_functions.c | 2 + Zend/zend_execute_API.c | 3 + configure.ac | 1 + ext/spl/php_spl.c | 120 ++----------------------- win32/build/config.w32 | 2 +- 7 files changed, 209 insertions(+), 115 deletions(-) create mode 100644 Zend/zend_autoload.c create mode 100644 Zend/zend_autoload.h diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c new file mode 100644 index 0000000000000..89b8027aa339e --- /dev/null +++ b/Zend/zend_autoload.c @@ -0,0 +1,165 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gina Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_API.h" +#include "zend_autoload.h" +#include "zend_hash.h" +#include "zend_types.h" +#include "zend_exceptions.h" +#include "zend_string.h" + +ZEND_TLS HashTable *autoloader_class_autoload_functions; + +ZEND_API void zend_autoload_callback_zval_destroy(zval *element) +{ + zend_fcall_info_cache *fcc = Z_PTR_P(element); + zend_fcc_dtor(fcc); + efree(fcc); +} + +static Bucket *autoload_find_registered_function(const HashTable *autoloader_table, const zend_fcall_info_cache *function_entry) +{ + zend_fcall_info_cache *current_function_entry; + ZEND_HASH_MAP_FOREACH_PTR(autoloader_table, current_function_entry) { + if (zend_fcc_equals(current_function_entry, function_entry)) { + return _p; + } + } ZEND_HASH_FOREACH_END(); + return NULL; +} + +ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name) +{ + if (!autoloader_class_autoload_functions) { + return NULL; + } + + zval zname; + ZVAL_STR(&zname, class_name); + + const HashTable *class_autoload_functions = autoloader_class_autoload_functions; + + /* Cannot use ZEND_HASH_MAP_FOREACH_PTR here as autoloaders may be + * added/removed during autoloading. */ + HashPosition pos; + zend_hash_internal_pointer_reset_ex(class_autoload_functions, &pos); + while (true) { + zend_fcall_info_cache *func_info = zend_hash_get_current_data_ptr_ex(class_autoload_functions, &pos); + if (!func_info) { + break; + } + zend_call_known_fcc(func_info, /* retval */ NULL, /* param_count */ 1, /* params */ &zname, /* named_params */ NULL); + + if (EG(exception)) { + return NULL; + } + if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) { + return (zend_class_entry*)ZSTR_GET_CE_CACHE(class_name); + } + + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); + if (ce) { + return ce; + } + + zend_hash_move_forward_ex(class_autoload_functions, &pos); + } + return NULL; +} +/* Needed for compatibility with spl_register_autoload() */ +ZEND_API void zend_autoload_register_class_loader(zend_fcall_info_cache *fcc, bool prepend) +{ + ZEND_ASSERT(ZEND_FCC_INITIALIZED(*fcc)); + + if (!autoloader_class_autoload_functions) { + ALLOC_HASHTABLE(autoloader_class_autoload_functions); + zend_hash_init(autoloader_class_autoload_functions, 1, NULL, zend_autoload_callback_zval_destroy, false); + /* Initialize as non-packed hash table for prepend functionality. */ + zend_hash_real_init_mixed(autoloader_class_autoload_functions); + } + + // TODO: Assertion for this + //if (fcc->function_handler->type == ZEND_INTERNAL_FUNCTION && + // fcc->function_handler->internal_function.handler == zif_autoload_call_class) { + // zend_argument_value_error(1, "must not be the autoload_call_class() function"); + // return; + //} + + /* If function is already registered, don't do anything */ + if (autoload_find_registered_function(autoloader_class_autoload_functions, fcc)) { + /* Release potential call trampoline */ + zend_release_fcall_info_cache(fcc); + return; + } + + zend_fcc_addref(fcc); + zend_hash_next_index_insert_mem(autoloader_class_autoload_functions, fcc, sizeof(zend_fcall_info_cache)); + if (prepend && zend_hash_num_elements(autoloader_class_autoload_functions) > 1) { + /* Move the newly created element to the head of the hashtable */ + ZEND_ASSERT(!HT_IS_PACKED(autoloader_class_autoload_functions)); + Bucket tmp = autoloader_class_autoload_functions->arData[autoloader_class_autoload_functions->nNumUsed-1]; + memmove(autoloader_class_autoload_functions->arData + 1, autoloader_class_autoload_functions->arData, sizeof(Bucket) * (autoloader_class_autoload_functions->nNumUsed - 1)); + autoloader_class_autoload_functions->arData[0] = tmp; + zend_hash_rehash(autoloader_class_autoload_functions); + } +} + +ZEND_API bool zend_autoload_unregister_class_loader(const zend_fcall_info_cache *fcc) { + if (autoloader_class_autoload_functions) { + Bucket *p = autoload_find_registered_function(autoloader_class_autoload_functions, fcc); + if (p) { + zend_hash_del_bucket(autoloader_class_autoload_functions, p); + return true; + } + } + return false; +} + +ZEND_API void zend_autoload_fcc_map_to_callable_zval_map(zval *return_value) { + if (autoloader_class_autoload_functions) { + zend_fcall_info_cache *fcc; + + array_init_size(return_value, zend_hash_num_elements(autoloader_class_autoload_functions)); + ZEND_HASH_MAP_FOREACH_PTR(autoloader_class_autoload_functions, fcc) { + zval tmp; + zend_get_callable_zval_from_fcc(fcc, &tmp); + add_next_index_zval(return_value, &tmp); + } ZEND_HASH_FOREACH_END(); + } else { + RETURN_EMPTY_ARRAY(); + } +} + +/* Only for deprecated strange behaviour of spl_autoload_unregister() */ +ZEND_API void zend_autoload_drop_autoload_map(void) +{ + if (autoloader_class_autoload_functions) { + /* Don't destroy the hash table, as we might be iterating over it right now. */ + zend_hash_clean(autoloader_class_autoload_functions); + } +} + +void zend_autoload_shutdown(void) +{ + if (autoloader_class_autoload_functions) { + zend_hash_destroy(autoloader_class_autoload_functions); + FREE_HASHTABLE(autoloader_class_autoload_functions); + autoloader_class_autoload_functions = NULL; + } +} diff --git a/Zend/zend_autoload.h b/Zend/zend_autoload.h new file mode 100644 index 0000000000000..aa78f0002cb18 --- /dev/null +++ b/Zend/zend_autoload.h @@ -0,0 +1,31 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gina Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +#include "zend_string.h" +#include "zend_API.h" +#include "zend.h" + +ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name); +ZEND_API zend_class_entry *zend_perform_class_map_autoload(zend_string *class_name, zend_string *lc_name); +ZEND_API void zend_autoload_callback_zval_destroy(zval *entry); +ZEND_API void zend_autoload_register_class_loader(zend_fcall_info_cache *fcc, bool prepend); +ZEND_API bool zend_autoload_unregister_class_loader(const zend_fcall_info_cache *fcc); +ZEND_API void zend_autoload_fcc_map_to_callable_zval_map(zval *return_value); +/* Only for deprecated strange behaviour of spl_autoload_unregister() */ +ZEND_API void zend_autoload_drop_autoload_map(void); +void zend_autoload_shutdown(void); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index a4ece3061d5d0..09d8793fd56ec 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -29,12 +29,14 @@ #include "zend_extensions.h" #include "zend_closures.h" #include "zend_generators.h" +#include "zend_autoload.h" #include "zend_builtin_functions_arginfo.h" #include "zend_smart_str.h" /* }}} */ ZEND_MINIT_FUNCTION(core) { /* {{{ */ + zend_autoload = zend_perform_class_autoload; zend_register_default_classes(); zend_standard_class_def = register_class_stdClass(); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 69337e27fd530..efb76441b35cd 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -39,6 +39,7 @@ #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" +#include "zend_autoload.h" #ifdef HAVE_SYS_TIME_H #include #endif @@ -451,6 +452,8 @@ void shutdown_executor(void) /* {{{ */ zend_stream_shutdown(); } zend_end_try(); + /* Shutdown autoloader prior to releasing values as it may hold references to objects */ + zend_autoload_shutdown(); zend_shutdown_executor_values(fast_shutdown); zend_weakrefs_shutdown(); diff --git a/configure.ac b/configure.ac index 77fc8c89cdf40..beeed7eb21115 100644 --- a/configure.ac +++ b/configure.ac @@ -1738,6 +1738,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_ast.c zend_atomic.c zend_attributes.c + zend_autoload.c zend_builtin_functions.c zend_call_stack.c zend_closures.c diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 35cc7d426911d..d57a1a5e53b76 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -32,11 +32,11 @@ #include "spl_dllist.h" #include "spl_fixedarray.h" #include "spl_heap.h" +#include "zend_autoload.h" #include "zend_exceptions.h" #include "zend_interfaces.h" ZEND_TLS zend_string *spl_autoload_extensions; -ZEND_TLS HashTable *spl_autoload_functions; #define SPL_DEFAULT_FILE_EXTENSIONS ".inc,.php" @@ -340,50 +340,6 @@ PHP_FUNCTION(spl_autoload_extensions) } } /* }}} */ -static void autoload_func_info_zval_dtor(zval *element) -{ - zend_fcall_info_cache *fcc = Z_PTR_P(element); - zend_fcc_dtor(fcc); - efree(fcc); -} - -static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { - if (!spl_autoload_functions) { - return NULL; - } - - /* We don't use ZEND_HASH_MAP_FOREACH here, - * because autoloaders may be added/removed during autoloading. */ - HashPosition pos; - zend_hash_internal_pointer_reset_ex(spl_autoload_functions, &pos); - - zval zname; - ZVAL_STR(&zname, class_name); - while (true) { - zend_fcall_info_cache *fcc = - zend_hash_get_current_data_ptr_ex(spl_autoload_functions, &pos); - if (!fcc) { - break; - } - - zend_call_known_fcc(fcc, NULL, 1, &zname, NULL); - if (UNEXPECTED(EG(exception))) { - break; - } - - if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) { - return (zend_class_entry*)ZSTR_GET_CE_CACHE(class_name); - } - zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); - if (ce != NULL) { - return ce; - } - - zend_hash_move_forward_ex(spl_autoload_functions, &pos); - } - return NULL; -} - /* {{{ Try all registered autoload function to load the requested class */ PHP_FUNCTION(spl_autoload_call) { @@ -394,24 +350,10 @@ PHP_FUNCTION(spl_autoload_call) } zend_string *lc_name = zend_string_tolower(class_name); - spl_perform_autoload(class_name, lc_name); + zend_perform_class_autoload(class_name, lc_name); zend_string_release(lc_name); } /* }}} */ -static Bucket *spl_find_registered_function(const zend_fcall_info_cache *find_fcc) { - if (!spl_autoload_functions) { - return NULL; - } - - zend_fcall_info_cache *fcc; - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, fcc) { - if (zend_fcc_equals(fcc, find_fcc)) { - return _p; - } - } ZEND_HASH_FOREACH_END(); - return NULL; -} - /* {{{ Register given function as autoloader */ PHP_FUNCTION(spl_autoload_register) { @@ -432,13 +374,6 @@ PHP_FUNCTION(spl_autoload_register) "spl_autoload_register() will always throw"); } - if (!spl_autoload_functions) { - ALLOC_HASHTABLE(spl_autoload_functions); - zend_hash_init(spl_autoload_functions, 1, NULL, autoload_func_info_zval_dtor, false); - /* Initialize as non-packed hash table for prepend functionality. */ - zend_hash_real_init_mixed(spl_autoload_functions); - } - /* If first arg is not null */ if (ZEND_FCI_INITIALIZED(fci)) { if (!ZEND_FCC_INITIALIZED(fcc)) { @@ -458,22 +393,7 @@ PHP_FUNCTION(spl_autoload_register) fcc.function_handler = zend_hash_str_find_ptr(CG(function_table), ZEND_STRL("spl_autoload")); } - if (spl_find_registered_function(&fcc)) { - /* Release call trampoline */ - zend_release_fcall_info_cache(&fcc); - RETURN_TRUE; - } - - zend_fcc_addref(&fcc); - zend_hash_next_index_insert_mem(spl_autoload_functions, &fcc, sizeof(zend_fcall_info_cache)); - if (prepend && spl_autoload_functions->nNumOfElements > 1) { - /* Move the newly created element to the head of the hashtable */ - ZEND_ASSERT(!HT_IS_PACKED(spl_autoload_functions)); - Bucket tmp = spl_autoload_functions->arData[spl_autoload_functions->nNumUsed-1]; - memmove(spl_autoload_functions->arData + 1, spl_autoload_functions->arData, sizeof(Bucket) * (spl_autoload_functions->nNumUsed - 1)); - spl_autoload_functions->arData[0] = tmp; - zend_hash_rehash(spl_autoload_functions); - } + zend_autoload_register_class_loader(&fcc, prepend); RETURN_TRUE; } /* }}} */ @@ -498,22 +418,13 @@ PHP_FUNCTION(spl_autoload_unregister) if (UNEXPECTED(EG(exception))) { RETURN_THROWS(); } - if (spl_autoload_functions) { - /* Don't destroy the hash table, as we might be iterating over it right now. */ - zend_hash_clean(spl_autoload_functions); - } + zend_autoload_drop_autoload_map(); RETURN_TRUE; } - Bucket *p = spl_find_registered_function(&fcc); + RETVAL_BOOL(zend_autoload_unregister_class_loader(&fcc)); /* Release trampoline */ zend_release_fcall_info_cache(&fcc); - if (p) { - zend_hash_del_bucket(spl_autoload_functions, p); - RETURN_TRUE; - } - - RETURN_FALSE; } /* }}} */ /* {{{ Return all registered autoloader functions */ @@ -521,18 +432,7 @@ PHP_FUNCTION(spl_autoload_functions) { ZEND_PARSE_PARAMETERS_NONE(); - if (spl_autoload_functions) { - zend_fcall_info_cache *fcc; - - array_init_size(return_value, zend_hash_num_elements(spl_autoload_functions)); - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, fcc) { - zval tmp; - zend_get_callable_zval_from_fcc(fcc, &tmp); - add_next_index_zval(return_value, &tmp); - } ZEND_HASH_FOREACH_END(); - } else { - RETURN_EMPTY_ARRAY(); - } + zend_autoload_fcc_map_to_callable_zval_map(return_value); } /* }}} */ /* {{{ Return hash id for given object */ @@ -612,8 +512,6 @@ PHP_MINFO_FUNCTION(spl) /* {{{ PHP_MINIT_FUNCTION(spl) */ PHP_MINIT_FUNCTION(spl) { - zend_autoload = spl_perform_autoload; - PHP_MINIT(spl_exceptions)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_iterators)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_array)(INIT_FUNC_ARGS_PASSTHRU); @@ -630,7 +528,6 @@ PHP_MINIT_FUNCTION(spl) PHP_RINIT_FUNCTION(spl) /* {{{ */ { spl_autoload_extensions = NULL; - spl_autoload_functions = NULL; return SUCCESS; } /* }}} */ @@ -640,11 +537,6 @@ PHP_RSHUTDOWN_FUNCTION(spl) /* {{{ */ zend_string_release_ex(spl_autoload_extensions, 0); spl_autoload_extensions = NULL; } - if (spl_autoload_functions) { - zend_hash_destroy(spl_autoload_functions); - FREE_HASHTABLE(spl_autoload_functions); - spl_autoload_functions = NULL; - } return SUCCESS; } /* }}} */ diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 403f0aa6efbfe..aefcfb5f82474 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -241,7 +241,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c \ - zend_lazy_objects.c"); + zend_lazy_objects.c zend_autoload.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({