diff --git a/Zend/zend_API.c b/Zend/zend_API.c index b4f13bedecc56..03e021f881faa 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3861,6 +3861,7 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ if (!ce_org) { zend_function *func; zend_string *lmname; + zend_string *name = Z_STR_P(callable); /* Check if function with given name exists. * This may be a compound name that includes namespace name */ @@ -3868,15 +3869,14 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ /* Skip leading \ */ ZSTR_ALLOCA_ALLOC(lmname, Z_STRLEN_P(callable) - 1, use_heap); zend_str_tolower_copy(ZSTR_VAL(lmname), Z_STRVAL_P(callable) + 1, Z_STRLEN_P(callable) - 1); - func = zend_fetch_function(lmname); + func = zend_fetch_function(name, lmname); ZSTR_ALLOCA_FREE(lmname, use_heap); } else { - lmname = Z_STR_P(callable); - func = zend_fetch_function(lmname); + func = zend_fetch_function(name, name); if (!func) { ZSTR_ALLOCA_ALLOC(lmname, Z_STRLEN_P(callable), use_heap); zend_str_tolower_copy(ZSTR_VAL(lmname), Z_STRVAL_P(callable), Z_STRLEN_P(callable)); - func = zend_fetch_function(lmname); + func = zend_fetch_function(name, lmname); ZSTR_ALLOCA_FREE(lmname, use_heap); } } diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 654ba7e7ea129..c08be5bf10ac6 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -1162,10 +1162,13 @@ ZEND_FUNCTION(function_exists) { zend_string *name; bool exists; + bool autoload = 1; zend_string *lcname; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(name) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(autoload) ZEND_PARSE_PARAMETERS_END(); if (ZSTR_VAL(name)[0] == '\\') { @@ -1176,7 +1179,11 @@ ZEND_FUNCTION(function_exists) lcname = zend_string_tolower(name); } - exists = zend_hash_exists(EG(function_table), lcname); + if (autoload) { + exists = zend_locate_function(name, lcname); + } else { + exists = zend_hash_exists(EG(function_table), lcname); + } zend_string_release_ex(lcname, 0); RETURN_BOOL(exists); diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index f7009c4ffba6e..c9368eae59cd1 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -91,7 +91,7 @@ function trait_exists(string $trait, bool $autoload = true): bool {} function enum_exists(string $enum, bool $autoload = true): bool {} -function function_exists(string $function): bool {} +function function_exists(string $function, bool $autoload = true): bool {} function class_alias(string $class, string $alias, bool $autoload = true): bool {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index 612fd1d275d55..4920daf29d454 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3dbc84896823c9aaa9ac8aeef8841266920c3e50 */ + * Stub hash: 490e8d9d89266f7a980359e5613aa653bf7c1d30 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0) ZEND_ARG_TYPE_MASK(0, status, MAY_BE_STRING|MAY_BE_LONG, "0") @@ -121,6 +121,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_function_exists, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, function, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, autoload, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_alias, 0, 2, _IS_BOOL, 0) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 733ce54dc24ad..4fdccca1c0dd8 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4250,9 +4250,9 @@ static zend_never_inline void ZEND_FASTCALL init_func_run_time_cache(zend_op_arr } /* }}} */ -ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name) /* {{{ */ +ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name, zend_string *lc_name) /* {{{ */ { - zval *zv = zend_hash_find(EG(function_table), name); + zval *zv = zend_locate_function(name, lc_name); if (EXPECTED(zv != NULL)) { zend_function *fbc = Z_FUNC_P(zv); @@ -4859,6 +4859,28 @@ static void zend_swap_operands(zend_op *op) /* {{{ */ /* }}} */ #endif +zval* zend_locate_function(zend_string *function_name, zend_string *lc_name) /* {{{ */ +{ + zval *func; + func = zend_hash_find(EG(function_table), lc_name); + if (func == NULL && zend_autoload_function) { + zend_string *previous_filename = EG(filename_override); + zend_long previous_lineno = EG(lineno_override); + EG(filename_override) = NULL; + EG(lineno_override) = -1; + zend_exception_save(); + + func = zend_autoload_function(function_name, lc_name); + + zend_exception_restore(); + EG(filename_override) = previous_filename; + EG(lineno_override) = previous_lineno; + } + + return func; +} +/* }}} */ + static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_string *function, uint32_t num_args) /* {{{ */ { zend_function *fbc; @@ -4920,7 +4942,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s } else { lcname = zend_string_tolower(function); } - if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) { + if (UNEXPECTED((func = zend_locate_function(function, lcname)) == NULL)) { zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function)); zend_string_release_ex(lcname, 0); return NULL; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 190d67f82a598..63e9e53c5f048 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -35,6 +35,7 @@ ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, z /* The lc_name may be stack allocated! */ ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API extern zval *(*zend_autoload_function)(zend_string *name, zend_string *lc_name); void init_executor(void); void shutdown_executor(void); @@ -50,6 +51,7 @@ ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_val ZEND_API bool zend_is_valid_class_name(zend_string *name); ZEND_API zend_class_entry *zend_lookup_class(zend_string *name); ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *lcname, uint32_t flags); +ZEND_API zval *zend_locate_function(zend_string *function_name, zend_string *lcname); ZEND_API zend_class_entry *zend_get_called_scope(zend_execute_data *ex); ZEND_API zend_object *zend_get_this_object(zend_execute_data *ex); ZEND_API zend_result zend_eval_string(const char *str, zval *retval_ptr, const char *string_name); @@ -405,7 +407,7 @@ ZEND_API zend_class_entry *zend_fetch_class(zend_string *class_name, uint32_t fe ZEND_API zend_class_entry *zend_fetch_class_with_scope(zend_string *class_name, uint32_t fetch_type, zend_class_entry *scope); ZEND_API zend_class_entry *zend_fetch_class_by_name(zend_string *class_name, zend_string *lcname, uint32_t fetch_type); -ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name); +ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name, zend_string *lc_name); ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function_str(const char *name, size_t len); ZEND_API void ZEND_FASTCALL zend_init_func_run_time_cache(zend_op_array *op_array); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 21e17fe8c666b..a6d607be0e462 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -52,6 +52,7 @@ ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); ZEND_API zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API zval *(*zend_autoload_function)(zend_string *name, zend_string *lc_name); /* true globals */ ZEND_API const zend_fcall_info empty_fcall_info = {0}; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b46952a81718b..c56282f4532c4 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3834,7 +3834,7 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { function_name = (zval*)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(function_name+1)); + func = zend_locate_function(Z_STR_P(function_name), Z_STR_P(function_name+1)); if (UNEXPECTED(func == NULL)) { ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } @@ -3978,7 +3978,7 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { func_name = (zval *)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 1)); + func = zend_locate_function(Z_STR_P(func_name + 1), Z_STR_P(func_name + 1)); if (func == NULL) { func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 2)); if (UNEXPECTED(func == NULL)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6738674667e74..f75f990354bfb 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3893,7 +3893,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { function_name = (zval*)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(function_name+1)); + func = zend_locate_function(Z_STR_P(function_name), Z_STR_P(function_name+1)); if (UNEXPECTED(func == NULL)) { ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } @@ -3975,7 +3975,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { func_name = (zval *)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 1)); + func = zend_locate_function(Z_STR_P(func_name + 1), Z_STR_P(func_name + 1)); if (func == NULL) { func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 2)); if (UNEXPECTED(func == NULL)) { diff --git a/benchmark/benchmark.php b/benchmark/benchmark.php index 0c2ac4c6010a4..66c91a5a24c1a 100644 --- a/benchmark/benchmark.php +++ b/benchmark/benchmark.php @@ -66,7 +66,7 @@ function runBench(bool $jit): array { function runSymfonyDemo(bool $jit): array { $dir = __DIR__ . '/repos/symfony-demo-2.2.3'; - cloneRepo($dir, 'https://github.com/php/benchmarking-symfony-demo-2.2.3.git'); + cloneRepo($dir, 'https://github.com/withinboredom/benchmarking-symfony-demo-2.2.3.git'); runPhpCommand([$dir . '/bin/console', 'cache:clear']); runPhpCommand([$dir . '/bin/console', 'cache:warmup']); return runValgrindPhpCgiCommand('symfony-demo', [$dir . '/public/index.php'], cwd: $dir, jit: $jit, warmup: 50, repeat: 50); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 531db004b3ba6..49f727483b6b7 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1643,11 +1643,11 @@ ZEND_METHOD(ReflectionFunction, __construct) ALLOCA_FLAG(use_heap) ZSTR_ALLOCA_ALLOC(lcname, ZSTR_LEN(fname) - 1, use_heap); zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(fname) + 1, ZSTR_LEN(fname) - 1); - fptr = zend_fetch_function(lcname); + fptr = zend_fetch_function(fname, lcname); ZSTR_ALLOCA_FREE(lcname, use_heap); } else { lcname = zend_string_tolower(fname); - fptr = zend_fetch_function(lcname); + fptr = zend_fetch_function(fname, lcname); zend_string_release(lcname); } diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 78315e9880b4f..15df162fd8be4 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -37,7 +37,9 @@ #include "main/snprintf.h" ZEND_TLS zend_string *spl_autoload_extensions; -ZEND_TLS HashTable *spl_autoload_functions; +ZEND_TLS HashTable *spl_autoload_class_functions; +ZEND_TLS HashTable *spl_autoload_function_functions; +ZEND_TLS HashTable *spl_autoload_function_skiplist; #define SPL_DEFAULT_FILE_EXTENSIONS ".inc,.php" @@ -294,9 +296,11 @@ PHP_FUNCTION(spl_autoload) char *pos, *pos1; zend_string *class_name, *lc_name, *file_exts = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|S!", &class_name, &file_exts) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(class_name) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_NULL(file_exts) + ZEND_PARSE_PARAMETERS_END(); if (!file_exts) { file_exts = spl_autoload_extensions; @@ -414,18 +418,72 @@ static bool autoload_func_info_equals( && alfi1->closure == alfi2->closure; } -static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { - if (!spl_autoload_functions) { +static zval *spl_perform_function_autoload(zend_string *function_name, zend_string *lc_name) { + if(!spl_autoload_function_functions) { + return NULL; + } + + if(!spl_autoload_function_skiplist) { + ALLOC_HASHTABLE(spl_autoload_function_skiplist); + zend_hash_init(spl_autoload_function_skiplist, 1, NULL, NULL, 0); + } + + if(zend_hash_find(spl_autoload_function_skiplist, lc_name)) { + 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_function_functions, &pos); + zval *result = NULL; + + while (1) { + autoload_func_info *alfi = + zend_hash_get_current_data_ptr_ex(spl_autoload_function_functions, &pos); + if (!alfi) { + break; + } + + zend_function *func = alfi->func_ptr; + if(UNEXPECTED(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + func = emalloc(sizeof(zend_op_array)); + memcpy(func, alfi->func_ptr, sizeof(zend_op_array)); + zend_string_addref(func->op_array.function_name); + } + + zval params[1]; + ZVAL_STR(¶ms[0], function_name); + zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, params, NULL); + if (EG(exception)) { + break; + } + + result = zend_hash_find_ex(EG(function_table), lc_name, true); + if (result) { + return result; + } + + zend_hash_move_forward_ex(spl_autoload_function_functions, &pos); + } + + zend_hash_add_empty_element(spl_autoload_function_skiplist, lc_name); + + return NULL; +} + +static zend_class_entry *spl_perform_class_autoload(zend_string *class_name, zend_string *lc_name) { + if (!spl_autoload_class_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); + zend_hash_internal_pointer_reset_ex(spl_autoload_class_functions, &pos); while (1) { autoload_func_info *alfi = - zend_hash_get_current_data_ptr_ex(spl_autoload_functions, &pos); + zend_hash_get_current_data_ptr_ex(spl_autoload_class_functions, &pos); if (!alfi) { break; } @@ -437,9 +495,9 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri zend_string_addref(func->op_array.function_name); } - zval param; - ZVAL_STR(¶m, class_name); - zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, ¶m, NULL); + zval params[1]; + ZVAL_STR(¶ms[0], class_name); + zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, params, NULL); if (EG(exception)) { break; } @@ -453,7 +511,7 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri } } - zend_hash_move_forward_ex(spl_autoload_functions, &pos); + zend_hash_move_forward_ex(spl_autoload_class_functions, &pos); } return NULL; } @@ -461,14 +519,23 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri /* {{{ Try all registered autoload function to load the requested class */ PHP_FUNCTION(spl_autoload_call) { - zend_string *class_name; + zend_string *name; + zend_long type = ZEND_AUTOLOAD_CLASS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &class_name) == FAILURE) { - RETURN_THROWS(); + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(name) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + ZEND_PARSE_PARAMETERS_END(); + + zend_string *lc_name = zend_string_tolower(name); + if (type & ZEND_AUTOLOAD_CLASS) { + spl_perform_class_autoload(name, lc_name); + } + if (type & ZEND_AUTOLOAD_FUNCTION) { + spl_perform_function_autoload(name, lc_name); } - zend_string *lc_name = zend_string_tolower(class_name); - spl_perform_autoload(class_name, lc_name); zend_string_release(lc_name); } /* }}} */ @@ -482,13 +549,15 @@ PHP_FUNCTION(spl_autoload_call) zend_hash_rehash(ht); \ } while (0) -static Bucket *spl_find_registered_function(autoload_func_info *find_alfi) { - if (!spl_autoload_functions) { +static Bucket *spl_find_registered_function(autoload_func_info *find_alfi, int type) { + if (!spl_autoload_class_functions) { return NULL; } + HashTable *map = type & ZEND_AUTOLOAD_CLASS ? spl_autoload_class_functions : spl_autoload_function_functions; + autoload_func_info *alfi; - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, alfi) { + ZEND_HASH_MAP_FOREACH_PTR(map, alfi) { if (autoload_func_info_equals(alfi, find_alfi)) { return _p; } @@ -501,15 +570,17 @@ PHP_FUNCTION(spl_autoload_register) { bool do_throw = 1; bool prepend = 0; + zend_long type = ZEND_AUTOLOAD_CLASS; zend_fcall_info fci = {0}; zend_fcall_info_cache fcc; autoload_func_info *alfi; - ZEND_PARSE_PARAMETERS_START(0, 3) + ZEND_PARSE_PARAMETERS_START(0, 4) Z_PARAM_OPTIONAL Z_PARAM_FUNC_OR_NULL(fci, fcc) Z_PARAM_BOOL(do_throw) Z_PARAM_BOOL(prepend) + Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END(); if (!do_throw) { @@ -517,11 +588,18 @@ 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, 0); + if (!spl_autoload_class_functions) { + ALLOC_HASHTABLE(spl_autoload_class_functions); + zend_hash_init(spl_autoload_class_functions, 1, NULL, autoload_func_info_zval_dtor, 0); + /* Initialize as non-packed hash table for prepend functionality. */ + zend_hash_real_init_mixed(spl_autoload_class_functions); + } + + if (!spl_autoload_function_functions) { + ALLOC_HASHTABLE(spl_autoload_function_functions); + zend_hash_init(spl_autoload_function_functions, 1, NULL, autoload_func_info_zval_dtor, 0); /* Initialize as non-packed hash table for prepend functionality. */ - zend_hash_real_init_mixed(spl_autoload_functions); + zend_hash_real_init_mixed(spl_autoload_function_functions); } /* If first arg is not null */ @@ -556,15 +634,28 @@ PHP_FUNCTION(spl_autoload_register) alfi->closure = NULL; } - if (spl_find_registered_function(alfi)) { + if (spl_find_registered_function(alfi, ZEND_AUTOLOAD_CLASS)) { + autoload_func_info_destroy(alfi); + RETURN_TRUE; + } else if (spl_find_registered_function(alfi, ZEND_AUTOLOAD_FUNCTION)) { autoload_func_info_destroy(alfi); RETURN_TRUE; } - zend_hash_next_index_insert_ptr(spl_autoload_functions, alfi); - if (prepend && spl_autoload_functions->nNumOfElements > 1) { - /* Move the newly created element to the head of the hashtable */ - HT_MOVE_TAIL_TO_HEAD(spl_autoload_functions); + if (type & ZEND_AUTOLOAD_CLASS) { + zend_hash_next_index_insert_ptr(spl_autoload_class_functions, alfi); + if (prepend && spl_autoload_class_functions->nNumOfElements > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(spl_autoload_class_functions); + } + } + + if (type & ZEND_AUTOLOAD_FUNCTION) { + zend_hash_next_index_insert_ptr(spl_autoload_function_functions, alfi); + if (prepend && spl_autoload_function_functions->nNumOfElements > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(spl_autoload_function_functions); + } } RETURN_TRUE; @@ -575,16 +666,19 @@ PHP_FUNCTION(spl_autoload_unregister) { zend_fcall_info fci; zend_fcall_info_cache fcc; + zend_long type = ZEND_AUTOLOAD_CLASS; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END(); if (fcc.function_handler && zend_string_equals_literal( fcc.function_handler->common.function_name, "spl_autoload_call")) { - if (spl_autoload_functions) { + if (spl_autoload_class_functions) { /* Don't destroy the hash table, as we might be iterating over it right now. */ - zend_hash_clean(spl_autoload_functions); + zend_hash_clean(spl_autoload_class_functions); } RETURN_TRUE; } @@ -597,11 +691,23 @@ PHP_FUNCTION(spl_autoload_unregister) } autoload_func_info *alfi = autoload_func_info_from_fci(&fci, &fcc); - Bucket *p = spl_find_registered_function(alfi); - autoload_func_info_destroy(alfi); - if (p) { - zend_hash_del_bucket(spl_autoload_functions, p); - RETURN_TRUE; + + if (type & ZEND_AUTOLOAD_CLASS) { + Bucket *p = spl_find_registered_function(alfi, ZEND_AUTOLOAD_CLASS); + autoload_func_info_destroy(alfi); + if (p) { + zend_hash_del_bucket(spl_autoload_class_functions, p); + RETURN_TRUE; + } + } + + if (type & ZEND_AUTOLOAD_FUNCTION) { + Bucket *p = spl_find_registered_function(alfi, ZEND_AUTOLOAD_FUNCTION); + autoload_func_info_destroy(alfi); + if (p) { + zend_hash_del_bucket(spl_autoload_function_functions, p); + RETURN_TRUE; + } } RETURN_FALSE; @@ -611,14 +717,19 @@ PHP_FUNCTION(spl_autoload_unregister) PHP_FUNCTION(spl_autoload_functions) { autoload_func_info *alfi; + zend_long type = ZEND_AUTOLOAD_CLASS; - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + ZEND_PARSE_PARAMETERS_END(); array_init(return_value); - if (spl_autoload_functions) { - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, alfi) { + + zend_array *arr = type & ZEND_AUTOLOAD_CLASS ? spl_autoload_class_functions : NULL; + + if (arr) { + ZEND_HASH_MAP_FOREACH_PTR(arr, alfi) { if (alfi->closure) { GC_ADDREF(alfi->closure); add_next_index_object(return_value, alfi->closure); @@ -639,6 +750,31 @@ PHP_FUNCTION(spl_autoload_functions) } } ZEND_HASH_FOREACH_END(); } + + arr = type & ZEND_AUTOLOAD_FUNCTION ? spl_autoload_function_functions : NULL; + + if (arr) { + ZEND_HASH_MAP_FOREACH_PTR(arr, alfi) { + if (alfi->closure) { + GC_ADDREF(alfi->closure); + add_next_index_object(return_value, alfi->closure); + } else if (alfi->func_ptr->common.scope) { + zval tmp; + + array_init(&tmp); + if (alfi->obj) { + GC_ADDREF(alfi->obj); + add_next_index_object(&tmp, alfi->obj); + } else { + add_next_index_str(&tmp, zend_string_copy(alfi->ce->name)); + } + add_next_index_str(&tmp, zend_string_copy(alfi->func_ptr->common.function_name)); + add_next_index_zval(return_value, &tmp); + } else { + add_next_index_str(return_value, zend_string_copy(alfi->func_ptr->common.function_name)); + } + } ZEND_HASH_FOREACH_END(); + } } /* }}} */ /* {{{ Return hash id for given object */ @@ -718,7 +854,10 @@ PHP_MINFO_FUNCTION(spl) /* {{{ PHP_MINIT_FUNCTION(spl) */ PHP_MINIT_FUNCTION(spl) { - zend_autoload = spl_perform_autoload; + zend_autoload = spl_perform_class_autoload; + zend_autoload_function = spl_perform_function_autoload; + + register_php_spl_symbols(module_number); PHP_MINIT(spl_exceptions)(INIT_FUNC_ARGS_PASSTHRU); PHP_MINIT(spl_iterators)(INIT_FUNC_ARGS_PASSTHRU); @@ -736,7 +875,9 @@ PHP_MINIT_FUNCTION(spl) PHP_RINIT_FUNCTION(spl) /* {{{ */ { spl_autoload_extensions = NULL; - spl_autoload_functions = NULL; + spl_autoload_class_functions = NULL; + spl_autoload_function_functions = NULL; + spl_autoload_function_skiplist = NULL; return SUCCESS; } /* }}} */ @@ -746,10 +887,20 @@ 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; + if (spl_autoload_class_functions) { + zend_hash_destroy(spl_autoload_class_functions); + FREE_HASHTABLE(spl_autoload_class_functions); + spl_autoload_class_functions = NULL; + } + if (spl_autoload_function_functions) { + zend_hash_destroy(spl_autoload_function_functions); + FREE_HASHTABLE(spl_autoload_function_functions); + spl_autoload_function_functions = NULL; + } + if (spl_autoload_function_skiplist) { + zend_hash_destroy(spl_autoload_function_skiplist); + FREE_HASHTABLE(spl_autoload_function_skiplist); + spl_autoload_function_skiplist = NULL; } return SUCCESS; } /* }}} */ diff --git a/ext/spl/php_spl.h b/ext/spl/php_spl.h index c2f7ebf9eee17..b63dcca9257d2 100644 --- a/ext/spl/php_spl.h +++ b/ext/spl/php_spl.h @@ -32,4 +32,9 @@ PHP_MINFO_FUNCTION(spl); PHPAPI zend_string *php_spl_object_hash(zend_object *obj); +#ifndef ZEND_AUTOLOAD_CLASS +#define ZEND_AUTOLOAD_CLASS 1 +#define ZEND_AUTOLOAD_FUNCTION 2 +#endif + #endif /* PHP_SPL_H */ diff --git a/ext/spl/php_spl.stub.php b/ext/spl/php_spl.stub.php index d3b5d44f11d1e..1ff44d276d125 100644 --- a/ext/spl/php_spl.stub.php +++ b/ext/spl/php_spl.stub.php @@ -2,6 +2,18 @@ /** @generate-class-entries */ +/** + * @var int + * @cvalue ZEND_AUTOLOAD_CLASS + */ +const SPL_AUTOLOAD_CLASS = UNKNOWN; + +/** + * @var int + * @cvalue ZEND_AUTOLOAD_FUNCTION + */ +const SPL_AUTOLOAD_FUNCTION = UNKNOWN; + /** * @param object|string $object_or_class * @return array|false @@ -23,17 +35,17 @@ function class_parents($object_or_class, bool $autoload = true): array|false {} */ function class_uses($object_or_class, bool $autoload = true): array|false {} -function spl_autoload(string $class, ?string $file_extensions = null): void {} +function spl_autoload(string $class, string|null $file_extensions = null): void {} -function spl_autoload_call(string $class): void {} +function spl_autoload_call(string $class, int $type = SPL_AUTOLOAD_CLASS): void {} function spl_autoload_extensions(?string $file_extensions = null): string {} -function spl_autoload_functions(): array {} +function spl_autoload_functions(int $type = SPL_AUTOLOAD_CLASS): array {} -function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool {} +function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false, int $type = SPL_AUTOLOAD_CLASS): bool {} -function spl_autoload_unregister(callable $callback): bool {} +function spl_autoload_unregister(callable $callback, int $type = SPL_AUTOLOAD_CLASS): bool {} /** * @return array diff --git a/ext/spl/php_spl_arginfo.h b/ext/spl/php_spl_arginfo.h index 68c71fc524bc5..950daa9a22ab3 100644 --- a/ext/spl/php_spl_arginfo.h +++ b/ext/spl/php_spl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 21ec2dcca99c85c90afcd319da76016a9f678dc2 */ + * Stub hash: b7356f73a6abd373f2cc4c3d996245ac96e731ec */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_implements, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_INFO(0, object_or_class) @@ -17,6 +17,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_call, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "SPL_AUTOLOAD_CLASS") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_extensions, 0, 0, IS_STRING, 0) @@ -24,19 +25,23 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_extensions, 0, 0, I ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_functions, 0, 0, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "SPL_AUTOLOAD_CLASS") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_register, 0, 0, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, callback, IS_CALLABLE, 1, "null") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw, _IS_BOOL, 0, "true") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, prepend, _IS_BOOL, 0, "false") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "SPL_AUTOLOAD_CLASS") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_unregister, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "SPL_AUTOLOAD_CLASS") ZEND_END_ARG_INFO() -#define arginfo_spl_classes arginfo_spl_autoload_functions +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_classes, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_object_hash, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) @@ -95,3 +100,9 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(iterator_to_array, arginfo_iterator_to_array) ZEND_FE_END }; + +static void register_php_spl_symbols(int module_number) +{ + REGISTER_LONG_CONSTANT("SPL_AUTOLOAD_CLASS", ZEND_AUTOLOAD_CLASS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SPL_AUTOLOAD_FUNCTION", ZEND_AUTOLOAD_FUNCTION, CONST_PERSISTENT); +} diff --git a/ext/spl/tests/spl_autoload_functions_001.phpt b/ext/spl/tests/spl_autoload_functions_001.phpt new file mode 100644 index 0000000000000..3892d167a33ef --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_001.phpt @@ -0,0 +1,13 @@ +--TEST-- +SPL autoloader can autoload functions +--FILE-- + +--EXPECT-- +name=foo diff --git a/ext/spl/tests/spl_autoload_functions_002.phpt b/ext/spl/tests/spl_autoload_functions_002.phpt new file mode 100644 index 0000000000000..77bfd119a8582 --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_002.phpt @@ -0,0 +1,18 @@ +--TEST-- +SPL autoloader calls local functions first +--FILE-- + +--EXPECT-- +name=foo\strlen +3 diff --git a/ext/spl/tests/spl_autoload_functions_003.phpt b/ext/spl/tests/spl_autoload_functions_003.phpt new file mode 100644 index 0000000000000..483996a7543af --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_003.phpt @@ -0,0 +1,22 @@ +--TEST-- +SPL autoloader only autoloads a local function once +--FILE-- + +--EXPECTF-- +name=foo\no_exist + +Fatal error: Uncaught Error: Call to undefined function Foo\no_exist() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/spl/tests/spl_autoload_functions_004.phpt b/ext/spl/tests/spl_autoload_functions_004.phpt new file mode 100644 index 0000000000000..f70ce1be0a329 --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +SPL autoloader can unregister function autoloaders +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined function bar() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/spl/tests/spl_autoload_functions_005.phpt b/ext/spl/tests/spl_autoload_functions_005.phpt new file mode 100644 index 0000000000000..1f94053b3f1fa --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_005.phpt @@ -0,0 +1,38 @@ +--TEST-- +SPL autoloader can be used to autoload dynamic functions +--FILE-- +invoke('baz'); + +eval("Foo5('baz');"); + +(Foo6(...))('bax'); + +$funcs = ['Foo7', 'Foo8']; + +array_map(fn($f) => $f(), $funcs); + +Foo9(); + +?> +--EXPECT-- +name=Foo1 +name=Foo2 +name=Foo3 +name=Foo4 +name=Foo5 +name=Foo6 +name=Foo7 +name=Foo8 +name=Foo9 diff --git a/ext/spl/tests/spl_autoload_functions_006.phpt b/ext/spl/tests/spl_autoload_functions_006.phpt new file mode 100644 index 0000000000000..14a1588d52573 --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_006.phpt @@ -0,0 +1,18 @@ +--TEST-- +show bounded autoload +--FILE-- + +--EXPECT-- +name=test\strlen +333 diff --git a/ext/spl/tests/spl_autoload_functions_007.phpt b/ext/spl/tests/spl_autoload_functions_007.phpt new file mode 100644 index 0000000000000..c2609c0a70c0a --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_007.phpt @@ -0,0 +1,16 @@ +--TEST-- +autoloading different cases results in calling the autoloader once +--FILE-- + +--EXPECT-- +name=FOO +FOOFOO diff --git a/ext/spl/tests/spl_autoload_functions_008.phpt b/ext/spl/tests/spl_autoload_functions_008.phpt new file mode 100644 index 0000000000000..0da4cbab6b487 --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_008.phpt @@ -0,0 +1,36 @@ +--TEST-- +SPL function autoload with exceptions +--FILE-- +autoLoad(...), type: SPL_AUTOLOAD_FUNCTION); +spl_autoload_register($myAutoLoader->autoThrow(...), type: SPL_AUTOLOAD_FUNCTION); + +try +{ + var_dump(function_exists("TestFunc", true)); +} +catch(Exception $e) +{ + echo 'Exception: ' . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +MyAutoLoader::autoLoad(TestFunc) +MyAutoLoader::autoThrow(TestFunc) +Exception: Unavailable diff --git a/ext/spl/tests/spl_autoload_functions_009.phpt b/ext/spl/tests/spl_autoload_functions_009.phpt new file mode 100644 index 0000000000000..c518fe8427f0f --- /dev/null +++ b/ext/spl/tests/spl_autoload_functions_009.phpt @@ -0,0 +1,37 @@ +--TEST-- +can retrieve autoloader for functions and classes +--FILE-- + +--EXPECTF-- +class AClass +function AFunction +bool(true) +bool(true) +bool(true) +bool(true) + +Fatal error: Uncaught Error: spl_autoload_functions() expects either ZEND_AUTOLOAD_CLASS or ZEND_AUTOLOAD_FUNCTION as the first argument in %s:%d +Stack trace: +#0 %s(%d): spl_autoload_functions(3) +#1 {main} + thrown in %s on line %d