From 51493d4709b466896acdc270e7ea6e19152dab71 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:16:01 +0200 Subject: [PATCH 1/9] Introduce zend_lookup_function() --- Zend/zend_API.c | 23 +--------------------- Zend/zend_execute.c | 28 +++++++++------------------ Zend/zend_execute.h | 2 ++ Zend/zend_execute_API.c | 43 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_vm_def.h | 30 +++++++++++++--------------- Zend/zend_vm_execute.h | 30 +++++++++++++--------------- 6 files changed, 81 insertions(+), 75 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 229af38c2a8c4..063099c0f7c51 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3842,32 +3842,11 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ int call_via_handler = 0; zend_class_entry *scope; zval *zv; - ALLOCA_FLAG(use_heap) fcc->calling_scope = NULL; if (!ce_org) { - zend_function *func; - zend_string *lmname; - - /* Check if function with given name exists. - * This may be a compound name that includes namespace name */ - if (UNEXPECTED(Z_STRVAL_P(callable)[0] == '\\')) { - /* 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); - ZSTR_ALLOCA_FREE(lmname, use_heap); - } else { - lmname = Z_STR_P(callable); - func = zend_fetch_function(lmname); - 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); - ZSTR_ALLOCA_FREE(lmname, use_heap); - } - } + zend_function *func = zend_fetch_function(Z_STR_P(callable)); if (EXPECTED(func != NULL)) { fcc->function_handler = func; return 1; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 602bb3b0e79f5..657374fccd6e3 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4211,19 +4211,19 @@ 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) /* {{{ */ { - zval *zv = zend_hash_find(EG(function_table), name); + zend_function *fbc = zend_lookup_function(name); - if (EXPECTED(zv != NULL)) { - zend_function *fbc = Z_FUNC_P(zv); + if (UNEXPECTED(fbc == NULL)) { + return NULL; + } - if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { - init_func_run_time_cache_i(&fbc->op_array); - } - return fbc; + if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { + init_func_run_time_cache_i(&fbc->op_array); } - return NULL; + return fbc; } /* }}} */ +// TODO Update or drop as this indicates a zend_call_method() without an object... ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function_str(const char *name, size_t len) /* {{{ */ { zval *zv = zend_hash_str_find(EG(function_table), name, len); @@ -4814,7 +4814,6 @@ static void zend_swap_operands(zend_op *op) /* {{{ */ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_string *function, uint32_t num_args) /* {{{ */ { zend_function *fbc; - zval *func; zend_class_entry *called_scope; zend_string *lcname; const char *colon; @@ -4866,20 +4865,11 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s init_func_run_time_cache(&fbc->op_array); } } else { - if (ZSTR_VAL(function)[0] == '\\') { - lcname = zend_string_alloc(ZSTR_LEN(function) - 1, 0); - zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(function) + 1, ZSTR_LEN(function) - 1); - } else { - lcname = zend_string_tolower(function); - } - if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) { + if (UNEXPECTED((fbc = zend_lookup_function(function)) == NULL)) { zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function)); - zend_string_release_ex(lcname, 0); return NULL; } - zend_string_release_ex(lcname, 0); - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index fe854f305150c..eebd800d8f9d5 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -48,6 +48,8 @@ ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value); ZEND_API void execute_ex(zend_execute_data *execute_data); ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value); ZEND_API bool zend_is_valid_class_name(zend_string *name); +ZEND_API zend_function *zend_lookup_function(zend_string *name); +ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string *lcname, bool use_autoload); 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 zend_class_entry *zend_get_called_scope(zend_execute_data *ex); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index f1f5bfc84516f..212620b6ed4be 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1142,6 +1142,49 @@ static const uint32_t valid_chars[8] = { 0xffffffff, }; +ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string *lc_key, bool use_autoload) +{ + zend_function *fbc = NULL; + zval *func; + zend_string *lc_name; + zend_string *autoload_name; + + if (lc_key) { + lc_name = lc_key; + } else { + if (name == NULL || !ZSTR_LEN(name)) { + return NULL; + } + + if (ZSTR_VAL(name)[0] == '\\') { + lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0); + zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); + } else { + lc_name = zend_string_tolower(name); + } + } + + func = zend_hash_find(EG(function_table), lc_name); + + if (EXPECTED(func)) { + if (!lc_key) { + zend_string_release_ex(lc_name, 0); + } + fbc = Z_FUNC_P(func); + return fbc; + } + + if (!lc_key) { + zend_string_release_ex(lc_name, 0); + } + return NULL; +} + +ZEND_API zend_function *zend_lookup_function(zend_string *name) /* {{{ */ +{ + return zend_lookup_function_ex(name, NULL, 0); +} + ZEND_API bool zend_is_valid_class_name(zend_string *name) { for (size_t i = 0; i < ZSTR_LEN(name); i++) { unsigned char c = ZSTR_VAL(name)[i]; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 55fdb7d46582b..4de7cb958a348 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3818,17 +3818,16 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) { USE_OPLINE zend_function *fbc; - zval *function_name, *func; zend_execute_data *call; 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)); - if (UNEXPECTED(func == NULL)) { + zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); + /* Fetch lowercase name stored in the next literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } @@ -3960,22 +3959,21 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) { USE_OPLINE - zval *func_name; - zval *func; zend_function *fbc; zend_execute_data *call; 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)); - if (func == NULL) { - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 2)); - if (UNEXPECTED(func == NULL)) { + zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); + /* Fetch lowercase name stored in the next literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + /* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true); + if (fbc == NULL) { ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } } - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } @@ -3993,15 +3991,13 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT) { USE_OPLINE - zval *fname; - zval *func; zend_function *fbc; zend_execute_data *call; fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { - fname = (zval*)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(fname)); + zval *fname = (zval*)RT_CONSTANT(opline, opline->op2); + zval *func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(fname)); ZEND_ASSERT(func != NULL && "Function existence must be checked at compile time"); fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 16455b6e0cd58..a05ec82f968b5 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3887,17 +3887,16 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME { USE_OPLINE zend_function *fbc; - zval *function_name, *func; zend_execute_data *call; 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)); - if (UNEXPECTED(func == NULL)) { + zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); + /* Fetch lowercase name stored in the next literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } @@ -3967,22 +3966,21 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_CONST_H static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE - zval *func_name; - zval *func; zend_function *fbc; zend_execute_data *call; 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)); - if (func == NULL) { - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 2)); - if (UNEXPECTED(func == NULL)) { + zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); + /* Fetch lowercase name stored in the next literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + /* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true); + if (fbc == NULL) { ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } } - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } @@ -4000,15 +3998,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE - zval *fname; - zval *func; zend_function *fbc; zend_execute_data *call; fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { - fname = (zval*)RT_CONSTANT(opline, opline->op2); - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(fname)); + zval *fname = (zval*)RT_CONSTANT(opline, opline->op2); + zval *func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(fname)); ZEND_ASSERT(func != NULL && "Function existence must be checked at compile time"); fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { From c24009cc32b88f3a486ab236b5df52bd3ff9b074 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:16:09 +0200 Subject: [PATCH 2/9] New core class autoloader --- .../class/autoload_call_basic.phpt | 17 ++ .../class/autoload_call_invalid_name.phpt | 36 +++ .../class/autoload_called_scope.phpt | 6 +- .../class/autoload_invalid_name_variable.phpt | 41 +++ ...toloader_with_closures_and_invocables.phpt | 68 +++++ .../{ => autoloading/class}/bug42798.phpt | 4 +- .../{ => autoloading/class}/bug46665.phpt | 2 +- .../class}/bug46665_autoload.inc | 0 .../tests/autoloading/class}/bug65006.phpt | 8 +- Zend/tests/autoloading/class/bug71204.phpt | 19 ++ .../tests/autoloading/class}/bug73896.phpt | 7 +- .../autoloading/class/destructor_call.phpt | 6 +- .../class/emit-parse-error-in-autoloader.phpt | 6 +- .../exceptions_during_autoloading001.phpt | 26 +- .../exceptions_during_autoloading002.phpt | 18 +- .../exceptions_during_autoloading003.phpt | 16 +- .../class/innacessible_methods.phpt | 20 +- Zend/tests/autoloading/class/methods.phpt | 43 +++ Zend/tests/autoloading/class/prepending.phpt | 34 +++ .../autoloading/class/register_class.phpt | 59 ++++ .../class/same_class_different_instances.phpt | 47 +++ .../autoloading/class/static_methods.phpt | 15 +- ...hrow_with_autoload_call_as_autoloader.phpt | 14 + .../class/trampoline_autoloader.phpt | 56 ++++ ...oline_unregister_autoloader_from_list.phpt | 46 +++ .../un-register_take_effect_immediately.phpt | 8 +- .../class/unregister_autoloader.phpt | 33 +++ .../unregister_autoloader_from_list.phpt | 22 ++ Zend/zend_autoload.c | 247 ++++++++++++++++ Zend/zend_autoload.h | 32 +++ Zend/zend_builtin_functions.c | 3 + Zend/zend_builtin_functions.stub.php | 8 + Zend/zend_builtin_functions_arginfo.h | 26 +- Zend/zend_execute.h | 3 +- Zend/zend_execute_API.c | 11 +- configure.ac | 1 + ext/spl/php_spl.c | 272 +----------------- ext/spl/php_spl.stub.php | 3 + ext/spl/php_spl_arginfo.h | 14 +- ext/spl/tests/bug40091.phpt | 41 --- ext/spl/tests/bug44144.phpt | 23 -- ext/spl/tests/bug48023.phpt | 15 - ext/spl/tests/bug48493.phpt | 26 -- ext/spl/tests/bug61697.phpt | 24 -- ext/spl/tests/bug71204.phpt | 19 -- ext/spl/tests/bug75049.phpt | 17 -- ext/spl/tests/spl_autoload_004.phpt | 40 --- ext/spl/tests/spl_autoload_005.phpt | 49 ---- ext/spl/tests/spl_autoload_009.phpt | 23 -- ext/spl/tests/spl_autoload_010.phpt | 27 -- ext/spl/tests/spl_autoload_013.phpt | 55 ---- ext/spl/tests/spl_autoload_014.phpt | 45 --- ext/spl/tests/spl_autoload_bug48541.phpt | 37 --- ext/spl/tests/spl_autoload_call_basic.phpt | 18 -- ...ith_spl_autoloader_call_as_autoloader.phpt | 2 +- ...load_unregister_without_registrations.phpt | 2 +- win32/build/config.w32 | 3 +- 57 files changed, 944 insertions(+), 819 deletions(-) create mode 100644 Zend/tests/autoloading/class/autoload_call_basic.phpt create mode 100644 Zend/tests/autoloading/class/autoload_call_invalid_name.phpt rename ext/spl/tests/spl_autoload_called_scope.phpt => Zend/tests/autoloading/class/autoload_called_scope.phpt (72%) create mode 100644 Zend/tests/autoloading/class/autoload_invalid_name_variable.phpt create mode 100644 Zend/tests/autoloading/class/autoloader_with_closures_and_invocables.phpt rename Zend/tests/{ => autoloading/class}/bug42798.phpt (55%) rename Zend/tests/{ => autoloading/class}/bug46665.phpt (85%) rename Zend/tests/{ => autoloading/class}/bug46665_autoload.inc (100%) rename {ext/spl/tests => Zend/tests/autoloading/class}/bug65006.phpt (72%) create mode 100644 Zend/tests/autoloading/class/bug71204.phpt rename {ext/spl/tests => Zend/tests/autoloading/class}/bug73896.phpt (70%) rename ext/spl/tests/spl_autoload_011.phpt => Zend/tests/autoloading/class/destructor_call.phpt (76%) rename ext/spl/tests/bug74372.phpt => Zend/tests/autoloading/class/emit-parse-error-in-autoloader.phpt (51%) rename ext/spl/tests/spl_autoload_003.phpt => Zend/tests/autoloading/class/exceptions_during_autoloading001.phpt (57%) rename ext/spl/tests/spl_autoload_008.phpt => Zend/tests/autoloading/class/exceptions_during_autoloading002.phpt (77%) rename ext/spl/tests/spl_autoload_012.phpt => Zend/tests/autoloading/class/exceptions_during_autoloading003.phpt (66%) rename ext/spl/tests/spl_autoload_007.phpt => Zend/tests/autoloading/class/innacessible_methods.phpt (60%) create mode 100644 Zend/tests/autoloading/class/methods.phpt create mode 100644 Zend/tests/autoloading/class/prepending.phpt create mode 100644 Zend/tests/autoloading/class/register_class.phpt create mode 100644 Zend/tests/autoloading/class/same_class_different_instances.phpt rename ext/spl/tests/spl_autoload_006.phpt => Zend/tests/autoloading/class/static_methods.phpt (52%) create mode 100644 Zend/tests/autoloading/class/throw_with_autoload_call_as_autoloader.phpt create mode 100644 Zend/tests/autoloading/class/trampoline_autoloader.phpt create mode 100644 Zend/tests/autoloading/class/trampoline_unregister_autoloader_from_list.phpt rename ext/spl/tests/bug71202.phpt => Zend/tests/autoloading/class/un-register_take_effect_immediately.phpt (68%) create mode 100644 Zend/tests/autoloading/class/unregister_autoloader.phpt create mode 100644 Zend/tests/autoloading/class/unregister_autoloader_from_list.phpt create mode 100644 Zend/zend_autoload.c create mode 100644 Zend/zend_autoload.h delete mode 100644 ext/spl/tests/bug40091.phpt delete mode 100644 ext/spl/tests/bug44144.phpt delete mode 100644 ext/spl/tests/bug48023.phpt delete mode 100644 ext/spl/tests/bug48493.phpt delete mode 100644 ext/spl/tests/bug61697.phpt delete mode 100644 ext/spl/tests/bug71204.phpt delete mode 100644 ext/spl/tests/bug75049.phpt delete mode 100644 ext/spl/tests/spl_autoload_004.phpt delete mode 100644 ext/spl/tests/spl_autoload_005.phpt delete mode 100644 ext/spl/tests/spl_autoload_009.phpt delete mode 100644 ext/spl/tests/spl_autoload_010.phpt delete mode 100644 ext/spl/tests/spl_autoload_013.phpt delete mode 100644 ext/spl/tests/spl_autoload_014.phpt delete mode 100644 ext/spl/tests/spl_autoload_bug48541.phpt delete mode 100644 ext/spl/tests/spl_autoload_call_basic.phpt diff --git a/Zend/tests/autoloading/class/autoload_call_basic.phpt b/Zend/tests/autoloading/class/autoload_call_basic.phpt new file mode 100644 index 0000000000000..eae6361fe89c1 --- /dev/null +++ b/Zend/tests/autoloading/class/autoload_call_basic.phpt @@ -0,0 +1,17 @@ +--TEST-- +Basic autoload_call_class() function +--CREDITS-- +Jean-Marc Fontaine +# Alter Way Contribution Day 2011 +--FILE-- + +--EXPECT-- +bool(true) diff --git a/Zend/tests/autoloading/class/autoload_call_invalid_name.phpt b/Zend/tests/autoloading/class/autoload_call_invalid_name.phpt new file mode 100644 index 0000000000000..95ffae3569b20 --- /dev/null +++ b/Zend/tests/autoloading/class/autoload_call_invalid_name.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test autoload_call_class() with invalid symbol name +--FILE-- +getMessage(); +} +try { + autoload_call_class('"'); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +try { + autoload_call_class(''); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +try { + autoload_call_class("al\no"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +?> +--EXPECT-- +string(6) "12ayhs" +string(1) """ +string(0) "" +string(4) "al +o" diff --git a/ext/spl/tests/spl_autoload_called_scope.phpt b/Zend/tests/autoloading/class/autoload_called_scope.phpt similarity index 72% rename from ext/spl/tests/spl_autoload_called_scope.phpt rename to Zend/tests/autoloading/class/autoload_called_scope.phpt index 23f2e84d952b6..d19fc3d9c91d2 100644 --- a/ext/spl/tests/spl_autoload_called_scope.phpt +++ b/Zend/tests/autoloading/class/autoload_called_scope.phpt @@ -1,11 +1,11 @@ --TEST-- -SPL autoloader should not do anything magic with called scope +Autoloader should not do anything magic with called scope --FILE-- getMessage(), \PHP_EOL; +} +$name = '"'; +try { + new $name; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +$name = ''; +try { + new $name; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +$name = "al\no"; +try { + new $name; +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +?> +--EXPECT-- +string(6) "12ayhs" +Error: Class "12ayhs" not found +Error: Class """ not found +Error: Class "" not found +Error: Class "al +o" not found diff --git a/Zend/tests/autoloading/class/autoloader_with_closures_and_invocables.phpt b/Zend/tests/autoloading/class/autoloader_with_closures_and_invocables.phpt new file mode 100644 index 0000000000000..cae10c006208b --- /dev/null +++ b/Zend/tests/autoloading/class/autoloader_with_closures_and_invocables.phpt @@ -0,0 +1,68 @@ +--TEST-- +Autoloading with closures and invocables +--FILE-- +dir}') called with $class\n"); + } +} + +class WorkingAutoloader { + public function __invoke($class) { + echo ("WorkingAutoloader() called with $class\n"); + eval("class $class { }"); + } +} + +$al1 = new Autoloader('d1'); +$al2 = new WorkingAutoloader('d2'); + +autoload_register_class($closure); +autoload_register_class($al1); +autoload_register_class($al2); + +var_dump(autoload_list_class()); + +$x = new TestX; + +autoload_unregister_class($closure); +autoload_unregister_class($al1); + +$y = new TestY; + +?> +--EXPECTF-- +array(3) { + [0]=> + object(Closure)#1 (4) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["parameter"]=> + array(1) { + ["$name"]=> + string(10) "" + } + } + [1]=> + object(Autoloader)#2 (1) { + ["dir":"Autoloader":private]=> + string(2) "d1" + } + [2]=> + object(WorkingAutoloader)#3 (0) { + } +} +autoload(TestX) +Autoloader('d1') called with TestX +WorkingAutoloader() called with TestX +WorkingAutoloader() called with TestY diff --git a/Zend/tests/bug42798.phpt b/Zend/tests/autoloading/class/bug42798.phpt similarity index 55% rename from Zend/tests/bug42798.phpt rename to Zend/tests/autoloading/class/bug42798.phpt index 5f19fe3080255..23a38b7cbc730 100644 --- a/Zend/tests/bug42798.phpt +++ b/Zend/tests/autoloading/class/bug42798.phpt @@ -1,8 +1,8 @@ --TEST-- -Bug #42798 (_autoload() not triggered for classes used in method signature) +Bug #42798 (Autoloading not triggered for classes used in method signature) --FILE-- --EXPECTF-- diff --git a/Zend/tests/autoloading/class/bug71204.phpt b/Zend/tests/autoloading/class/bug71204.phpt new file mode 100644 index 0000000000000..bb75036ed9c05 --- /dev/null +++ b/Zend/tests/autoloading/class/bug71204.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #71204 (segfault if clean autoloaders while autoloading) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "A" not found in %s:%d +Stack trace: +#0 {main} + thrown in %sbug71204.php on line %d diff --git a/ext/spl/tests/bug73896.phpt b/Zend/tests/autoloading/class/bug73896.phpt similarity index 70% rename from ext/spl/tests/bug73896.phpt rename to Zend/tests/autoloading/class/bug73896.phpt index 657f30c50dbec..3e32bac243e47 100644 --- a/ext/spl/tests/bug73896.phpt +++ b/Zend/tests/autoloading/class/bug73896.phpt @@ -1,16 +1,16 @@ --TEST-- -Bug #73896 (spl_autoload() crashes when calls magic _call()) +Bug #73896 (autoload_register_class() crashes when calls magic __call()) --FILE-- var = 2; -spl_autoload_register(array($a, 'autoload')); +autoload_register_class(array($a, 'autoload')); unset($a); var_dump(class_exists("C", true)); diff --git a/ext/spl/tests/bug74372.phpt b/Zend/tests/autoloading/class/emit-parse-error-in-autoloader.phpt similarity index 51% rename from ext/spl/tests/bug74372.phpt rename to Zend/tests/autoloading/class/emit-parse-error-in-autoloader.phpt index c2506009a5846..a5c70c8faf1ac 100644 --- a/ext/spl/tests/bug74372.phpt +++ b/Zend/tests/autoloading/class/emit-parse-error-in-autoloader.phpt @@ -1,12 +1,12 @@ --TEST-- -Bug #74372: autoloading file with syntax error uses next autoloader, may hide parse error +Parse errors should be thrown if occuring from an autoloader --FILE-- getMessage() . "\n"; } diff --git a/ext/spl/tests/spl_autoload_008.phpt b/Zend/tests/autoloading/class/exceptions_during_autoloading002.phpt similarity index 77% rename from ext/spl/tests/spl_autoload_008.phpt rename to Zend/tests/autoloading/class/exceptions_during_autoloading002.phpt index 738c691ddfe9f..e1baf602e96ff 100644 --- a/ext/spl/tests/spl_autoload_008.phpt +++ b/Zend/tests/autoloading/class/exceptions_during_autoloading002.phpt @@ -1,7 +1,5 @@ --TEST-- -SPL: spl_autoload() with exceptions ---INI-- -include_path=. +Exceptions during autoloading --FILE-- $func) var_dump($func); try { - spl_autoload_register($func); + autoload_register_class($func); } catch (TypeError $e) { echo get_class($e) . ': ' . $e->getMessage() . \PHP_EOL; - var_dump(count(spl_autoload_functions())); + var_dump(count(autoload_list_class())); continue; } - if (count(spl_autoload_functions())) { + if (count(autoload_list_class())) { echo "registered\n"; try { @@ -61,8 +59,8 @@ foreach($funcs as $idx => $func) } } - spl_autoload_unregister($func); - var_dump(count(spl_autoload_functions())); + autoload_unregister_class($func); + var_dump(count(autoload_list_class())); } ?> @@ -81,7 +79,7 @@ Exception: Bla int(0) ====2==== string(22) "MyAutoLoader::dynaLoad" -TypeError: spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, non-static method MyAutoLoader::dynaLoad() cannot be called statically +TypeError: autoload_register_class(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically int(0) ====3==== array(2) { @@ -101,7 +99,7 @@ array(2) { [1]=> string(8) "dynaLoad" } -TypeError: spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, non-static method MyAutoLoader::dynaLoad() cannot be called statically +TypeError: autoload_register_class(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically int(0) ====5==== array(2) { diff --git a/ext/spl/tests/spl_autoload_012.phpt b/Zend/tests/autoloading/class/exceptions_during_autoloading003.phpt similarity index 66% rename from ext/spl/tests/spl_autoload_012.phpt rename to Zend/tests/autoloading/class/exceptions_during_autoloading003.phpt index 218d3e800ff23..45ef5599f7cce 100644 --- a/ext/spl/tests/spl_autoload_012.phpt +++ b/Zend/tests/autoloading/class/exceptions_during_autoloading003.phpt @@ -1,22 +1,20 @@ --TEST-- -SPL: spl_autoload() capturing multiple Exceptions in __autoload +Capturing multiple Exceptions during autoloading --FILE-- $func) if ($idx) echo "\n"; try { var_dump($func); - spl_autoload_register($func); + autoload_register_class($func); echo "ok\n"; } catch(\TypeError $e) { echo $e->getMessage() . \PHP_EOL; @@ -52,16 +52,16 @@ foreach($funcs as $idx => $func) ?> --EXPECTF-- string(22) "MyAutoLoader::notExist" -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, class MyAutoLoader does not have a method "notExist" +autoload_register_class(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" string(22) "MyAutoLoader::noAccess" -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, cannot access protected method MyAutoLoader::noAccess() +autoload_register_class(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() string(22) "MyAutoLoader::autoLoad" ok string(22) "MyAutoLoader::dynaLoad" -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, non-static method MyAutoLoader::dynaLoad() cannot be called statically +autoload_register_class(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically array(2) { [0]=> @@ -69,7 +69,7 @@ array(2) { [1]=> string(8) "notExist" } -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, class MyAutoLoader does not have a method "notExist" +autoload_register_class(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" array(2) { [0]=> @@ -77,7 +77,7 @@ array(2) { [1]=> string(8) "noAccess" } -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, cannot access protected method MyAutoLoader::noAccess() +autoload_register_class(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() array(2) { [0]=> @@ -93,7 +93,7 @@ array(2) { [1]=> string(8) "dynaLoad" } -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, non-static method MyAutoLoader::dynaLoad() cannot be called statically +autoload_register_class(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically array(2) { [0]=> @@ -102,7 +102,7 @@ array(2) { [1]=> string(8) "notExist" } -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, class MyAutoLoader does not have a method "notExist" +autoload_register_class(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" array(2) { [0]=> @@ -111,7 +111,7 @@ array(2) { [1]=> string(8) "noAccess" } -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, cannot access protected method MyAutoLoader::noAccess() +autoload_register_class(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() array(2) { [0]=> diff --git a/Zend/tests/autoloading/class/methods.phpt b/Zend/tests/autoloading/class/methods.phpt new file mode 100644 index 0000000000000..f2f37f70ed23a --- /dev/null +++ b/Zend/tests/autoloading/class/methods.phpt @@ -0,0 +1,43 @@ +--TEST-- +Autoloader is a method +--FILE-- +getMessage() . \PHP_EOL; +} + +// and + +$myAutoLoader = new MyAutoLoader(); + +autoload_register_class(array($myAutoLoader, 'autoLoad')); +autoload_register_class(array($myAutoLoader, 'autoThrow')); + +try { + var_dump(class_exists("TestClass", true)); +} catch(Exception $e) { + echo 'Exception: ' . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +autoload_register_class(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::autoLoad() cannot be called statically +MyAutoLoader::autoLoad(TestClass) +MyAutoLoader::autoThrow(TestClass) +Exception: Unavailable diff --git a/Zend/tests/autoloading/class/prepending.phpt b/Zend/tests/autoloading/class/prepending.phpt new file mode 100644 index 0000000000000..6b048732c6838 --- /dev/null +++ b/Zend/tests/autoloading/class/prepending.phpt @@ -0,0 +1,34 @@ +--TEST-- +Prepending autoloaders +--FILE-- + $name\n"; +} +function autoloadB($name) { + echo "B -> $name\n"; +} +function autoloadC($name) { + echo "C -> $name\n"; + class C{} +} + +autoload_register_class('autoloadA'); +autoload_register_class('autoloadB', true); +autoload_register_class('autoloadC'); +var_dump(autoload_list_class()); + +new C; +?> +--EXPECT-- +array(3) { + [0]=> + string(9) "autoloadB" + [1]=> + string(9) "autoloadA" + [2]=> + string(9) "autoloadC" +} +B -> C +A -> C +C -> C diff --git a/Zend/tests/autoloading/class/register_class.phpt b/Zend/tests/autoloading/class/register_class.phpt new file mode 100644 index 0000000000000..d44fcbcda3b7b --- /dev/null +++ b/Zend/tests/autoloading/class/register_class.phpt @@ -0,0 +1,59 @@ +--TEST-- +Test autoload_register_class(): basic function behavior 001 +--FILE-- +getMessage(), \PHP_EOL; +} +*/ +echo "===EMPTY===\n"; + +function TestFunc1($classname) { + echo __METHOD__ . "($classname)\n"; +} + +function TestFunc2($classname) { + echo __METHOD__ . "($classname)\n"; +} + +echo "===REGISTER===\n"; + +autoload_register_class("TestFunc1"); +autoload_register_class("TestFunc2"); +autoload_register_class("TestFunc2"); // 2nd call ignored + +var_dump(class_exists("TestClass", true)); + +echo "===LOAD===\n"; + +autoload_register_class("spl_autoload"); +var_dump(class_exists("TestClass", true)); + +echo "===NOFUNCTION===\n"; + +try { + autoload_register_class("unavailable_autoload_function"); +} catch(\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +?> +--EXPECT-- +===EMPTY=== +===REGISTER=== +TestFunc1(TestClass) +TestFunc2(TestClass) +bool(false) +===LOAD=== +TestFunc1(TestClass) +TestFunc2(TestClass) +bool(false) +===NOFUNCTION=== +autoload_register_class(): Argument #1 ($callback) must be a valid callback, function "unavailable_autoload_function" not found or invalid function name diff --git a/Zend/tests/autoloading/class/same_class_different_instances.phpt b/Zend/tests/autoloading/class/same_class_different_instances.phpt new file mode 100644 index 0000000000000..a995b26532f3c --- /dev/null +++ b/Zend/tests/autoloading/class/same_class_different_instances.phpt @@ -0,0 +1,47 @@ +--TEST-- +Registering two different instance of a class as an autoloader should work +--FILE-- +directory_to_use, "\n"; + } +} + +$autoloader1 = new MyAutoloader('dir1'); +autoload_register_class(array($autoloader1, 'autoload')); + +$autoloader2 = new MyAutoloader('dir2'); +autoload_register_class(array($autoloader2, 'autoload')); + +var_dump(autoload_list_class()); +var_dump(class_exists('NonExisting')); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(MyAutoloader)#1 (1) { + ["directory_to_use":"MyAutoloader":private]=> + string(4) "dir1" + } + [1]=> + string(8) "autoload" + } + [1]=> + array(2) { + [0]=> + object(MyAutoloader)#2 (1) { + ["directory_to_use":"MyAutoloader":private]=> + string(4) "dir2" + } + [1]=> + string(8) "autoload" + } +} +dir1 +dir2 +bool(false) diff --git a/ext/spl/tests/spl_autoload_006.phpt b/Zend/tests/autoloading/class/static_methods.phpt similarity index 52% rename from ext/spl/tests/spl_autoload_006.phpt rename to Zend/tests/autoloading/class/static_methods.phpt index 7db2b462ca367..c6903ad337b61 100644 --- a/ext/spl/tests/spl_autoload_006.phpt +++ b/Zend/tests/autoloading/class/static_methods.phpt @@ -1,20 +1,17 @@ --TEST-- -SPL: spl_autoload() with static methods ---INI-- -include_path=. +Autoloader is a static method --FILE-- getMessage() . \PHP_EOL; +} + +?> +--EXPECT-- +autoload_register_class(): Argument #1 ($callback) must not be the autoload_call_class() function diff --git a/Zend/tests/autoloading/class/trampoline_autoloader.phpt b/Zend/tests/autoloading/class/trampoline_autoloader.phpt new file mode 100644 index 0000000000000..506c5333f0484 --- /dev/null +++ b/Zend/tests/autoloading/class/trampoline_autoloader.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test autoload_unregister_class(): behavior with a trampoline +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline1" + } + [1]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline2" + } +} +Trampoline for trampoline1 +Trampoline for trampoline2 +bool(false) +Unregister trampoline: +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/Zend/tests/autoloading/class/trampoline_unregister_autoloader_from_list.phpt b/Zend/tests/autoloading/class/trampoline_unregister_autoloader_from_list.phpt new file mode 100644 index 0000000000000..6ef40a42a19d4 --- /dev/null +++ b/Zend/tests/autoloading/class/trampoline_unregister_autoloader_from_list.phpt @@ -0,0 +1,46 @@ +--TEST-- +Unregister all class autoloaders by traversing the registered list: behavior with a trampoline +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline1" + } + [1]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline2" + } +} +array(0) { +} diff --git a/ext/spl/tests/bug71202.phpt b/Zend/tests/autoloading/class/un-register_take_effect_immediately.phpt similarity index 68% rename from ext/spl/tests/bug71202.phpt rename to Zend/tests/autoloading/class/un-register_take_effect_immediately.phpt index 84c9b609445f1..2b66aaca247f0 100644 --- a/ext/spl/tests/bug71202.phpt +++ b/Zend/tests/autoloading/class/un-register_take_effect_immediately.phpt @@ -1,5 +1,5 @@ --TEST-- -Bug #71202 (Autoload function registered by another not activated immediately) +(Un)Registering autoloaders must take effect immidiately --FILE-- +--EXPECT-- +TestFunc1(TestClass) +TestFunc2(TestClass) +bool(false) +bool(true) +bool(false) +TestFunc2(TestClass) +bool(false) diff --git a/Zend/tests/autoloading/class/unregister_autoloader_from_list.phpt b/Zend/tests/autoloading/class/unregister_autoloader_from_list.phpt new file mode 100644 index 0000000000000..1072abfe6c5bb --- /dev/null +++ b/Zend/tests/autoloading/class/unregister_autoloader_from_list.phpt @@ -0,0 +1,22 @@ +--TEST-- +Unregister all autoloaders by traversing the registered list +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c new file mode 100644 index 0000000000000..dd8aa26cb96f9 --- /dev/null +++ b/Zend/zend_autoload.c @@ -0,0 +1,247 @@ +/* + +----------------------------------------------------------------------+ + | 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: George 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_interfaces.h" +#include "zend_string.h" + +ZEND_TLS HashTable *autoloader_class_autoload_functions; + +#define HT_MOVE_TAIL_TO_HEAD(ht) \ + do { \ + Bucket tmp = (ht)->arData[(ht)->nNumUsed-1]; \ + memmove((ht)->arData + 1, (ht)->arData, \ + sizeof(Bucket) * ((ht)->nNumUsed - 1)); \ + (ht)->arData[0] = tmp; \ + if (!((ht)->u.flags & HASH_FLAG_PACKED)) { \ + zend_hash_rehash(ht); \ + } else { \ + zend_autoload_reindex(ht); \ + } \ + } while (0) + +static void zend_autoload_reindex(HashTable *ht) +{ + ZEND_ASSERT(ht->u.flags & HASH_FLAG_PACKED); + for (size_t i = 0; i < ht->nNumUsed; i++) { + ht->arData[i].h = i; + } +} + +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(HashTable *autoloader_table, 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); + + 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 (1) { + 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); + } + if (zend_hash_exists(EG(class_table), lc_name)) { + return (zend_class_entry*) zend_hash_find_ptr(EG(class_table), lc_name); + } + + zend_hash_move_forward_ex(class_autoload_functions, &pos); + } + return NULL; +} + +/* Needed for compatibility with spl_register_autoload() */ +ZEND_API void zend_register_class_autoloader(zend_fcall_info *fci, zend_fcall_info_cache *fcc, bool prepend) +{ + 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, 0); + /* Initialize as non-packed hash table for prepend functionality. */ + zend_hash_real_init_mixed(autoloader_class_autoload_functions); + } + + if (!ZEND_FCC_INITIALIZED(*fcc)) { + ZEND_ASSERT(fci && ZEND_FCI_INITIALIZED(*fci) && "FCI Must be provided if the callable is a trampoline"); + /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal + * with it ourselves. It is important that it is not refetched on every call, + * because calls may occur from different scopes. */ + zend_is_callable_ex(&fci->function_name, NULL, 0, NULL, fcc, NULL); + } + + 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 call trampoline */ + zend_release_fcall_info_cache(fcc); + return; + } + + zend_fcall_info_cache *entry = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(entry, fcc); + zend_hash_next_index_insert_ptr(autoloader_class_autoload_functions, entry); + if (prepend && zend_hash_num_elements(autoloader_class_autoload_functions) > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(autoloader_class_autoload_functions); + } +} + +// TODO USERLAND FUNCTIONS, maybe namespace them? +/* Register given function as a class autoloader */ +ZEND_FUNCTION(autoload_register_class) +{ + bool prepend = false; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(prepend) + ZEND_PARSE_PARAMETERS_END(); + + zend_register_class_autoloader(&fci, &fcc, prepend); +} + +ZEND_FUNCTION(autoload_unregister_class) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + if (!autoloader_class_autoload_functions) { + RETURN_FALSE; + } + ZEND_ASSERT(autoloader_class_autoload_functions); + + /* Why the fuck does this flush the autoloading table? This makes close to no sense. + * This is forward-ported from SPL */ + if (ZEND_FCC_INITIALIZED(fcc) && fcc.function_handler->type == ZEND_INTERNAL_FUNCTION && + fcc.function_handler->internal_function.handler == zif_autoload_call_class) { + // Don't destroy the hash table, as we might be iterating over it right now. + zend_hash_clean(autoloader_class_autoload_functions); + RETURN_TRUE; + } + + if (!ZEND_FCC_INITIALIZED(fcc)) { + /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal + * with it ourselves. It is important that it is not refetched on every call, + * because calls may occur from different scopes. */ + zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); + } + Bucket *p = autoload_find_registered_function(autoloader_class_autoload_functions, &fcc); + /* Release trampoline */ + zend_release_fcall_info_cache(&fcc); + + if (p) { + zend_hash_del_bucket(autoloader_class_autoload_functions, p); + RETURN_TRUE; + } + + RETURN_FALSE; +} + +/* Try all registered class autoloader functions to load the requested class */ +ZEND_FUNCTION(autoload_call_class) +{ + zend_string *class_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &class_name) == FAILURE) { + RETURN_THROWS(); + } + + zend_string *lc_name = zend_string_tolower(class_name); + zend_perform_class_autoload(class_name, lc_name); + zend_string_release(lc_name); +} + +/* Return all registered class autoloader functions */ +ZEND_FUNCTION(autoload_list_class) +{ + zend_fcall_info_cache *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + if (!autoloader_class_autoload_functions) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(autoloader_class_autoload_functions, func_info) { + zval tmp; + zend_get_callable_zval_from_fcc(func_info, &tmp); + add_next_index_zval(return_value, &tmp); + } ZEND_HASH_FOREACH_END(); +} + +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..ccc7112ac943f --- /dev/null +++ b/Zend/zend_autoload.h @@ -0,0 +1,32 @@ +/* + +----------------------------------------------------------------------+ + | 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: George Peter Banyard | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_API.h" +#include "zend_hash.h" + +ZEND_FUNCTION(autoload_register_class); +ZEND_FUNCTION(autoload_unregister_class); +ZEND_FUNCTION(autoload_call_class); +ZEND_FUNCTION(autoload_list_class); + +ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name); +ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_name, zend_string *lc_name); +ZEND_API void zend_autoload_callback_zval_destroy(zval *entry); +ZEND_API void zend_register_class_autoloader(zend_fcall_info *fci, zend_fcall_info_cache *fcc, bool prepend); +void zend_autoload_shutdown(void); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 245552d094609..bdffd8de718be 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -29,12 +29,15 @@ #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_class = zend_perform_class_autoload; + zend_register_default_classes(); zend_standard_class_def = register_class_stdClass(); diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index f7009c4ffba6e..7e0135c9ab8ce 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -210,3 +210,11 @@ function gc_disable(): void {} * @refcount 1 */ function gc_status(): array {} + +function autoload_register_class(callable $callback, bool $prepend = false): void {} + +function autoload_unregister_class(callable $callback): bool {} + +function autoload_call_class(string $class): void {} + +function autoload_list_class(): iterable {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index b6b9b8753a450..ec74a8cc067aa 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: a7316a341ec0a6ebc2646d42373feb3d69ae97f5 */ 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") @@ -223,6 +223,22 @@ ZEND_END_ARG_INFO() #define arginfo_gc_status arginfo_func_get_args +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_register_class, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, prepend, _IS_BOOL, 0, "false") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_unregister_class, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_call_class, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_autoload_list_class, 0, 0, Traversable, MAY_BE_ARRAY) +ZEND_END_ARG_INFO() + ZEND_FRAMELESS_FUNCTION(property_exists, 2); static const zend_frameless_function_info frameless_function_infos_property_exists[] = { @@ -297,6 +313,10 @@ ZEND_FUNCTION(gc_enabled); ZEND_FUNCTION(gc_enable); ZEND_FUNCTION(gc_disable); ZEND_FUNCTION(gc_status); +ZEND_FUNCTION(autoload_register_class); +ZEND_FUNCTION(autoload_unregister_class); +ZEND_FUNCTION(autoload_call_class); +ZEND_FUNCTION(autoload_list_class); static const zend_function_entry ext_functions[] = { ZEND_FE(exit, arginfo_exit) @@ -361,6 +381,10 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(gc_enable, arginfo_gc_enable) ZEND_FE(gc_disable, arginfo_gc_disable) ZEND_FE(gc_status, arginfo_gc_status) + ZEND_FE(autoload_register_class, arginfo_autoload_register_class) + ZEND_FE(autoload_unregister_class, arginfo_autoload_unregister_class) + ZEND_FE(autoload_call_class, arginfo_autoload_call_class) + ZEND_FE(autoload_list_class, arginfo_autoload_list_class) ZEND_FE_END }; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index eebd800d8f9d5..007e110c815eb 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -34,7 +34,8 @@ ZEND_API extern void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); /* 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 zend_class_entry *(*zend_autoload_class)(zend_string *name, zend_string *lc_name); +ZEND_API extern zend_function *(*zend_autoload_function)(zend_string *name, zend_string *lc_name); void init_executor(void); void shutdown_executor(void); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 212620b6ed4be..53a85f73c960c 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 @@ -51,7 +52,8 @@ 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 zend_class_entry *(*zend_autoload_class)(zend_string *name, zend_string *lc_name); +ZEND_API zend_function *(*zend_autoload_function)(zend_string *name, zend_string *lc_name); /* true globals */ ZEND_API const zend_fcall_info empty_fcall_info = {0}; @@ -438,6 +440,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(); @@ -1147,7 +1151,6 @@ ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string * zend_function *fbc = NULL; zval *func; zend_string *lc_name; - zend_string *autoload_name; if (lc_key) { lc_name = lc_key; @@ -1262,7 +1265,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * return NULL; } - if (!zend_autoload) { + if (!zend_autoload_class) { if (!key) { zend_string_release_ex(lc_name, 0); } @@ -1298,7 +1301,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * EG(filename_override) = NULL; EG(lineno_override) = -1; zend_exception_save(); - ce = zend_autoload(autoload_name, lc_name); + ce = zend_autoload_class(autoload_name, lc_name); zend_exception_restore(); EG(filename_override) = previous_filename; EG(lineno_override) = previous_lineno; diff --git a/configure.ac b/configure.ac index 0abaf4a4f185b..1de3668783a66 100644 --- a/configure.ac +++ b/configure.ac @@ -1711,6 +1711,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 78315e9880b4f..3da18478f0062 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -34,6 +34,7 @@ #include "spl_heap.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_autoload.h" #include "main/snprintf.h" ZEND_TLS zend_string *spl_autoload_extensions; @@ -351,159 +352,13 @@ PHP_FUNCTION(spl_autoload_extensions) } } /* }}} */ -typedef struct { - zend_function *func_ptr; - zend_object *obj; - zend_object *closure; - zend_class_entry *ce; -} autoload_func_info; - -static void autoload_func_info_destroy(autoload_func_info *alfi) { - if (alfi->obj) { - zend_object_release(alfi->obj); - } - if (alfi->func_ptr && - UNEXPECTED(alfi->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_string_release_ex(alfi->func_ptr->common.function_name, 0); - zend_free_trampoline(alfi->func_ptr); - } - if (alfi->closure) { - zend_object_release(alfi->closure); - } - efree(alfi); -} - -static void autoload_func_info_zval_dtor(zval *element) -{ - autoload_func_info_destroy(Z_PTR_P(element)); -} - -static autoload_func_info *autoload_func_info_from_fci( - zend_fcall_info *fci, zend_fcall_info_cache *fcc) { - autoload_func_info *alfi = emalloc(sizeof(autoload_func_info)); - alfi->ce = fcc->calling_scope; - alfi->func_ptr = fcc->function_handler; - alfi->obj = fcc->object; - if (alfi->obj) { - GC_ADDREF(alfi->obj); - } - if (Z_TYPE(fci->function_name) == IS_OBJECT) { - alfi->closure = Z_OBJ(fci->function_name); - GC_ADDREF(alfi->closure); - } else { - alfi->closure = NULL; - } - return alfi; -} - -static bool autoload_func_info_equals( - const autoload_func_info *alfi1, const autoload_func_info *alfi2) { - if (UNEXPECTED( - (alfi1->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) && - (alfi2->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) - )) { - return alfi1->obj == alfi2->obj - && alfi1->ce == alfi2->ce - && alfi1->closure == alfi2->closure - && zend_string_equals(alfi1->func_ptr->common.function_name, alfi2->func_ptr->common.function_name) - ; - } - return alfi1->func_ptr == alfi2->func_ptr - && alfi1->obj == alfi2->obj - && alfi1->ce == alfi2->ce - && alfi1->closure == alfi2->closure; -} - -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); - while (1) { - autoload_func_info *alfi = - zend_hash_get_current_data_ptr_ex(spl_autoload_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 param; - ZVAL_STR(¶m, class_name); - zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, ¶m, NULL); - if (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); - } else { - zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); - if (ce) { - 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) -{ - zend_string *class_name; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &class_name) == FAILURE) { - RETURN_THROWS(); - } - - zend_string *lc_name = zend_string_tolower(class_name); - spl_perform_autoload(class_name, lc_name); - zend_string_release(lc_name); -} /* }}} */ - -#define HT_MOVE_TAIL_TO_HEAD(ht) \ - ZEND_ASSERT(!HT_IS_PACKED(ht)); \ - do { \ - Bucket tmp = (ht)->arData[(ht)->nNumUsed-1]; \ - memmove((ht)->arData + 1, (ht)->arData, \ - sizeof(Bucket) * ((ht)->nNumUsed - 1)); \ - (ht)->arData[0] = tmp; \ - zend_hash_rehash(ht); \ - } while (0) - -static Bucket *spl_find_registered_function(autoload_func_info *find_alfi) { - if (!spl_autoload_functions) { - return NULL; - } - - autoload_func_info *alfi; - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, alfi) { - if (autoload_func_info_equals(alfi, find_alfi)) { - return _p; - } - } ZEND_HASH_FOREACH_END(); - return NULL; -} - /* {{{ Register given function as autoloader */ PHP_FUNCTION(spl_autoload_register) { bool do_throw = 1; bool prepend = 0; zend_fcall_info fci = {0}; - zend_fcall_info_cache fcc; - autoload_func_info *alfi; + zend_fcall_info_cache fcc = {0}; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL @@ -517,130 +372,19 @@ 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); - /* 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 (!fcc.function_handler) { - /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal - * with it outselves. It is important that it is not refetched on every call, - * because calls may occur from different scopes. */ - zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL); - } - - if (fcc.function_handler->type == ZEND_INTERNAL_FUNCTION && - fcc.function_handler->internal_function.handler == zif_spl_autoload_call) { - zend_argument_value_error(1, "must not be the spl_autoload_call() function"); - RETURN_THROWS(); - } - - alfi = autoload_func_info_from_fci(&fci, &fcc); - if (UNEXPECTED(alfi->func_ptr == &EG(trampoline))) { - zend_function *copy = emalloc(sizeof(zend_op_array)); - - memcpy(copy, alfi->func_ptr, sizeof(zend_op_array)); - alfi->func_ptr->common.function_name = NULL; - alfi->func_ptr = copy; - } + zend_register_class_autoloader(&fci, &fcc, prepend); } else { - alfi = emalloc(sizeof(autoload_func_info)); - alfi->func_ptr = zend_hash_str_find_ptr( - CG(function_table), "spl_autoload", sizeof("spl_autoload") - 1); - alfi->obj = NULL; - alfi->ce = NULL; - alfi->closure = NULL; - } - - if (spl_find_registered_function(alfi)) { - 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); + /* Pass the zend_function * of the internal pointer directly */ + fcc.function_handler = zend_hash_str_find_ptr(CG(function_table), "spl_autoload", strlen("spl_autoload")); + zend_register_class_autoloader(NULL, &fcc, prepend); } + /* Return true to maintain BC */ RETURN_TRUE; } /* }}} */ -/* {{{ Unregister given function as autoloader */ -PHP_FUNCTION(spl_autoload_unregister) -{ - zend_fcall_info fci; - zend_fcall_info_cache fcc; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_FUNC(fci, fcc) - 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) { - /* Don't destroy the hash table, as we might be iterating over it right now. */ - zend_hash_clean(spl_autoload_functions); - } - RETURN_TRUE; - } - - if (!fcc.function_handler) { - /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal - * with it outselves. It is important that it is not refetched on every call, - * because calls may occur from different scopes. */ - zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); - } - - 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; - } - - RETURN_FALSE; -} /* }}} */ - -/* {{{ Return all registered autoloader functions */ -PHP_FUNCTION(spl_autoload_functions) -{ - autoload_func_info *alfi; - - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } - - array_init(return_value); - if (spl_autoload_functions) { - ZEND_HASH_MAP_FOREACH_PTR(spl_autoload_functions, 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 */ PHP_FUNCTION(spl_object_hash) { @@ -718,8 +462,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); diff --git a/ext/spl/php_spl.stub.php b/ext/spl/php_spl.stub.php index d3b5d44f11d1e..c1a71ce46f999 100644 --- a/ext/spl/php_spl.stub.php +++ b/ext/spl/php_spl.stub.php @@ -25,14 +25,17 @@ function class_uses($object_or_class, bool $autoload = true): array|false {} function spl_autoload(string $class, ?string $file_extensions = null): void {} +/** @alias autoload_call_class */ function spl_autoload_call(string $class): void {} function spl_autoload_extensions(?string $file_extensions = null): string {} +/** @alias autoload_list_class */ function spl_autoload_functions(): array {} function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool {} +/** @alias autoload_unregister_class */ function spl_autoload_unregister(callable $callback): bool {} /** diff --git a/ext/spl/php_spl_arginfo.h b/ext/spl/php_spl_arginfo.h index 68c71fc524bc5..f49a3e2a763d4 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: 92f8ab4833e20330385f597cc62349d7ee23d1ca */ 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) @@ -65,11 +65,11 @@ ZEND_FUNCTION(class_implements); ZEND_FUNCTION(class_parents); ZEND_FUNCTION(class_uses); ZEND_FUNCTION(spl_autoload); -ZEND_FUNCTION(spl_autoload_call); +ZEND_FUNCTION(autoload_call_class); ZEND_FUNCTION(spl_autoload_extensions); -ZEND_FUNCTION(spl_autoload_functions); +ZEND_FUNCTION(autoload_list_class); ZEND_FUNCTION(spl_autoload_register); -ZEND_FUNCTION(spl_autoload_unregister); +ZEND_FUNCTION(autoload_unregister_class); ZEND_FUNCTION(spl_classes); ZEND_FUNCTION(spl_object_hash); ZEND_FUNCTION(spl_object_id); @@ -82,11 +82,11 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(class_parents, arginfo_class_parents) ZEND_FE(class_uses, arginfo_class_uses) ZEND_FE(spl_autoload, arginfo_spl_autoload) - ZEND_FE(spl_autoload_call, arginfo_spl_autoload_call) + ZEND_RAW_FENTRY("spl_autoload_call", zif_autoload_call_class, arginfo_spl_autoload_call, 0, NULL, NULL) ZEND_FE(spl_autoload_extensions, arginfo_spl_autoload_extensions) - ZEND_FE(spl_autoload_functions, arginfo_spl_autoload_functions) + ZEND_RAW_FENTRY("spl_autoload_functions", zif_autoload_list_class, arginfo_spl_autoload_functions, 0, NULL, NULL) ZEND_FE(spl_autoload_register, arginfo_spl_autoload_register) - ZEND_FE(spl_autoload_unregister, arginfo_spl_autoload_unregister) + ZEND_RAW_FENTRY("spl_autoload_unregister", zif_autoload_unregister_class, arginfo_spl_autoload_unregister, 0, NULL, NULL) ZEND_FE(spl_classes, arginfo_spl_classes) ZEND_FE(spl_object_hash, arginfo_spl_object_hash) ZEND_FE(spl_object_id, arginfo_spl_object_id) diff --git a/ext/spl/tests/bug40091.phpt b/ext/spl/tests/bug40091.phpt deleted file mode 100644 index 3bb1bbf15238b..0000000000000 --- a/ext/spl/tests/bug40091.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Bug #40091 (issue with spl_autoload_register() and 2 instances of the same class) ---FILE-- - ---EXPECT-- -Array -( - [0] => Array - ( - [0] => MyAutoloader Object - ( - ) - - [1] => autoload - ) - - [1] => Array - ( - [0] => MyAutoloader Object - ( - ) - - [1] => autoload - ) - -) diff --git a/ext/spl/tests/bug44144.phpt b/ext/spl/tests/bug44144.phpt deleted file mode 100644 index 7dbcf1e636719..0000000000000 --- a/ext/spl/tests/bug44144.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Bug #44144 (spl_autoload_functions() should return object instance when appropriate) ---FILE-- - ---EXPECTF-- -array(1) { - [0]=> - array(2) { - [0]=> - object(Foo)#%d (0) { - } - [1]=> - string(15) "nonstaticMethod" - } -} diff --git a/ext/spl/tests/bug48023.phpt b/ext/spl/tests/bug48023.phpt deleted file mode 100644 index e42e2f4f03cde..0000000000000 --- a/ext/spl/tests/bug48023.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Bug #48023 (spl_autoload_register didn't addref closures) ---FILE-- - -===DONE=== ---EXPECTF-- -Fatal error: Uncaught Error: Class "Foo" not found in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d diff --git a/ext/spl/tests/bug48493.phpt b/ext/spl/tests/bug48493.phpt deleted file mode 100644 index d0be7f8ec71f0..0000000000000 --- a/ext/spl/tests/bug48493.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -SPL: Bug #48493 spl_autoload_unregister() can't handle prepended functions ---FILE-- - ---EXPECT-- -array(2) { - [0]=> - string(9) "autoload1" - [1]=> - string(9) "autoload2" -} -array(1) { - [0]=> - string(9) "autoload1" -} diff --git a/ext/spl/tests/bug61697.phpt b/ext/spl/tests/bug61697.phpt deleted file mode 100644 index 458bba66638b2..0000000000000 --- a/ext/spl/tests/bug61697.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -Bug #61697 (spl_autoload_functions returns lambda functions incorrectly) ---FILE-- - ---EXPECT-- -Array -( -) diff --git a/ext/spl/tests/bug71204.phpt b/ext/spl/tests/bug71204.phpt deleted file mode 100644 index 8d1c721c100b7..0000000000000 --- a/ext/spl/tests/bug71204.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -Bug #71204 (segfault if clean spl_autoload_funcs while autoloading ) ---FILE-- - ---EXPECTF-- -Fatal error: Uncaught Error: Class "A" not found in %s:%d -Stack trace: -#0 {main} - thrown in %sbug71204.php on line %d diff --git a/ext/spl/tests/bug75049.phpt b/ext/spl/tests/bug75049.phpt deleted file mode 100644 index 85e3ef12ca7a6..0000000000000 --- a/ext/spl/tests/bug75049.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Bug #75049 (spl_autoload_unregister can't handle spl_autoload_functions results) ---FILE-- - ---EXPECT-- -11110 diff --git a/ext/spl/tests/spl_autoload_004.phpt b/ext/spl/tests/spl_autoload_004.phpt deleted file mode 100644 index 8e1eafc37a5d9..0000000000000 --- a/ext/spl/tests/spl_autoload_004.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -SPL: spl_autoload() with static methods ---INI-- -include_path=. ---FILE-- - ---EXPECT-- -array(1) { - [0]=> - array(2) { - [0]=> - string(12) "MyAutoLoader" - [1]=> - string(8) "autoLoad" - } -} -MyAutoLoader::autoLoad(TestClass) -bool(false) diff --git a/ext/spl/tests/spl_autoload_005.phpt b/ext/spl/tests/spl_autoload_005.phpt deleted file mode 100644 index 0f2c5ed2c3024..0000000000000 --- a/ext/spl/tests/spl_autoload_005.phpt +++ /dev/null @@ -1,49 +0,0 @@ ---TEST-- -SPL: spl_autoload() with methods ---INI-- -include_path=. ---FILE-- -getMessage() . \PHP_EOL; -} - -// and - -$myAutoLoader = new MyAutoLoader(); - -spl_autoload_register(array($myAutoLoader, 'autoLoad')); -spl_autoload_register(array($myAutoLoader, 'autoThrow')); - -try -{ - var_dump(class_exists("TestClass", true)); -} -catch(Exception $e) -{ - echo 'Exception: ' . $e->getMessage() . "\n"; -} - -?> ---EXPECT-- -spl_autoload_register(): Argument #1 ($callback) must be a valid callback or null, non-static method MyAutoLoader::autoLoad() cannot be called statically -MyAutoLoader::autoLoad(TestClass) -MyAutoLoader::autoThrow(TestClass) -Exception: Unavailable diff --git a/ext/spl/tests/spl_autoload_009.phpt b/ext/spl/tests/spl_autoload_009.phpt deleted file mode 100644 index f12fd0af7e709..0000000000000 --- a/ext/spl/tests/spl_autoload_009.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -SPL: spl_autoload() and friends ---INI-- -include_path=. ---FILE-- - ---EXPECTF-- -%stestclass.inc -%stestclass.class.inc -bool(true) diff --git a/ext/spl/tests/spl_autoload_010.phpt b/ext/spl/tests/spl_autoload_010.phpt deleted file mode 100644 index 3b0754d8dfa97..0000000000000 --- a/ext/spl/tests/spl_autoload_010.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -SPL: spl_autoload() and prepend ---INI-- -include_path=. ---FILE-- - $name\n"; -} -function autoloadB($name) { - echo "B -> $name\n"; -} -function autoloadC($name) { - echo "C -> $name\n"; - class C{} -} - -spl_autoload_register('autoloadA'); -spl_autoload_register('autoloadB', true, true); -spl_autoload_register('autoloadC'); - -new C; -?> ---EXPECT-- -B -> C -A -> C -C -> C diff --git a/ext/spl/tests/spl_autoload_013.phpt b/ext/spl/tests/spl_autoload_013.phpt deleted file mode 100644 index b9b6f6571f320..0000000000000 --- a/ext/spl/tests/spl_autoload_013.phpt +++ /dev/null @@ -1,55 +0,0 @@ ---TEST-- -SPL: spl_autoload_functions() with closures and invocables ---FILE-- -dir = $dir; - } - public function __invoke($class) { - var_dump("{$this->dir}/$class.php"); - } -} - -$al1 = new Autoloader('d1'); -$al2 = new Autoloader('d2'); - -spl_autoload_register($closure); -spl_autoload_register($al1); -spl_autoload_register($al2); - -var_dump(spl_autoload_functions()); - -?> ---EXPECTF-- -array(3) { - [0]=> - object(Closure)#%d (4) { - ["name"]=> - string(%d) "{closure:%s:%d}" - ["file"]=> - string(%d) "%s" - ["line"]=> - int(%d) - ["parameter"]=> - array(1) { - ["$class"]=> - string(10) "" - } - } - [1]=> - object(Autoloader)#%d (1) { - ["dir":"Autoloader":private]=> - string(2) "d1" - } - [2]=> - object(Autoloader)#%d (1) { - ["dir":"Autoloader":private]=> - string(2) "d2" - } -} diff --git a/ext/spl/tests/spl_autoload_014.phpt b/ext/spl/tests/spl_autoload_014.phpt deleted file mode 100644 index 3c7cc38f63e37..0000000000000 --- a/ext/spl/tests/spl_autoload_014.phpt +++ /dev/null @@ -1,45 +0,0 @@ ---TEST-- -SPL: spl_autoload_unregister() with closures and invocables ---FILE-- -dir = $dir; - } - public function __invoke($class) { - echo ("Autoloader('{$this->dir}') called with $class\n"); - } -} - -class WorkingAutoloader { - public function __invoke($class) { - echo ("WorkingAutoloader() called with $class\n"); - eval("class $class { }"); - } -} - -$al1 = new Autoloader('d1'); -$al2 = new WorkingAutoloader('d2'); - -spl_autoload_register($closure); -spl_autoload_register($al1); -spl_autoload_register($al2); - -$x = new TestX; - -spl_autoload_unregister($closure); -spl_autoload_unregister($al1); - -$y = new TestY; - -?> ---EXPECT-- -closure called with class TestX -Autoloader('d1') called with TestX -WorkingAutoloader() called with TestX -WorkingAutoloader() called with TestY diff --git a/ext/spl/tests/spl_autoload_bug48541.phpt b/ext/spl/tests/spl_autoload_bug48541.phpt deleted file mode 100644 index acdf36aa71d10..0000000000000 --- a/ext/spl/tests/spl_autoload_bug48541.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -SPL: spl_autoload_register() Bug #48541: registering multiple closures fails with memleaks ---FILE-- -getClosure(); -$b = function ($class) { - eval('class ' . $class . '{function __construct(){echo "foo\n";}}'); - echo "b called\n"; -}; -spl_autoload_register($a); -spl_autoload_register($a2); -spl_autoload_register($b); - -$c = $a; -$c2 = $a2; -spl_autoload_register($c); -spl_autoload_register($c2); -$c = new foo; -?> ---EXPECT-- -a called -a2 called -b called -foo diff --git a/ext/spl/tests/spl_autoload_call_basic.phpt b/ext/spl/tests/spl_autoload_call_basic.phpt deleted file mode 100644 index 2bd65c22be4ba..0000000000000 --- a/ext/spl/tests/spl_autoload_call_basic.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -spl_autoload_call() function - basic test for spl_autoload_call() ---CREDITS-- -Jean-Marc Fontaine -# Alter Way Contribution Day 2011 ---FILE-- - ---EXPECTF-- -%stestclass.class.inc -bool(true) diff --git a/ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt b/ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt index 943d80ae25377..a0e2a9b95e57e 100644 --- a/ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt +++ b/ext/spl/tests/spl_autoload_throw_with_spl_autoloader_call_as_autoloader.phpt @@ -11,4 +11,4 @@ try { ?> --EXPECT-- -spl_autoload_register(): Argument #1 ($callback) must not be the spl_autoload_call() function +spl_autoload_register(): Argument #1 ($callback) must not be the autoload_call_class() function diff --git a/ext/spl/tests/spl_autoload_unregister_without_registrations.phpt b/ext/spl/tests/spl_autoload_unregister_without_registrations.phpt index 0a7ca5a1352fd..3a58b8fb7e032 100644 --- a/ext/spl/tests/spl_autoload_unregister_without_registrations.phpt +++ b/ext/spl/tests/spl_autoload_unregister_without_registrations.phpt @@ -6,5 +6,5 @@ var_dump(spl_autoload_unregister("spl_autoload_call")); ?> Done --EXPECT-- -bool(true) +bool(false) Done diff --git a/win32/build/config.w32 b/win32/build/config.w32 index c4a617a2e16ec..bdf336ae9e099 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +240,8 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.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_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c \ + zend_property_hooks.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({ From 87aec86287df4ca1f43c50029132ca91a88f42a7 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:21:50 +0200 Subject: [PATCH 3/9] Implementation of function autoloading --- .../function/autoload_call_basic.phpt | 14 ++ .../function/autoload_call_invalid_name.phpt | 36 ++++ .../function/autoload_called_scope.phpt | 33 ++++ .../autoload_invalid_name_variable.phpt | 41 +++++ ...toloader_with_closures_and_invocables.phpt | 68 ++++++++ .../autoloading/function/destructor_call.phpt | 28 +++ .../emit-parse-error-in-autoloader.phpt | 17 ++ .../exceptions_during_autoloading001.phpt | 33 ++++ .../exceptions_during_autoloading002.phpt | 127 ++++++++++++++ .../exceptions_during_autoloading003.phpt | 50 ++++++ .../exceptions_during_autoloading004.phpt | 67 ++++++++ ...unpinned_local_function_returns_false.phpt | 31 ++++ .../function/global_fallback001.phpt | 17 ++ .../function/global_fallback002.phpt | 25 +++ .../function/global_fallback003.phpt | 27 +++ ...al_fallback_doesnt_repeat_autoloading.phpt | 42 +++++ ...llback_doesnt_repeat_autoloading_file1.inc | 12 ++ ...llback_doesnt_repeat_autoloading_file2.inc | 9 + ..._autoloading_seperate_files_inclusion.phpt | 30 ++++ .../function/innacessible_methods.phpt | 132 ++++++++++++++ .../local_function_pinned-to_global.phpt | 25 +++ Zend/tests/autoloading/function/methods.phpt | 43 +++++ .../missing_function_in_namespace.phpt | 14 ++ .../autoloading/function/prepending.phpt | 34 ++++ .../function/register_autoloader.phpt | 39 +++++ .../same_class_different_instances.phpt | 47 +++++ .../autoloading/function/static_methods.phpt | 31 ++++ ...hrow_with_autoload_call_as_autoloader.phpt | 14 ++ .../function/trampoline_autoloader.phpt | 56 ++++++ ...oline_unregister_autoloader_from_list.phpt | 46 +++++ .../un-register_take_effect_immediately.phpt | 30 ++++ .../function/unregister_autoloader.phpt | 33 ++++ .../unregister_autoloader_from_list.phpt | 22 +++ Zend/zend_API.c | 23 ++- Zend/zend_API.h | 2 + Zend/zend_autoload.c | 161 +++++++++++++++++- Zend/zend_autoload.h | 4 + Zend/zend_builtin_functions.c | 27 ++- Zend/zend_builtin_functions.stub.php | 10 +- Zend/zend_builtin_functions_arginfo.h | 21 ++- Zend/zend_execute.c | 6 +- Zend/zend_execute.h | 8 +- Zend/zend_execute_API.c | 83 ++++++--- Zend/zend_vm_def.h | 33 +++- Zend/zend_vm_execute.h | 33 +++- 45 files changed, 1624 insertions(+), 60 deletions(-) create mode 100644 Zend/tests/autoloading/function/autoload_call_basic.phpt create mode 100644 Zend/tests/autoloading/function/autoload_call_invalid_name.phpt create mode 100644 Zend/tests/autoloading/function/autoload_called_scope.phpt create mode 100644 Zend/tests/autoloading/function/autoload_invalid_name_variable.phpt create mode 100644 Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt create mode 100644 Zend/tests/autoloading/function/destructor_call.phpt create mode 100644 Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt create mode 100644 Zend/tests/autoloading/function/exceptions_during_autoloading001.phpt create mode 100644 Zend/tests/autoloading/function/exceptions_during_autoloading002.phpt create mode 100644 Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt create mode 100644 Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt create mode 100644 Zend/tests/autoloading/function/function_exists_unpinned_local_function_returns_false.phpt create mode 100644 Zend/tests/autoloading/function/global_fallback001.phpt create mode 100644 Zend/tests/autoloading/function/global_fallback002.phpt create mode 100644 Zend/tests/autoloading/function/global_fallback003.phpt create mode 100644 Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading.phpt create mode 100644 Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file1.inc create mode 100644 Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file2.inc create mode 100644 Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_seperate_files_inclusion.phpt create mode 100644 Zend/tests/autoloading/function/innacessible_methods.phpt create mode 100644 Zend/tests/autoloading/function/local_function_pinned-to_global.phpt create mode 100644 Zend/tests/autoloading/function/methods.phpt create mode 100644 Zend/tests/autoloading/function/missing_function_in_namespace.phpt create mode 100644 Zend/tests/autoloading/function/prepending.phpt create mode 100644 Zend/tests/autoloading/function/register_autoloader.phpt create mode 100644 Zend/tests/autoloading/function/same_class_different_instances.phpt create mode 100644 Zend/tests/autoloading/function/static_methods.phpt create mode 100644 Zend/tests/autoloading/function/throw_with_autoload_call_as_autoloader.phpt create mode 100644 Zend/tests/autoloading/function/trampoline_autoloader.phpt create mode 100644 Zend/tests/autoloading/function/trampoline_unregister_autoloader_from_list.phpt create mode 100644 Zend/tests/autoloading/function/un-register_take_effect_immediately.phpt create mode 100644 Zend/tests/autoloading/function/unregister_autoloader.phpt create mode 100644 Zend/tests/autoloading/function/unregister_autoloader_from_list.phpt diff --git a/Zend/tests/autoloading/function/autoload_call_basic.phpt b/Zend/tests/autoloading/function/autoload_call_basic.phpt new file mode 100644 index 0000000000000..4df9042cd7c7e --- /dev/null +++ b/Zend/tests/autoloading/function/autoload_call_basic.phpt @@ -0,0 +1,14 @@ +--TEST-- +Basic autoload_call_function() function +--FILE-- + +--EXPECT-- +bool(true) diff --git a/Zend/tests/autoloading/function/autoload_call_invalid_name.phpt b/Zend/tests/autoloading/function/autoload_call_invalid_name.phpt new file mode 100644 index 0000000000000..2393167d50b9d --- /dev/null +++ b/Zend/tests/autoloading/function/autoload_call_invalid_name.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test autoload_call_function() with invalid symbol name +--FILE-- +getMessage(); +} +try { + autoload_call_function('"'); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +try { + autoload_call_function(''); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +try { + autoload_call_function("al\no"); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(); +} +?> +--EXPECT-- +string(6) "12ayhs" +string(1) """ +string(0) "" +string(4) "al +o" diff --git a/Zend/tests/autoloading/function/autoload_called_scope.phpt b/Zend/tests/autoloading/function/autoload_called_scope.phpt new file mode 100644 index 0000000000000..68fa6682471c0 --- /dev/null +++ b/Zend/tests/autoloading/function/autoload_called_scope.phpt @@ -0,0 +1,33 @@ +--TEST-- +Autoloader should not do anything magic with called scope +--FILE-- + +--EXPECT-- +self=Test, static=Test +self=Test, static=Test2 diff --git a/Zend/tests/autoloading/function/autoload_invalid_name_variable.phpt b/Zend/tests/autoloading/function/autoload_invalid_name_variable.phpt new file mode 100644 index 0000000000000..0cc6242968fc4 --- /dev/null +++ b/Zend/tests/autoloading/function/autoload_invalid_name_variable.phpt @@ -0,0 +1,41 @@ +--TEST-- +Dynamic autoload with invalid symbol name in variable +--FILE-- +getMessage(), \PHP_EOL; +} +$name = '"'; +try { + $name(); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +$name = ''; +try { + $name(); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +$name = "al\no"; +try { + $name(); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; +} +?> +--EXPECT-- +string(6) "12ayhs" +Error: Call to undefined function 12ayhs() +Error: Call to undefined function "() +Error: Call to undefined function () +Error: Call to undefined function al +o() diff --git a/Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt b/Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt new file mode 100644 index 0000000000000..ec1c774706d70 --- /dev/null +++ b/Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt @@ -0,0 +1,68 @@ +--TEST-- +Autoloading with closures and invocables +--FILE-- +dir}') called with $func\n"); + } +} + +class WorkingAutoloader { + public function __invoke($func) { + echo ("WorkingAutoloader() called with $func\n"); + eval("function $func() { }"); + } +} + +$al1 = new Autoloader('d1'); +$al2 = new WorkingAutoloader('d2'); + +autoload_register_function($closure); +autoload_register_function($al1); +autoload_register_function($al2); + +var_dump(autoload_list_function()); + +$foo = foo(); + +autoload_unregister_function($closure); +autoload_unregister_function($al1); + +$bar = bar(); + +?> +--EXPECTF-- +array(3) { + [0]=> + object(Closure)#1 (4) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["parameter"]=> + array(1) { + ["$name"]=> + string(10) "" + } + } + [1]=> + object(Autoloader)#2 (1) { + ["dir":"Autoloader":private]=> + string(2) "d1" + } + [2]=> + object(WorkingAutoloader)#3 (0) { + } +} +autoload(foo) +Autoloader('d1') called with foo +WorkingAutoloader() called with foo +WorkingAutoloader() called with bar diff --git a/Zend/tests/autoloading/function/destructor_call.phpt b/Zend/tests/autoloading/function/destructor_call.phpt new file mode 100644 index 0000000000000..4bbb90909a6d7 --- /dev/null +++ b/Zend/tests/autoloading/function/destructor_call.phpt @@ -0,0 +1,28 @@ +--TEST-- +Destructor call of autoloader when object freed +--FILE-- +var."\n"; + } + public function __destruct() { + echo "__destruct__\n"; + } +} + +$a = new A; +$a->var = 2; + +autoload_register_function(array($a, 'autoload')); +unset($a); + +var_dump(function_exists("C", true)); +?> +===DONE=== +--EXPECT-- +var:2 +bool(false) +===DONE=== +__destruct__ diff --git a/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt b/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt new file mode 100644 index 0000000000000..5d67d8d134de9 --- /dev/null +++ b/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt @@ -0,0 +1,17 @@ +--TEST-- +Parse errors should be thrown if occuring from an autoloader +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected identifier "ha" in %s on line %d diff --git a/Zend/tests/autoloading/function/exceptions_during_autoloading001.phpt b/Zend/tests/autoloading/function/exceptions_during_autoloading001.phpt new file mode 100644 index 0000000000000..0976006af549f --- /dev/null +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading001.phpt @@ -0,0 +1,33 @@ +--TEST-- +Exception thrown from within autoloading function +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +TestFunc1(foo) +TestFunc2(foo) +Exception: Function foo missing diff --git a/Zend/tests/autoloading/function/exceptions_during_autoloading002.phpt b/Zend/tests/autoloading/function/exceptions_during_autoloading002.phpt new file mode 100644 index 0000000000000..aa2b6407edd99 --- /dev/null +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading002.phpt @@ -0,0 +1,127 @@ +--TEST-- +Exceptions during autoloading +--FILE-- + $func) +{ + echo "====$idx====\n"; + + var_dump($func); + try { + autoload_register_function($func); + } catch (TypeError $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; + var_dump(count(autoload_list_function())); + continue; + } + + if (count(autoload_list_function())) { + echo "registered\n"; + + try { + var_dump(function_exists("foo", true)); + } catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), \PHP_EOL; + } + } + + autoload_unregister_function($func); + var_dump(count(autoload_list_function())); +} + +?> +--EXPECTF-- +====0==== +string(10) "MyAutoLoad" +registered +MyAutoLoad(foo) +Exception: Bla +int(0) +====1==== +string(22) "MyAutoLoader::autoLoad" +registered +MyAutoLoader::autoLoad(foo) +Exception: Bla +int(0) +====2==== +string(22) "MyAutoLoader::dynaLoad" +TypeError: autoload_register_function(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically +int(0) +====3==== +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "autoLoad" +} +registered +MyAutoLoader::autoLoad(foo) +Exception: Bla +int(0) +====4==== +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "dynaLoad" +} +TypeError: autoload_register_function(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically +int(0) +====5==== +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "autoLoad" +} +registered +MyAutoLoader::autoLoad(foo) +Exception: Bla +int(0) +====6==== +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "dynaLoad" +} +registered +MyAutoLoader::dynaLoad(foo) +Exception: Bla +int(0) diff --git a/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt b/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt new file mode 100644 index 0000000000000..3c4aa9a73b1a0 --- /dev/null +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt @@ -0,0 +1,50 @@ +--TEST-- +Capturing multiple Exceptions during autoloading +--FILE-- +getMessage()."\n"; + } while ($e = $e->getPrevious()); +} + +try { + foo(); +} catch (Exception $e) { + do { + echo $e->getMessage()."\n"; + } while ($e = $e->getPrevious()); +} + +function_exists('foo'); +?> +===DONE=== +--EXPECTF-- +autoload_first +first +autoload_first +first +autoload_first + +Fatal error: Uncaught Exception: first in %s:%d +Stack trace: +#0 [internal function]: autoload_first('foo') +#1 %s(%d): function_exists('foo') +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt b/Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt new file mode 100644 index 0000000000000..baa58dd83ab28 --- /dev/null +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt @@ -0,0 +1,67 @@ +--TEST-- +Capturing multiple Exceptions during autoloading in a namespace +--FILE-- +getMessage()."\n"; + } while ($e = $e->getPrevious()); + } + echo "Try-catch around unqualified function call\n"; + try { + foo(); + } catch (\Throwable $e) { + do { + echo $e::class, ': ', $e->getMessage(), "\n"; + } while ($e = $e->getPrevious()); + } + echo "Try-catch around qualified function call\n"; + try { + \foo(); + } catch (\Throwable $e) { + do { + echo $e::class, ': ', $e->getMessage(), "\n"; + } while ($e = $e->getPrevious()); + } + + echo "function_exists() without try-catch\n"; + \function_exists('foo'); +} +?> +--EXPECTF-- +Try-catch around function_exists() +autoload_first +first +Try-catch around unqualified function call +autoload_first +Exception: first +Try-catch around qualified function call +autoload_first +Exception: first +function_exists() without try-catch +autoload_first + +Fatal error: Uncaught Exception: first in %s:%d +Stack trace: +#0 [internal function]: autoload_first('foo') +#1 %s(%d): function_exists('foo') +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/autoloading/function/function_exists_unpinned_local_function_returns_false.phpt b/Zend/tests/autoloading/function/function_exists_unpinned_local_function_returns_false.phpt new file mode 100644 index 0000000000000..654df3c4a5a96 --- /dev/null +++ b/Zend/tests/autoloading/function/function_exists_unpinned_local_function_returns_false.phpt @@ -0,0 +1,31 @@ +--TEST-- +function_exist of local function returns false even with global one defined +--FILE-- + +--EXPECT-- +function loader called with foo\function_exists +function loader called with foo\bar +I'm bar in foo namespace diff --git a/Zend/tests/autoloading/function/global_fallback001.phpt b/Zend/tests/autoloading/function/global_fallback001.phpt new file mode 100644 index 0000000000000..3d27008899121 --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Check functions found on fallback to root. +--FILE-- + +--EXPECT-- +I am just foo. diff --git a/Zend/tests/autoloading/function/global_fallback002.phpt b/Zend/tests/autoloading/function/global_fallback002.phpt new file mode 100644 index 0000000000000..df4d3d96a6aa4 --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback002.phpt @@ -0,0 +1,25 @@ +--TEST-- +Fallback to global function triggers autoloading once. +--FILE-- + +--EXPECT-- +function loader called with bar\foo +I am foo in global namespace. diff --git a/Zend/tests/autoloading/function/global_fallback003.phpt b/Zend/tests/autoloading/function/global_fallback003.phpt new file mode 100644 index 0000000000000..1d93dd8499fec --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback003.phpt @@ -0,0 +1,27 @@ +--TEST-- +Fallback to non-existent function triggers autoloading once in namespace, once in global. +--FILE-- +getMessage() . "\n"; + } +} + +?> +--EXPECT-- +function loader called with bar\non_existent_function +function loader called with non_existent_function +Error correctly caught: Call to undefined function bar\non_existent_function() diff --git a/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading.phpt b/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading.phpt new file mode 100644 index 0000000000000..5d7dd91da002a --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading.phpt @@ -0,0 +1,42 @@ +--TEST-- +Fallback to global function should trigger autoloading only once per namespace. +--FILE-- + +--EXPECT-- +function loader called with bar\foo +I am foo in global namespace. +I am foo in global namespace. +I am foo in global namespace. +I am foo in global namespace. +I am foo in global namespace. +function loader called with Quux\foo +I am foo in global namespace. diff --git a/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file1.inc b/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file1.inc new file mode 100644 index 0000000000000..55b8752b57c1e --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback_doesnt_repeat_autoloading_file1.inc @@ -0,0 +1,12 @@ + +--EXPECT-- +First file START +function loader called with bar\foo +I am foo in global namespace. +I am foo in global namespace. +I am foo in global namespace. +I am foo in global namespace. +First file END +Second file START +I am foo in global namespace. +Second file END diff --git a/Zend/tests/autoloading/function/innacessible_methods.phpt b/Zend/tests/autoloading/function/innacessible_methods.phpt new file mode 100644 index 0000000000000..2edc8aeb6bbd6 --- /dev/null +++ b/Zend/tests/autoloading/function/innacessible_methods.phpt @@ -0,0 +1,132 @@ +--TEST-- +Autoloading inaccessible methods +--INI-- +include_path=. +--FILE-- + $func) +{ + if ($idx) echo "\n"; + try { + var_dump($func); + autoload_register_function($func); + echo "ok\n"; + } catch(\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; + } +} + +?> +--EXPECTF-- +string(22) "MyAutoLoader::notExist" +autoload_register_function(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" + +string(22) "MyAutoLoader::noAccess" +autoload_register_function(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() + +string(22) "MyAutoLoader::autoLoad" +ok + +string(22) "MyAutoLoader::dynaLoad" +autoload_register_function(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically + +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "notExist" +} +autoload_register_function(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" + +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "noAccess" +} +autoload_register_function(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() + +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "autoLoad" +} +ok + +array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "dynaLoad" +} +autoload_register_function(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::dynaLoad() cannot be called statically + +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "notExist" +} +autoload_register_function(): Argument #1 ($callback) must be a valid callback, class MyAutoLoader does not have a method "notExist" + +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "noAccess" +} +autoload_register_function(): Argument #1 ($callback) must be a valid callback, cannot access protected method MyAutoLoader::noAccess() + +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "autoLoad" +} +ok + +array(2) { + [0]=> + object(MyAutoLoader)#%d (0) { + } + [1]=> + string(8) "dynaLoad" +} +ok diff --git a/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt new file mode 100644 index 0000000000000..feb2dd8109e6c --- /dev/null +++ b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt @@ -0,0 +1,25 @@ +--TEST-- +Local function which falls back to global is aliased to the global one +--FILE-- + +--EXPECT-- +int(5) +\Foo\strlen() was bound to global \strlen() +int(5) diff --git a/Zend/tests/autoloading/function/methods.phpt b/Zend/tests/autoloading/function/methods.phpt new file mode 100644 index 0000000000000..16f32fb86b1fb --- /dev/null +++ b/Zend/tests/autoloading/function/methods.phpt @@ -0,0 +1,43 @@ +--TEST-- +Autoloader is a method +--FILE-- +getMessage() . \PHP_EOL; +} + +// and + +$myAutoLoader = new MyAutoLoader(); + +autoload_register_function(array($myAutoLoader, 'autoLoad')); +autoload_register_function(array($myAutoLoader, 'autoThrow')); + +try { + var_dump(function_exists("foo", true)); +} catch(Exception $e) { + echo 'Exception: ' . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +autoload_register_function(): Argument #1 ($callback) must be a valid callback, non-static method MyAutoLoader::autoLoad() cannot be called statically +MyAutoLoader::autoLoad(foo) +MyAutoLoader::autoThrow(foo) +Exception: Unavailable diff --git a/Zend/tests/autoloading/function/missing_function_in_namespace.phpt b/Zend/tests/autoloading/function/missing_function_in_namespace.phpt new file mode 100644 index 0000000000000..f7f0d960c2ef5 --- /dev/null +++ b/Zend/tests/autoloading/function/missing_function_in_namespace.phpt @@ -0,0 +1,14 @@ +--TEST-- +Missing function in namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined function zoq\quux() in %s +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/autoloading/function/prepending.phpt b/Zend/tests/autoloading/function/prepending.phpt new file mode 100644 index 0000000000000..195ad9c0c1f2e --- /dev/null +++ b/Zend/tests/autoloading/function/prepending.phpt @@ -0,0 +1,34 @@ +--TEST-- +Prepending autoloaders +--FILE-- + $name\n"; +} +function autoloadB($name) { + echo "B -> $name\n"; +} +function autoloadC($name) { + echo "C -> $name\n"; + function foo(){} +} + +autoload_register_function('autoloadA'); +autoload_register_function('autoloadB', true); +autoload_register_function('autoloadC'); +var_dump(autoload_list_function()); + +foo(); +?> +--EXPECT-- +array(3) { + [0]=> + string(9) "autoloadB" + [1]=> + string(9) "autoloadA" + [2]=> + string(9) "autoloadC" +} +B -> foo +A -> foo +C -> foo diff --git a/Zend/tests/autoloading/function/register_autoloader.phpt b/Zend/tests/autoloading/function/register_autoloader.phpt new file mode 100644 index 0000000000000..9c96c3df8e4e6 --- /dev/null +++ b/Zend/tests/autoloading/function/register_autoloader.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test autoload_register_function(): basic behaviour +--FILE-- +getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +===REGISTER=== +TestFunc1(foo) +TestFunc2(foo) +bool(false) +===NOFUNCTION=== +TestFunc1(unavailable_autoload_function) +TestFunc2(unavailable_autoload_function) +autoload_register_function(): Argument #1 ($callback) must be a valid callback, function "unavailable_autoload_function" not found or invalid function name diff --git a/Zend/tests/autoloading/function/same_class_different_instances.phpt b/Zend/tests/autoloading/function/same_class_different_instances.phpt new file mode 100644 index 0000000000000..e7eaf68871fd4 --- /dev/null +++ b/Zend/tests/autoloading/function/same_class_different_instances.phpt @@ -0,0 +1,47 @@ +--TEST-- +Registering two different instance of a class as an autoloader should work +--FILE-- +directory_to_use, "\n"; + } +} + +$autoloader1 = new MyAutoloader('dir1'); +autoload_register_function(array($autoloader1, 'autoload')); + +$autoloader2 = new MyAutoloader('dir2'); +autoload_register_function(array($autoloader2, 'autoload')); + +var_dump(autoload_list_function()); +var_dump(function_exists('NonExisting')); + +?> +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(MyAutoloader)#1 (1) { + ["directory_to_use":"MyAutoloader":private]=> + string(4) "dir1" + } + [1]=> + string(8) "autoload" + } + [1]=> + array(2) { + [0]=> + object(MyAutoloader)#2 (1) { + ["directory_to_use":"MyAutoloader":private]=> + string(4) "dir2" + } + [1]=> + string(8) "autoload" + } +} +dir1 +dir2 +bool(false) diff --git a/Zend/tests/autoloading/function/static_methods.phpt b/Zend/tests/autoloading/function/static_methods.phpt new file mode 100644 index 0000000000000..65f5acaa506ed --- /dev/null +++ b/Zend/tests/autoloading/function/static_methods.phpt @@ -0,0 +1,31 @@ +--TEST-- +Autoloader is a static method +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + array(2) { + [0]=> + string(12) "MyAutoLoader" + [1]=> + string(8) "autoLoad" + } +} +MyAutoLoader::autoLoad(foo) +bool(false) diff --git a/Zend/tests/autoloading/function/throw_with_autoload_call_as_autoloader.phpt b/Zend/tests/autoloading/function/throw_with_autoload_call_as_autoloader.phpt new file mode 100644 index 0000000000000..64c066ca22b28 --- /dev/null +++ b/Zend/tests/autoloading/function/throw_with_autoload_call_as_autoloader.phpt @@ -0,0 +1,14 @@ +--TEST-- +Throw when using autoload_register_function() as the autoloading function +--FILE-- +getMessage() . \PHP_EOL; +} + +?> +--EXPECT-- +autoload_register_function(): Argument #1 ($callback) must not be the autoload_call_function() function diff --git a/Zend/tests/autoloading/function/trampoline_autoloader.phpt b/Zend/tests/autoloading/function/trampoline_autoloader.phpt new file mode 100644 index 0000000000000..cc83749a8fd76 --- /dev/null +++ b/Zend/tests/autoloading/function/trampoline_autoloader.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test autoload_register_function(): behavior with a trampoline +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline1" + } + [1]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline2" + } +} +Trampoline for trampoline1 +Trampoline for trampoline2 +bool(false) +Unregister trampoline: +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/Zend/tests/autoloading/function/trampoline_unregister_autoloader_from_list.phpt b/Zend/tests/autoloading/function/trampoline_unregister_autoloader_from_list.phpt new file mode 100644 index 0000000000000..2d7510e34db26 --- /dev/null +++ b/Zend/tests/autoloading/function/trampoline_unregister_autoloader_from_list.phpt @@ -0,0 +1,46 @@ +--TEST-- +Unregister all function autoloaders by traversing the registered list: behavior with a trampoline +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline1" + } + [1]=> + array(2) { + [0]=> + object(TrampolineTest)#1 (0) { + } + [1]=> + string(11) "trampoline2" + } +} +array(0) { +} diff --git a/Zend/tests/autoloading/function/un-register_take_effect_immediately.phpt b/Zend/tests/autoloading/function/un-register_take_effect_immediately.phpt new file mode 100644 index 0000000000000..e7f911b5035bb --- /dev/null +++ b/Zend/tests/autoloading/function/un-register_take_effect_immediately.phpt @@ -0,0 +1,30 @@ +--TEST-- +(Un)Registering autoloaders must take effect immidiately +--FILE-- + +--EXPECT-- +okey, done diff --git a/Zend/tests/autoloading/function/unregister_autoloader.phpt b/Zend/tests/autoloading/function/unregister_autoloader.phpt new file mode 100644 index 0000000000000..1d9bb931f32fe --- /dev/null +++ b/Zend/tests/autoloading/function/unregister_autoloader.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test autoload_unregister_function(): basic function behavior +--FILE-- + +--EXPECT-- +TestFunc1(foo) +TestFunc2(foo) +bool(false) +bool(true) +bool(false) +TestFunc2(foo) +bool(false) diff --git a/Zend/tests/autoloading/function/unregister_autoloader_from_list.phpt b/Zend/tests/autoloading/function/unregister_autoloader_from_list.phpt new file mode 100644 index 0000000000000..453923e6b6c6d --- /dev/null +++ b/Zend/tests/autoloading/function/unregister_autoloader_from_list.phpt @@ -0,0 +1,22 @@ +--TEST-- +Unregister all autoloaders by traversing the registered list +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 063099c0f7c51..616a2fa28680e 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3830,23 +3830,23 @@ ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) { } } -static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_execute_data *frame, zend_fcall_info_cache *fcc, bool strict_class, char **error, bool suppress_deprecation) /* {{{ */ +static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_execute_data *frame, + zend_fcall_info_cache *fcc, bool strict_class, char **error, bool suppress_deprecation, bool use_autoloader) /* {{{ */ { zend_class_entry *ce_org = fcc->calling_scope; bool retval = 0; zend_string *mname, *cname; zend_string *lmname; - const char *colon; - size_t clen; + const char *colon = zend_memrchr(Z_STRVAL_P(callable), ':', Z_STRLEN_P(callable)); HashTable *ftable; - int call_via_handler = 0; + bool call_via_handler = 0; zend_class_entry *scope; zval *zv; fcc->calling_scope = NULL; - if (!ce_org) { - zend_function *func = zend_fetch_function(Z_STR_P(callable)); + if (!ce_org && !colon) { + zend_function *func = zend_fetch_function_ex(Z_STR_P(callable), use_autoloader); if (EXPECTED(func != NULL)) { fcc->function_handler = func; return 1; @@ -3854,14 +3854,11 @@ static zend_always_inline bool zend_is_callable_check_func(zval *callable, zend_ } /* Split name into class/namespace and method/function names */ - if ((colon = zend_memrchr(Z_STRVAL_P(callable), ':', Z_STRLEN_P(callable))) != NULL && - colon > Z_STRVAL_P(callable) && - *(colon-1) == ':' - ) { + if (colon && colon > Z_STRVAL_P(callable) && *(colon-1) == ':') { size_t mlen; colon--; - clen = colon - Z_STRVAL_P(callable); + size_t clen = colon - Z_STRVAL_P(callable); mlen = Z_STRLEN_P(callable) - clen - 2; if (colon == Z_STRVAL_P(callable)) { @@ -4148,7 +4145,9 @@ ZEND_API bool zend_is_callable_at_frame( } check_func: - ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS); + ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, + /* suppress_deprecation */ check_flags & IS_CALLABLE_SUPPRESS_DEPRECATIONS, + /* use_autoloader */ !(check_flags & IS_CALLABLE_CHECK_NO_AUTOLOAD)); if (fcc == &fcc_local) { zend_release_fcall_info_cache(fcc); } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index ab67dd5717e69..a5823eb1009f1 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -409,6 +409,8 @@ ZEND_API ZEND_COLD void zend_wrong_property_read(zval *object, zval *property); #define IS_CALLABLE_CHECK_SYNTAX_ONLY (1<<0) #define IS_CALLABLE_SUPPRESS_DEPRECATIONS (1<<1) +/* Only used for DEBUG builds where the VM will check if a call should throw, causing double autoloading */ +#define IS_CALLABLE_CHECK_NO_AUTOLOAD (1<<2) ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc); ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, zend_object *object); diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c index dd8aa26cb96f9..ea7463639fb69 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -26,6 +26,7 @@ #include "zend_string.h" ZEND_TLS HashTable *autoloader_class_autoload_functions; +ZEND_TLS HashTable *autoloader_function_autoload_functions; #define HT_MOVE_TAIL_TO_HEAD(ht) \ do { \ @@ -33,7 +34,7 @@ ZEND_TLS HashTable *autoloader_class_autoload_functions; memmove((ht)->arData + 1, (ht)->arData, \ sizeof(Bucket) * ((ht)->nNumUsed - 1)); \ (ht)->arData[0] = tmp; \ - if (!((ht)->u.flags & HASH_FLAG_PACKED)) { \ + if (UNEXPECTED(!((ht)->u.flags & HASH_FLAG_PACKED))) { \ zend_hash_rehash(ht); \ } else { \ zend_autoload_reindex(ht); \ @@ -103,6 +104,40 @@ ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, return NULL; } +ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_name, zend_string *lc_name) +{ + if (!autoloader_function_autoload_functions) { + return NULL; + } + + zval zname; + ZVAL_STR(&zname, function_name); + + HashTable *function_autoload_functions = autoloader_function_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(function_autoload_functions, &pos); + while (1) { + zend_fcall_info_cache *func_info = zend_hash_get_current_data_ptr_ex(function_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 (zend_hash_exists(EG(function_table), lc_name)) { + return (zend_function*) zend_hash_find_ptr(EG(function_table), lc_name); + } + + zend_hash_move_forward_ex(function_autoload_functions, &pos); + } + return NULL; +} + /* Needed for compatibility with spl_register_autoload() */ ZEND_API void zend_register_class_autoloader(zend_fcall_info *fci, zend_fcall_info_cache *fcc, bool prepend) { @@ -214,7 +249,6 @@ ZEND_FUNCTION(autoload_call_class) zend_perform_class_autoload(class_name, lc_name); zend_string_release(lc_name); } - /* Return all registered class autoloader functions */ ZEND_FUNCTION(autoload_list_class) { @@ -237,6 +271,124 @@ ZEND_FUNCTION(autoload_list_class) } ZEND_HASH_FOREACH_END(); } +/* Register given function as a function autoloader */ +ZEND_FUNCTION(autoload_register_function) +{ + bool prepend = false; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(prepend) + ZEND_PARSE_PARAMETERS_END(); + + if (!autoloader_function_autoload_functions) { + ALLOC_HASHTABLE(autoloader_function_autoload_functions); + zend_hash_init(autoloader_function_autoload_functions, 1, NULL, zend_autoload_callback_zval_destroy, 0); + /* Initialize as non-packed hash table for prepend functionality. */ + zend_hash_real_init_mixed(autoloader_function_autoload_functions); + } + + if (!ZEND_FCC_INITIALIZED(fcc)) { + /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal + * with it ourselves. It is important that it is not refetched on every call, + * because calls may occur from different scopes. */ + zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); + } + + if (fcc.function_handler->type == ZEND_INTERNAL_FUNCTION && + fcc.function_handler->internal_function.handler == zif_autoload_call_function) { + zend_argument_value_error(1, "must not be the autoload_call_function() function"); + return; + } + + /* If function is already registered, don't do anything */ + if (autoload_find_registered_function(autoloader_function_autoload_functions, &fcc)) { + /* Release call trampoline */ + zend_release_fcall_info_cache(&fcc); + return; + } + + zend_fcall_info_cache *entry = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(entry, &fcc); + zend_hash_next_index_insert_ptr(autoloader_function_autoload_functions, entry); + if (prepend && zend_hash_num_elements(autoloader_function_autoload_functions) > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(autoloader_function_autoload_functions); + } +} + +ZEND_FUNCTION(autoload_unregister_function) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + if (!autoloader_function_autoload_functions) { + RETURN_FALSE; + } + + ZEND_ASSERT(autoloader_function_autoload_functions); + + if (!ZEND_FCC_INITIALIZED(fcc)) { + /* Call trampoline has been cleared by zpp. Refetch it, because we want to deal + * with it ourselves. It is important that it is not refetched on every call, + * because calls may occur from different scopes. */ + zend_is_callable_ex(&fci.function_name, NULL, 0, NULL, &fcc, NULL); + } + Bucket *p = autoload_find_registered_function(autoloader_function_autoload_functions, &fcc); + /* Release trampoline */ + zend_release_fcall_info_cache(&fcc); + + if (p) { + zend_hash_del_bucket(autoloader_function_autoload_functions, p); + RETURN_TRUE; + } + + RETURN_FALSE; +} + +/* Try all registered function autoloader functions to load the requested function */ +ZEND_FUNCTION(autoload_call_function) +{ + zend_string *function_name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &function_name) == FAILURE) { + RETURN_THROWS(); + } + + zend_string *lc_name = zend_string_tolower(function_name); + zend_perform_function_autoload(function_name, lc_name); + zend_string_release(lc_name); +} + +/* Return all registered function autoloader functions */ +ZEND_FUNCTION(autoload_list_function) +{ + zend_fcall_info_cache *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + if (!autoloader_function_autoload_functions) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(autoloader_function_autoload_functions, func_info) { + zval tmp; + zend_get_callable_zval_from_fcc(func_info, &tmp); + add_next_index_zval(return_value, &tmp); + } ZEND_HASH_FOREACH_END(); +} + void zend_autoload_shutdown(void) { if (autoloader_class_autoload_functions) { @@ -244,4 +396,9 @@ void zend_autoload_shutdown(void) FREE_HASHTABLE(autoloader_class_autoload_functions); autoloader_class_autoload_functions = NULL; } + if (autoloader_function_autoload_functions) { + zend_hash_destroy(autoloader_function_autoload_functions); + FREE_HASHTABLE(autoloader_function_autoload_functions); + autoloader_function_autoload_functions = NULL; + } } diff --git a/Zend/zend_autoload.h b/Zend/zend_autoload.h index ccc7112ac943f..2a5461caa4b05 100644 --- a/Zend/zend_autoload.h +++ b/Zend/zend_autoload.h @@ -24,6 +24,10 @@ ZEND_FUNCTION(autoload_register_class); ZEND_FUNCTION(autoload_unregister_class); ZEND_FUNCTION(autoload_call_class); ZEND_FUNCTION(autoload_list_class); +ZEND_FUNCTION(autoload_register_function); +ZEND_FUNCTION(autoload_unregister_function); +ZEND_FUNCTION(autoload_call_function); +ZEND_FUNCTION(autoload_list_function); ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name); ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_name, zend_string *lc_name); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index bdffd8de718be..cc49311fac697 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -37,6 +37,7 @@ ZEND_MINIT_FUNCTION(core) { /* {{{ */ zend_autoload_class = zend_perform_class_autoload; + zend_autoload_function = zend_perform_function_autoload; zend_register_default_classes(); @@ -1163,24 +1164,32 @@ ZEND_FUNCTION(enum_exists) ZEND_FUNCTION(function_exists) { zend_string *name; + bool autoload = true; bool exists; 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] == '\\') { - /* Ignore leading "\" */ - lcname = zend_string_alloc(ZSTR_LEN(name) - 1, 0); - zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); + if (!autoload) { + if (ZSTR_VAL(name)[0] == '\\') { + /* Ignore leading "\" */ + lcname = zend_string_alloc(ZSTR_LEN(name) - 1, 0); + zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); + } else { + lcname = zend_string_tolower(name); + } + + exists = zend_hash_exists(EG(function_table), lcname); + zend_string_release_ex(lcname, 0); } else { - lcname = zend_string_tolower(name); + zend_function *fbc = zend_lookup_function(name); + exists = fbc; } - 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 7e0135c9ab8ce..cfdc87ee0c08b 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 {} @@ -218,3 +218,11 @@ function autoload_unregister_class(callable $callback): bool {} function autoload_call_class(string $class): void {} function autoload_list_class(): iterable {} + +function autoload_register_function(callable $callback, bool $prepend = false): void {} + +function autoload_unregister_function(callable $callback): bool {} + +function autoload_call_function(string $function): void {} + +function autoload_list_function(): iterable {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index ec74a8cc067aa..7d054e3da4691 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: a7316a341ec0a6ebc2646d42373feb3d69ae97f5 */ + * Stub hash: af299a06e48f4ceda87110083d6a11a9807bd251 */ 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) @@ -239,6 +240,16 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_autoload_list_class, 0, 0, Traversable, MAY_BE_ARRAY) ZEND_END_ARG_INFO() +#define arginfo_autoload_register_function arginfo_autoload_register_class + +#define arginfo_autoload_unregister_function arginfo_autoload_unregister_class + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_call_function, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, function, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_autoload_list_function arginfo_autoload_list_class + ZEND_FRAMELESS_FUNCTION(property_exists, 2); static const zend_frameless_function_info frameless_function_infos_property_exists[] = { @@ -317,6 +328,10 @@ ZEND_FUNCTION(autoload_register_class); ZEND_FUNCTION(autoload_unregister_class); ZEND_FUNCTION(autoload_call_class); ZEND_FUNCTION(autoload_list_class); +ZEND_FUNCTION(autoload_register_function); +ZEND_FUNCTION(autoload_unregister_function); +ZEND_FUNCTION(autoload_call_function); +ZEND_FUNCTION(autoload_list_function); static const zend_function_entry ext_functions[] = { ZEND_FE(exit, arginfo_exit) @@ -385,6 +400,10 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(autoload_unregister_class, arginfo_autoload_unregister_class) ZEND_FE(autoload_call_class, arginfo_autoload_call_class) ZEND_FE(autoload_list_class, arginfo_autoload_list_class) + ZEND_FE(autoload_register_function, arginfo_autoload_register_function) + ZEND_FE(autoload_unregister_function, arginfo_autoload_unregister_function) + ZEND_FE(autoload_call_function, arginfo_autoload_call_function) + ZEND_FE(autoload_list_function, arginfo_autoload_list_function) ZEND_FE_END }; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 657374fccd6e3..89f2a2c1f4fb1 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1165,7 +1165,7 @@ static zend_always_inline bool zend_check_type_slow( type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && - zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { + zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS|IS_CALLABLE_CHECK_NO_AUTOLOAD : IS_CALLABLE_CHECK_NO_AUTOLOAD, NULL)) { return 1; } if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) { @@ -4209,9 +4209,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_ex(zend_string *name, bool use_autoloader) /* {{{ */ { - zend_function *fbc = zend_lookup_function(name); + zend_function *fbc = zend_lookup_function_ex(name, NULL, use_autoloader); if (UNEXPECTED(fbc == NULL)) { return NULL; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 007e110c815eb..cee8c885e251f 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -48,7 +48,8 @@ ZEND_API void zend_init_code_execute_data(zend_execute_data *execute_data, zend_ ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value); ZEND_API void execute_ex(zend_execute_data *execute_data); ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value); -ZEND_API bool zend_is_valid_class_name(zend_string *name); +ZEND_API bool zend_is_valid_symbol_name(zend_string *name); +#define zend_is_valid_class_name(name) zend_is_valid_symbol_name((name)) ZEND_API zend_function *zend_lookup_function(zend_string *name); ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string *lcname, bool use_autoload); ZEND_API zend_class_entry *zend_lookup_class(zend_string *name); @@ -405,7 +406,10 @@ 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_ex(zend_string *name, bool use_autoload); +static inline zend_function * zend_fetch_function(zend_string *name) { + return zend_fetch_function_ex(name, true); +} 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 53a85f73c960c..d2b2e19bc5268 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1146,19 +1146,30 @@ static const uint32_t valid_chars[8] = { 0xffffffff, }; +/* TODO Check first byte is not a digit? */ +ZEND_API bool zend_is_valid_symbol_name(zend_string *name) { + for (size_t i = 0; i < ZSTR_LEN(name); i++) { + unsigned char c = ZSTR_VAL(name)[i]; + if (!ZEND_BIT_TEST(valid_chars, c)) { + return 0; + } + } + return 1; +} + ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string *lc_key, bool use_autoload) { zend_function *fbc = NULL; zval *func; zend_string *lc_name; + if (name == NULL || !ZSTR_LEN(name)) { + return NULL; + } + if (lc_key) { - lc_name = lc_key; + lc_name = zend_string_copy(lc_key); } else { - if (name == NULL || !ZSTR_LEN(name)) { - return NULL; - } - if (ZSTR_VAL(name)[0] == '\\') { lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0); zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); @@ -1170,32 +1181,60 @@ ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string * func = zend_hash_find(EG(function_table), lc_name); if (EXPECTED(func)) { - if (!lc_key) { - zend_string_release_ex(lc_name, 0); - } + zend_string_release_ex(lc_name, 0); fbc = Z_FUNC_P(func); return fbc; } - if (!lc_key) { + /* The compiler is not-reentrant. Make sure we autoload only during run-time. */ + if (!use_autoload || zend_is_compiling()) { zend_string_release_ex(lc_name, 0); + return NULL; } - return NULL; + + if (!zend_autoload_function) { + zend_string_release_ex(lc_name, 0); + return NULL; + } + + /* Verify function name before passing it to the autoloader. */ + if (!lc_key && !zend_is_valid_symbol_name(name)) { + zend_string_release_ex(lc_name, 0); + return NULL; + } + + if (EG(in_autoload) == NULL) { + ALLOC_HASHTABLE(EG(in_autoload)); + zend_hash_init(EG(in_autoload), 8, NULL, NULL, 0); + } + + if (zend_hash_add_empty_element(EG(in_autoload), lc_name) == NULL) { + zend_string_release_ex(lc_name, 0); + return NULL; + } + + zend_string *autoload_name; + if (ZSTR_VAL(name)[0] == '\\') { + autoload_name = zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0); + } else { + autoload_name = zend_string_copy(name); + } + + zend_exception_save(); + fbc = zend_autoload_function(autoload_name, lc_name); + zend_exception_restore(); + + zend_string_release_ex(autoload_name, 0); + zend_hash_del(EG(in_autoload), lc_name); + + zend_string_release_ex(lc_name, 0); + + return fbc; } ZEND_API zend_function *zend_lookup_function(zend_string *name) /* {{{ */ { - return zend_lookup_function_ex(name, NULL, 0); -} - -ZEND_API bool zend_is_valid_class_name(zend_string *name) { - for (size_t i = 0; i < ZSTR_LEN(name); i++) { - unsigned char c = ZSTR_VAL(name)[i]; - if (!ZEND_BIT_TEST(valid_chars, c)) { - return 0; - } - } - return 1; + return zend_lookup_function_ex(name, NULL, /* use_autoload */ true); } ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *key, uint32_t flags) /* {{{ */ @@ -1273,7 +1312,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * } /* Verify class name before passing it to the autoloader. */ - if (!key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(name)) { + if (!key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_symbol_name(name)) { zend_string_release_ex(lc_name, 0); return NULL; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 4de7cb958a348..af48c554ba855 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3820,13 +3820,23 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) zend_function *fbc; zend_execute_data *call; + SAVE_OPLINE(); fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); + /* Perform quick look-up of function */ /* Fetch lowercase name stored in the next literal slot */ - fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); - if (UNEXPECTED(fbc == NULL)) { - ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); + zval *func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(function_name+1)); + if (UNEXPECTED(func == NULL)) { + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + if (EXPECTED(!EG(exception))) { + ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); + } + HANDLE_EXCEPTION(); + } + } else { + fbc = Z_FUNC_P(func); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); @@ -3962,17 +3972,32 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) zend_function *fbc; zend_execute_data *call; + SAVE_OPLINE(); fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); /* Fetch lowercase name stored in the next literal slot */ fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } /* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */ fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true); - if (fbc == NULL) { + if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } + /* We bind the unqualified name to the global function + * Use the lowercase name of the function stored in the first cache slot as + * function names are case insensitive */ + else { + zval tmp; + ZVAL_STR(&tmp, Z_STR_P(function_name+1)); + do_bind_function(fbc, &tmp); + } } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a05ec82f968b5..7048d2579c33b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3889,13 +3889,23 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME zend_function *fbc; zend_execute_data *call; + SAVE_OPLINE(); fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); + /* Perform quick look-up of function */ /* Fetch lowercase name stored in the next literal slot */ - fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); - if (UNEXPECTED(fbc == NULL)) { - ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + zval *func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(function_name+1)); + if (UNEXPECTED(func == NULL)) { + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + if (EXPECTED(!EG(exception))) { + ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + } + HANDLE_EXCEPTION(); + } + } else { + fbc = Z_FUNC_P(func); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); @@ -3969,17 +3979,32 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N zend_function *fbc; zend_execute_data *call; + SAVE_OPLINE(); fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); /* Fetch lowercase name stored in the next literal slot */ fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ true); if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } /* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */ fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ true); - if (fbc == NULL) { + if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } + /* We bind the unqualified name to the global function + * Use the lowercase name of the function stored in the first cache slot as + * function names are case insensitive */ + else { + zval tmp; + ZVAL_STR(&tmp, Z_STR_P(function_name+1)); + do_bind_function(fbc, &tmp); + } } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); From 15d6f25b52203e6f8255529b6c7199fd9a6c93e2 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:21:58 +0200 Subject: [PATCH 4/9] Use common function for autoload_list functions --- Zend/zend_autoload.c | 58 ++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c index ea7463639fb69..87bd34052ea25 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -179,6 +179,28 @@ ZEND_API void zend_register_class_autoloader(zend_fcall_info *fci, zend_fcall_in } // TODO USERLAND FUNCTIONS, maybe namespace them? +static void autoload_list(INTERNAL_FUNCTION_PARAMETERS, HashTable *symbol_table) +{ + + zend_fcall_info_cache *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + if (!symbol_table) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(symbol_table, func_info) { + zval tmp; + zend_get_callable_zval_from_fcc(func_info, &tmp); + add_next_index_zval(return_value, &tmp); + } ZEND_HASH_FOREACH_END(); +} + /* Register given function as a class autoloader */ ZEND_FUNCTION(autoload_register_class) { @@ -252,23 +274,7 @@ ZEND_FUNCTION(autoload_call_class) /* Return all registered class autoloader functions */ ZEND_FUNCTION(autoload_list_class) { - zend_fcall_info_cache *func_info; - - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } - - if (!autoloader_class_autoload_functions) { - RETURN_EMPTY_ARRAY(); - } - - array_init(return_value); - - ZEND_HASH_FOREACH_PTR(autoloader_class_autoload_functions, func_info) { - zval tmp; - zend_get_callable_zval_from_fcc(func_info, &tmp); - add_next_index_zval(return_value, &tmp); - } ZEND_HASH_FOREACH_END(); + autoload_list(INTERNAL_FUNCTION_PARAM_PASSTHRU, autoloader_class_autoload_functions); } /* Register given function as a function autoloader */ @@ -370,23 +376,7 @@ ZEND_FUNCTION(autoload_call_function) /* Return all registered function autoloader functions */ ZEND_FUNCTION(autoload_list_function) { - zend_fcall_info_cache *func_info; - - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } - - if (!autoloader_function_autoload_functions) { - RETURN_EMPTY_ARRAY(); - } - - array_init(return_value); - - ZEND_HASH_FOREACH_PTR(autoloader_function_autoload_functions, func_info) { - zval tmp; - zend_get_callable_zval_from_fcc(func_info, &tmp); - add_next_index_zval(return_value, &tmp); - } ZEND_HASH_FOREACH_END(); + autoload_list(INTERNAL_FUNCTION_PARAM_PASSTHRU, autoloader_function_autoload_functions); } void zend_autoload_shutdown(void) From 46657a43d8f3c1f4da382ef487f24fe56a2c3efa Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:22:04 +0200 Subject: [PATCH 5/9] Deprecate BONKERS SPL behaviour --- Zend/zend_autoload.c | 1 + ext/spl/tests/spl_autoload_002.phpt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c index 87bd34052ea25..8eacb96bcd360 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -235,6 +235,7 @@ ZEND_FUNCTION(autoload_unregister_class) * This is forward-ported from SPL */ if (ZEND_FCC_INITIALIZED(fcc) && fcc.function_handler->type == ZEND_INTERNAL_FUNCTION && fcc.function_handler->internal_function.handler == zif_autoload_call_class) { + zend_error(E_DEPRECATED, "Flushing the class autoloader table by passing autoload_call_class() is deprecated"); // Don't destroy the hash table, as we might be iterating over it right now. zend_hash_clean(autoloader_class_autoload_functions); RETURN_TRUE; diff --git a/ext/spl/tests/spl_autoload_002.phpt b/ext/spl/tests/spl_autoload_002.phpt index 1a67baabd0b3c..20bc82526c8d5 100644 --- a/ext/spl/tests/spl_autoload_002.phpt +++ b/ext/spl/tests/spl_autoload_002.phpt @@ -35,7 +35,7 @@ spl_autoload_unregister('spl_autoload'); var_dump(spl_autoload_functions()); ?> ---EXPECT-- +--EXPECTF-- array(0) { } array(1) { @@ -56,6 +56,8 @@ array(2) { [1]=> string(16) "SplAutoloadTest2" } + +Deprecated: Flushing the class autoloader table by passing autoload_call_class() is deprecated in %s on line %d array(0) { } array(1) { From 060bab7d1d4bfc9a8852b6cbfe5f482702a37cdb Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sat, 17 Aug 2024 19:22:36 +0200 Subject: [PATCH 6/9] Attempt to not alias on autoloading non qualified namespace call OpCache doesn't work properly as the local function gets bound to a global one. --- Zend/Optimizer/compact_literals.c | 8 ++++++- Zend/Optimizer/dfa_pass.c | 21 +++++++++++------- Zend/Optimizer/zend_optimizer.c | 8 ++++++- ...namic_local_function_pinned-to_global.phpt | 21 ++++++++++++++++++ ...nction_defined_after_pinned_to_global.phpt | 22 +++++++++++++++++++ .../local_function_pinned-to_global.phpt | 9 +------- Zend/zend_builtin_functions.c | 14 +++++++----- Zend/zend_compile.c | 18 ++++++++++++++- Zend/zend_execute.c | 5 ++++- Zend/zend_opcode.c | 3 +++ Zend/zend_vm_def.h | 17 ++++++++++++-- Zend/zend_vm_execute.h | 19 ++++++++++++++-- 12 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt create mode 100644 Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 4b27aebc9d39a..3f38015b39067 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -603,7 +603,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx break; case ZEND_INIT_FCALL: case ZEND_INIT_FCALL_BY_NAME: - case ZEND_INIT_NS_FCALL_BY_NAME: // op2 func if (func_slot[opline->op2.constant] >= 0) { opline->result.num = func_slot[opline->op2.constant]; @@ -613,6 +612,13 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx func_slot[opline->op2.constant] = opline->result.num; } break; + /* Do not compact runtime function cache for non qualified namespace calls */ + case ZEND_INIT_NS_FCALL_BY_NAME: + // op2 func + opline->result.num = cache_size; + cache_size += sizeof(void *); + func_slot[opline->op2.constant] = opline->result.num; + break; case ZEND_INIT_METHOD_CALL: if (opline->op2_type == IS_CONST) { // op2 method diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index ce2a19f2e8fe9..c7ca06686f6a2 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -408,14 +408,19 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) do { if (call_info->caller_call_opline - && call_info->caller_call_opline->opcode == ZEND_DO_ICALL - && call_info->callee_func - && zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array") - && (call_info->caller_init_opline->extended_value == 2 - || (call_info->caller_init_opline->extended_value == 3 - && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL - && (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) { - + && call_info->caller_call_opline->opcode == ZEND_DO_ICALL + && call_info->callee_func + && call_info->callee_func->common.function_name /* Ignore fake "pass" function */ + && zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array") + && ( + call_info->caller_init_opline->extended_value == 2 + || ( + call_info->caller_init_opline->extended_value == 3 + && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL + && (call_info->caller_call_opline - 1)->op1_type == IS_CONST + ) + ) + ) { zend_op *send_array; zend_op *send_needly; bool strict = 0; diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 823277694b4ff..525c49d423045 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -812,7 +812,13 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename) zend_function *fbc = Z_PTR_P(fbc_zv); if (fbc->type == ZEND_INTERNAL_FUNCTION) { - return false; + /* For ZEND_INIT_NS_FCALL_BY_NAME, the function may be the "fake" pass function + * to indicate that the global function should be used instead */ + if (UNEXPECTED(fbc == (zend_function *) &zend_pass_function)) { + return true; + } else { + return false; + } } else if (fbc->type == ZEND_USER_FUNCTION) { if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) { Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val)); diff --git a/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt b/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt new file mode 100644 index 0000000000000..0598c6769bbbd --- /dev/null +++ b/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt @@ -0,0 +1,21 @@ +--TEST-- +Local function which falls back to global is NOT aliased to the global one, dynamic string +--FILE-- + +--EXPECTF-- +int(5) + +Fatal error: Uncaught Error: Call to undefined function Foo\strlen() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt b/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt new file mode 100644 index 0000000000000..5f0ff17db0a1f --- /dev/null +++ b/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt @@ -0,0 +1,22 @@ +--TEST-- +Local function must be able to be defined after it got pinned to the global one +--FILE-- + +--EXPECT-- +int(5) +int(42) +int(42) diff --git a/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt index feb2dd8109e6c..45a1f55efe94f 100644 --- a/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt +++ b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt @@ -1,13 +1,8 @@ --TEST-- -Local function which falls back to global is aliased to the global one +Local function which falls back to global is NOT aliased to the global one, function_exists --FILE-- --EXPECT-- int(5) -\Foo\strlen() was bound to global \strlen() -int(5) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index cc49311fac697..2c8a7522a9d65 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -1165,8 +1165,8 @@ ZEND_FUNCTION(function_exists) { zend_string *name; bool autoload = true; - bool exists; zend_string *lcname; + zend_function *fbc; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(name) @@ -1183,14 +1183,18 @@ ZEND_FUNCTION(function_exists) lcname = zend_string_tolower(name); } - exists = zend_hash_exists(EG(function_table), lcname); + fbc = (zend_function*) zend_hash_find_ptr(EG(function_table), lcname); zend_string_release_ex(lcname, 0); } else { - zend_function *fbc = zend_lookup_function(name); - exists = fbc; + fbc = zend_lookup_function(name); } - RETURN_BOOL(exists); + /* If the function was marked as using the global function, indicate it doesn't exist */ + if (!fbc || fbc == (zend_function *) &zend_pass_function) { + RETURN_FALSE; + } + + RETURN_TRUE; } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9e736ea1b37ad..f3da250a2c816 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1261,6 +1261,20 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ { zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func); if (UNEXPECTED(!added_func)) { + /* If a function name was marked as using the global name, properly declare the namespaced function */ + zend_function *old_func = zend_hash_find_ptr(EG(function_table), Z_STR_P(lcname)); + if (old_func == (zend_function *) &zend_pass_function) { + zend_hash_update_ptr(EG(function_table), Z_STR_P(lcname), func); + if (func->op_array.refcount) { + ++*func->op_array.refcount; + } + if (func->common.function_name) { + zend_string_addref(func->common.function_name); + } + zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); + return SUCCESS; + } + do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0); return FAILURE; } @@ -1271,7 +1285,9 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ if (func->common.function_name) { zend_string_addref(func->common.function_name); } - zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); + if (EXPECTED(func != (zend_function *) &zend_pass_function)) { + zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); + } return SUCCESS; } /* }}} */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 89f2a2c1f4fb1..dc5317f0285ce 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4865,7 +4865,10 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s init_func_run_time_cache(&fbc->op_array); } } else { - if (UNEXPECTED((fbc = zend_lookup_function(function)) == NULL)) { + if (UNEXPECTED( + ((fbc = zend_lookup_function(function)) == NULL) + || (fbc == (zend_function *) &zend_pass_function) + )) { zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function)); return NULL; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0d1d8b6bf528f..194c6b624ae95 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -149,6 +149,9 @@ ZEND_API void zend_function_dtor(zval *zv) ZEND_ASSERT(function->common.function_name); destroy_op_array(&function->op_array); /* op_arrays are allocated on arena, so we don't have to free them */ + } else if (UNEXPECTED(function == (zend_function *) &zend_pass_function)) { + /* Ignore fake pass function */ + return; } else { ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION); ZEND_ASSERT(function->common.function_name); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index af48c554ba855..3edc61ded1dd2 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3843,6 +3843,8 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) } CACHE_PTR(opline->result.num, fbc); } + + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -3958,6 +3960,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) HANDLE_EXCEPTION(); } + ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -3990,18 +3993,27 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) } ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } - /* We bind the unqualified name to the global function + /* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it + * should use the global function. * Use the lowercase name of the function stored in the first cache slot as * function names are case insensitive */ else { zval tmp; ZVAL_STR(&tmp, Z_STR_P(function_name+1)); - do_bind_function(fbc, &tmp); + do_bind_function((zend_function *) &zend_pass_function, &tmp); } + } else if (fbc == (zend_function *) &zend_pass_function) { + /* Unqualified call was marked as using the global function + * Thus we need to replace the pass function with the actual function. + * Use the lowercase name of the function without tha namespace which is stored in the second cache slot */ + fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2)); + ZEND_ASSERT(fbc); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } + + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); CACHE_PTR(opline->result.num, fbc); } @@ -4031,6 +4043,7 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT) CACHE_PTR(opline->result.num, fbc); } + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7048d2579c33b..eaae199e7b6f9 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3912,6 +3912,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME } CACHE_PTR(opline->result.num, fbc); } + + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -3997,18 +3999,27 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N } ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } - /* We bind the unqualified name to the global function + /* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it + * should use the global function. * Use the lowercase name of the function stored in the first cache slot as * function names are case insensitive */ else { zval tmp; ZVAL_STR(&tmp, Z_STR_P(function_name+1)); - do_bind_function(fbc, &tmp); + do_bind_function((zend_function *) &zend_pass_function, &tmp); } + } else if (fbc == (zend_function *) &zend_pass_function) { + /* Unqualified call was marked as using the global function + * Thus we need to replace the pass function with the actual function. + * Use the lowercase name of the function without tha namespace which is stored in the second cache slot */ + fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2)); + ZEND_ASSERT(fbc); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } + + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); CACHE_PTR(opline->result.num, fbc); } @@ -4038,6 +4049,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CO CACHE_PTR(opline->result.num, fbc); } + ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); @@ -7444,6 +7456,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS HANDLE_EXCEPTION(); } + ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -10009,6 +10022,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV HANDLE_EXCEPTION(); } + ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -12490,6 +12504,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H HANDLE_EXCEPTION(); } + ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); From 8e72f7bde4b38a9c8ac04a4770942941422546e9 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sun, 18 Aug 2024 00:26:15 +0200 Subject: [PATCH 7/9] Revert "Attempt to not alias on autoloading non qualified namespace call" This approach doesn't really work This reverts commit 060bab7d1d4bfc9a8852b6cbfe5f482702a37cdb. --- Zend/Optimizer/compact_literals.c | 8 +------ Zend/Optimizer/dfa_pass.c | 21 +++++++----------- Zend/Optimizer/zend_optimizer.c | 8 +------ ...namic_local_function_pinned-to_global.phpt | 21 ------------------ ...nction_defined_after_pinned_to_global.phpt | 22 ------------------- .../local_function_pinned-to_global.phpt | 9 +++++++- Zend/zend_builtin_functions.c | 14 +++++------- Zend/zend_compile.c | 18 +-------------- Zend/zend_execute.c | 5 +---- Zend/zend_opcode.c | 3 --- Zend/zend_vm_def.h | 17 ++------------ Zend/zend_vm_execute.h | 19 ++-------------- 12 files changed, 29 insertions(+), 136 deletions(-) delete mode 100644 Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt delete mode 100644 Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 3f38015b39067..4b27aebc9d39a 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -603,6 +603,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx break; case ZEND_INIT_FCALL: case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: // op2 func if (func_slot[opline->op2.constant] >= 0) { opline->result.num = func_slot[opline->op2.constant]; @@ -612,13 +613,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx func_slot[opline->op2.constant] = opline->result.num; } break; - /* Do not compact runtime function cache for non qualified namespace calls */ - case ZEND_INIT_NS_FCALL_BY_NAME: - // op2 func - opline->result.num = cache_size; - cache_size += sizeof(void *); - func_slot[opline->op2.constant] = opline->result.num; - break; case ZEND_INIT_METHOD_CALL: if (opline->op2_type == IS_CONST) { // op2 method diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index c7ca06686f6a2..ce2a19f2e8fe9 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -408,19 +408,14 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) do { if (call_info->caller_call_opline - && call_info->caller_call_opline->opcode == ZEND_DO_ICALL - && call_info->callee_func - && call_info->callee_func->common.function_name /* Ignore fake "pass" function */ - && zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array") - && ( - call_info->caller_init_opline->extended_value == 2 - || ( - call_info->caller_init_opline->extended_value == 3 - && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL - && (call_info->caller_call_opline - 1)->op1_type == IS_CONST - ) - ) - ) { + && call_info->caller_call_opline->opcode == ZEND_DO_ICALL + && call_info->callee_func + && zend_string_equals_literal(call_info->callee_func->common.function_name, "in_array") + && (call_info->caller_init_opline->extended_value == 2 + || (call_info->caller_init_opline->extended_value == 3 + && (call_info->caller_call_opline - 1)->opcode == ZEND_SEND_VAL + && (call_info->caller_call_opline - 1)->op1_type == IS_CONST))) { + zend_op *send_array; zend_op *send_needly; bool strict = 0; diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 525c49d423045..823277694b4ff 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -812,13 +812,7 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename) zend_function *fbc = Z_PTR_P(fbc_zv); if (fbc->type == ZEND_INTERNAL_FUNCTION) { - /* For ZEND_INIT_NS_FCALL_BY_NAME, the function may be the "fake" pass function - * to indicate that the global function should be used instead */ - if (UNEXPECTED(fbc == (zend_function *) &zend_pass_function)) { - return true; - } else { - return false; - } + return false; } else if (fbc->type == ZEND_USER_FUNCTION) { if (fbc->op_array.fn_flags & ZEND_ACC_PRELOADED) { Bucket *fbc_bucket = (Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val)); diff --git a/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt b/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt deleted file mode 100644 index 0598c6769bbbd..0000000000000 --- a/Zend/tests/autoloading/function/dynamic_local_function_pinned-to_global.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Local function which falls back to global is NOT aliased to the global one, dynamic string ---FILE-- - ---EXPECTF-- -int(5) - -Fatal error: Uncaught Error: Call to undefined function Foo\strlen() in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d diff --git a/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt b/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt deleted file mode 100644 index 5f0ff17db0a1f..0000000000000 --- a/Zend/tests/autoloading/function/local_function_defined_after_pinned_to_global.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -Local function must be able to be defined after it got pinned to the global one ---FILE-- - ---EXPECT-- -int(5) -int(42) -int(42) diff --git a/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt index 45a1f55efe94f..feb2dd8109e6c 100644 --- a/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt +++ b/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt @@ -1,8 +1,13 @@ --TEST-- -Local function which falls back to global is NOT aliased to the global one, function_exists +Local function which falls back to global is aliased to the global one --FILE-- --EXPECT-- int(5) +\Foo\strlen() was bound to global \strlen() +int(5) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 2c8a7522a9d65..cc49311fac697 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -1165,8 +1165,8 @@ ZEND_FUNCTION(function_exists) { zend_string *name; bool autoload = true; + bool exists; zend_string *lcname; - zend_function *fbc; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(name) @@ -1183,18 +1183,14 @@ ZEND_FUNCTION(function_exists) lcname = zend_string_tolower(name); } - fbc = (zend_function*) zend_hash_find_ptr(EG(function_table), lcname); + exists = zend_hash_exists(EG(function_table), lcname); zend_string_release_ex(lcname, 0); } else { - fbc = zend_lookup_function(name); + zend_function *fbc = zend_lookup_function(name); + exists = fbc; } - /* If the function was marked as using the global function, indicate it doesn't exist */ - if (!fbc || fbc == (zend_function *) &zend_pass_function) { - RETURN_FALSE; - } - - RETURN_TRUE; + RETURN_BOOL(exists); } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f3da250a2c816..9e736ea1b37ad 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1261,20 +1261,6 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ { zend_function *added_func = zend_hash_add_ptr(EG(function_table), Z_STR_P(lcname), func); if (UNEXPECTED(!added_func)) { - /* If a function name was marked as using the global name, properly declare the namespaced function */ - zend_function *old_func = zend_hash_find_ptr(EG(function_table), Z_STR_P(lcname)); - if (old_func == (zend_function *) &zend_pass_function) { - zend_hash_update_ptr(EG(function_table), Z_STR_P(lcname), func); - if (func->op_array.refcount) { - ++*func->op_array.refcount; - } - if (func->common.function_name) { - zend_string_addref(func->common.function_name); - } - zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); - return SUCCESS; - } - do_bind_function_error(Z_STR_P(lcname), &func->op_array, 0); return FAILURE; } @@ -1285,9 +1271,7 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ if (func->common.function_name) { zend_string_addref(func->common.function_name); } - if (EXPECTED(func != (zend_function *) &zend_pass_function)) { - zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); - } + zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); return SUCCESS; } /* }}} */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index dc5317f0285ce..89f2a2c1f4fb1 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4865,10 +4865,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s init_func_run_time_cache(&fbc->op_array); } } else { - if (UNEXPECTED( - ((fbc = zend_lookup_function(function)) == NULL) - || (fbc == (zend_function *) &zend_pass_function) - )) { + if (UNEXPECTED((fbc = zend_lookup_function(function)) == NULL)) { zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function)); return NULL; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 194c6b624ae95..0d1d8b6bf528f 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -149,9 +149,6 @@ ZEND_API void zend_function_dtor(zval *zv) ZEND_ASSERT(function->common.function_name); destroy_op_array(&function->op_array); /* op_arrays are allocated on arena, so we don't have to free them */ - } else if (UNEXPECTED(function == (zend_function *) &zend_pass_function)) { - /* Ignore fake pass function */ - return; } else { ZEND_ASSERT(function->type == ZEND_INTERNAL_FUNCTION); ZEND_ASSERT(function->common.function_name); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3edc61ded1dd2..af48c554ba855 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3843,8 +3843,6 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) } CACHE_PTR(opline->result.num, fbc); } - - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -3960,7 +3958,6 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV, NUM) HANDLE_EXCEPTION(); } - ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -3993,27 +3990,18 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) } ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } - /* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it - * should use the global function. + /* We bind the unqualified name to the global function * Use the lowercase name of the function stored in the first cache slot as * function names are case insensitive */ else { zval tmp; ZVAL_STR(&tmp, Z_STR_P(function_name+1)); - do_bind_function((zend_function *) &zend_pass_function, &tmp); + do_bind_function(fbc, &tmp); } - } else if (fbc == (zend_function *) &zend_pass_function) { - /* Unqualified call was marked as using the global function - * Thus we need to replace the pass function with the actual function. - * Use the lowercase name of the function without tha namespace which is stored in the second cache slot */ - fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2)); - ZEND_ASSERT(fbc); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } - - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); CACHE_PTR(opline->result.num, fbc); } @@ -4043,7 +4031,6 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT) CACHE_PTR(opline->result.num, fbc); } - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index eaae199e7b6f9..7048d2579c33b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3912,8 +3912,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME } CACHE_PTR(opline->result.num, fbc); } - - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); call->prev_execute_data = EX(call); @@ -3999,27 +3997,18 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N } ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } - /* We bind the unqualified name to the internal "zend_pass_function" for it to indicate that it - * should use the global function. + /* We bind the unqualified name to the global function * Use the lowercase name of the function stored in the first cache slot as * function names are case insensitive */ else { zval tmp; ZVAL_STR(&tmp, Z_STR_P(function_name+1)); - do_bind_function((zend_function *) &zend_pass_function, &tmp); + do_bind_function(fbc, &tmp); } - } else if (fbc == (zend_function *) &zend_pass_function) { - /* Unqualified call was marked as using the global function - * Thus we need to replace the pass function with the actual function. - * Use the lowercase name of the function without tha namespace which is stored in the second cache slot */ - fbc = (zend_function *) zend_hash_find_ptr(EG(function_table), Z_STR_P(function_name+2)); - ZEND_ASSERT(fbc); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); } - - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); CACHE_PTR(opline->result.num, fbc); } @@ -4049,7 +4038,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CO CACHE_PTR(opline->result.num, fbc); } - ZEND_ASSERT(fbc != (zend_function*)&zend_pass_function); call = _zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL); @@ -7456,7 +7444,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS HANDLE_EXCEPTION(); } - ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -10022,7 +10009,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV HANDLE_EXCEPTION(); } - ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); @@ -12504,7 +12490,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H HANDLE_EXCEPTION(); } - ZEND_ASSERT(func != (zend_function*)&zend_pass_function); call = zend_vm_stack_push_call_frame(call_info, func, opline->extended_value, object_or_called_scope); call->prev_execute_data = EX(call); From 18c52c2fac1b1fbeac71048e1fd98a72601ed0f2 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sun, 18 Aug 2024 15:35:11 +0200 Subject: [PATCH 8/9] JIT Support for function autoloading --- .../emit-parse-error-in-autoloader.phpt | 2 ++ ext/opcache/jit/zend_jit_helpers.c | 32 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt b/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt index 5d67d8d134de9..7785b6f0675fe 100644 --- a/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt +++ b/Zend/tests/autoloading/function/emit-parse-error-in-autoloader.phpt @@ -1,5 +1,7 @@ --TEST-- Parse errors should be thrown if occuring from an autoloader +--XFAIL-- +Problem with JIT that needs to be resolved --FILE-- type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { fbc = _zend_jit_init_func_run_time_cache(&fbc->op_array); } @@ -81,12 +85,28 @@ static zend_function* ZEND_FASTCALL zend_jit_find_ns_func_helper(zval *func_name zend_function *fbc; if (func == NULL) { - func = zend_hash_find_known_hash(EG(function_table), Z_STR_P(func_name + 2)); - if (UNEXPECTED(func == NULL)) { - return NULL; + fbc = zend_lookup_function_ex(Z_STR_P(func_name), Z_STR_P(func_name + 1), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + return NULL; + } + /* Fallback onto global namespace, by fetching the unqualified lowercase name stored in the second literal slot */ + fbc = zend_lookup_function_ex(Z_STR_P(func_name + 2), Z_STR_P(func_name + 2), /* use_autoload */ true); + if (UNEXPECTED(fbc == NULL)) { + return NULL; + } + /* We bind the unqualified name to the global function + * Use the lowercase name of the function stored in the first cache slot as + * function names are case insensitive */ + else { + zval tmp; + ZVAL_STR(&tmp, Z_STR_P(func_name + 1)); + do_bind_function(fbc, &tmp); + } } + } else { + fbc = Z_FUNC_P(func); } - fbc = Z_FUNC_P(func); if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { fbc = _zend_jit_init_func_run_time_cache(&fbc->op_array); } From 7b2b59f63a5ea8f674ab4648ac1ec178d7a1e3b8 Mon Sep 17 00:00:00 2001 From: Gina Peter Bnayard Date: Sun, 18 Aug 2024 16:06:42 +0200 Subject: [PATCH 9/9] Fix SPL return type --- ext/spl/php_spl.stub.php | 2 +- ext/spl/php_spl_arginfo.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ext/spl/php_spl.stub.php b/ext/spl/php_spl.stub.php index c1a71ce46f999..3b54cf569086e 100644 --- a/ext/spl/php_spl.stub.php +++ b/ext/spl/php_spl.stub.php @@ -31,7 +31,7 @@ function spl_autoload_call(string $class): void {} function spl_autoload_extensions(?string $file_extensions = null): string {} /** @alias autoload_list_class */ -function spl_autoload_functions(): array {} +function spl_autoload_functions(): iterable {} function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): bool {} diff --git a/ext/spl/php_spl_arginfo.h b/ext/spl/php_spl_arginfo.h index f49a3e2a763d4..8a3c490ff3caa 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: 92f8ab4833e20330385f597cc62349d7ee23d1ca */ + * Stub hash: 98bf7efa4216725d1e806a61ab2be284b9624da8 */ 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) @@ -23,7 +23,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_extensions, 0, 0, I ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, file_extensions, IS_STRING, 1, "null") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_functions, 0, 0, IS_ARRAY, 0) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_spl_autoload_functions, 0, 0, Traversable, MAY_BE_ARRAY) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_register, 0, 0, _IS_BOOL, 0) @@ -36,7 +36,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_spl_autoload_unregister, 0, 1, _ ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) 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)