From ba081c22ffe89cc52b565f4ae59d23f3fa0f9d20 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 23 Sep 2021 00:08:55 +0100 Subject: [PATCH 01/17] Introduce zend_lookup_function() --- Zend/zend_API.c | 23 +--------------- Zend/zend_execute.c | 28 +++++++------------- Zend/zend_execute.h | 2 ++ Zend/zend_execute_API.c | 58 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_vm_def.h | 28 ++++++++------------ Zend/zend_vm_execute.h | 28 ++++++++------------ 6 files changed, 92 insertions(+), 75 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 3ef291c315596..1a30228234021 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3463,32 +3463,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 c41ac0965791d..1c91532ce89bc 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3785,19 +3785,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); @@ -4163,7 +4163,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; @@ -4215,20 +4214,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 d13087a5b0264..8711c56c3aca3 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -46,6 +46,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 fed3ae1d29963..ebaa077e8643d 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1046,6 +1046,64 @@ 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; + } + + /* The compiler is not-reentrant. Make sure we autoload only during run-time. */ + if (!use_autoload || zend_is_compiling()) { + if (!lc_key) { + zend_string_release_ex(lc_name, 0); + } + return NULL; + } + + if (!zend_autoload) { + if (!lc_key) { + zend_string_release_ex(lc_name, 0); + } + return NULL; + } + + 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 934860c33012e..9cf73767f1e3b 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3746,17 +3746,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); + fbc = zend_lookup_function(Z_STR_P(function_name+1)); + //fbc = zend_lookup_function(Z_STR_P(function_name)); + 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); } @@ -3888,22 +3887,19 @@ 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); + fbc = zend_lookup_function(Z_STR_P(function_name+1)); + if (fbc == NULL) { + fbc = zend_lookup_function(Z_STR_P(function_name+2)); + 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); } @@ -3921,15 +3917,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 37c2d1ee653e2..62ebd0c7a0ba0 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3571,17 +3571,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); + fbc = zend_lookup_function(Z_STR_P(function_name+1)); + //fbc = zend_lookup_function(Z_STR_P(function_name)); + 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); } @@ -3651,22 +3650,19 @@ 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); + fbc = zend_lookup_function(Z_STR_P(function_name+1)); + if (fbc == NULL) { + fbc = zend_lookup_function(Z_STR_P(function_name+2)); + 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); } @@ -3684,15 +3680,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 724267c92a78f87cfa19505371a17993cb13a5d6 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 23 Sep 2021 02:35:16 +0100 Subject: [PATCH 02/17] New core class autoloader Memory leaks pending. --- .../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 | 62 ++++ .../{ => 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 | 9 +- .../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 + .../un-register_take_effect_immediately.phpt | 8 +- .../class/unregister_autoloader.phpt | 33 ++ .../unregister_autoloader_from_list.phpt | 22 ++ Zend/zend.c | 1 + Zend/zend_autoload.c | 297 ++++++++++++++++++ Zend/zend_autoload.h | 37 +++ Zend/zend_builtin_functions.c | 3 + Zend/zend_builtin_functions.stub.php | 8 + Zend/zend_builtin_functions_arginfo.h | 25 +- Zend/zend_execute.h | 3 +- Zend/zend_execute_API.c | 18 +- Zend/zend_globals.h | 6 + configure.ac | 2 +- ext/spl/php_spl.c | 265 +--------------- 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 | 49 --- 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 +- 55 files changed, 912 insertions(+), 796 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 (66%) 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 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..e67384a3e8a05 --- /dev/null +++ b/Zend/tests/autoloading/class/autoloader_with_closures_and_invocables.phpt @@ -0,0 +1,62 @@ +--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; + +?> +--EXPECT-- +array(3) { + [0]=> + object(Closure)#1 (1) { + ["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 66% rename from ext/spl/tests/bug73896.phpt rename to Zend/tests/autoloading/class/bug73896.phpt index 657f30c50dbec..b6fbb1a1eb57f 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-- --EXPECT-- +Protected autoload() called! Exception: Class "teException" not found diff --git a/ext/spl/tests/spl_autoload_011.phpt b/Zend/tests/autoloading/class/destructor_call.phpt similarity index 76% rename from ext/spl/tests/spl_autoload_011.phpt rename to Zend/tests/autoloading/class/destructor_call.phpt index 771726ee56d88..2f667971de90c 100644 --- a/ext/spl/tests/spl_autoload_011.phpt +++ b/Zend/tests/autoloading/class/destructor_call.phpt @@ -1,7 +1,5 @@ --TEST-- -SPL: spl_autoload() and object freed ---INI-- -include_path=. +Destructor call of autoloader when object freed --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/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..ee8cacb363185 --- /dev/null +++ b/Zend/tests/autoloading/class/unregister_autoloader_from_list.phpt @@ -0,0 +1,22 @@ +--TEST-- +Unregister all autoloades by traversing the registered list +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend.c b/Zend/zend.c index c37cd9ed97453..756955eaf6f63 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -757,6 +757,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ ZVAL_UNDEF(&executor_globals->user_error_handler); ZVAL_UNDEF(&executor_globals->user_exception_handler); executor_globals->in_autoload = NULL; + // TODO Init autoloader stacks executor_globals->current_execute_data = NULL; executor_globals->current_module = NULL; executor_globals->exit_status = 0; diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c new file mode 100644 index 0000000000000..ca1746af2fe1e --- /dev/null +++ b/Zend/zend_autoload.c @@ -0,0 +1,297 @@ +/* + +----------------------------------------------------------------------+ + | 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" + +#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; + } +} + +static zend_always_inline bool zend_autoload_callback_equals(const zend_autoload_func* func, const zend_autoload_func* current) +{ + return func->fcc.function_handler == current->fcc.function_handler + && func->fcc.object == current->fcc.object + && func->fcc.calling_scope == current->fcc.calling_scope + ; +} + +static zend_autoload_func *autoload_func_from_fci(zend_fcall_info *fci, zend_fcall_info_cache *fcc) { + zend_autoload_func *entry = emalloc(sizeof(zend_autoload_func)); + memcpy(&entry->fci, fci, sizeof(zend_fcall_info)); + memcpy(&entry->fcc, fcc, sizeof(zend_fcall_info_cache)); + Z_TRY_ADDREF(entry->fci.function_name); + return entry; +} + +static void zend_autoload_callback_destroy(zend_autoload_func *entry) +{ + zend_release_fcall_info_cache(&entry->fcc); + zend_fcall_info_args_clear(&entry->fci, true); + zval_ptr_dtor(&entry->fci.function_name); + efree(entry); +} + +ZEND_API void zend_autoload_callback_zval_destroy(zval *element) +{ + zend_autoload_callback_destroy(Z_PTR_P(element)); +} + +static Bucket *autoload_find_registered_function(HashTable *autoloader_table, zend_autoload_func *function_entry) +{ + zend_autoload_func *current_function_entry; + ZEND_HASH_MAP_FOREACH_PTR(autoloader_table, current_function_entry) { + if (zend_autoload_callback_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) +{ + zval zname; + + ZEND_ASSERT(&EG(autoloaders).class_autoload_functions); + + ZVAL_STR(&zname, class_name); + + HashTable *class_autoload_functions = &EG(autoloaders).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_autoload_func *func_info = zend_hash_get_current_data_ptr_ex(class_autoload_functions, &pos); + if (!func_info) { + break; + } + zval retval; + + func_info->fci.retval = &retval; + zend_fcall_info_argn(&func_info->fci, 1, &zname); + zend_call_function(&func_info->fci, &func_info->fcc); + + 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; +} + +ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_name, zend_string *lc_name) +{ + zend_function *fn; + zend_autoload_func *func_info; + zval zname; + + ZEND_ASSERT(&EG(autoloaders).function_autoload_functions); + + ZVAL_STR(&zname, function_name); + + HashTable *function_autoload_functions = &EG(autoloaders).function_autoload_functions; + + // TODO Mimic Class + ZEND_HASH_MAP_FOREACH_PTR(function_autoload_functions, func_info) + zval retval; + + func_info->fci.retval = &retval; + zend_fcall_info_argn(&func_info->fci, 1, &zname); + zend_call_function(&func_info->fci, &func_info->fcc); + zend_fcall_info_args_clear(&func_info->fci, /* free_mem */ true); + zend_exception_save(); + if (zend_hash_exists(EG(function_table), lc_name)) { + break; + } + ZEND_HASH_FOREACH_END(); + + zend_exception_restore(); + + fn = (zend_function*) zend_hash_find_ptr(EG(function_table), lc_name); + + return fn; +} + +/* 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) +{ + zend_autoload_func *autoload_function_entry; + + ZEND_ASSERT(&EG(autoloaders).class_autoload_functions); + + if (!fcc->function_handler) { + /* 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; + } + + autoload_function_entry = autoload_func_from_fci(fci, fcc); + + /* If function is already registered, don't do anything */ + if (autoload_find_registered_function(&EG(autoloaders).class_autoload_functions, autoload_function_entry)) { + zend_autoload_callback_destroy(autoload_function_entry); + return; + } + + zend_hash_next_index_insert_ptr(&EG(autoloaders).class_autoload_functions, autoload_function_entry); + if (prepend && zend_hash_num_elements(&EG(autoloaders).class_autoload_functions) > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(&EG(autoloaders).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_autoload_func *autoload_function_entry; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + ZEND_ASSERT(&EG(autoloaders).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 (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(&EG(autoloaders).class_autoload_functions); + RETURN_TRUE; + } + + autoload_function_entry = autoload_func_from_fci(&fci, &fcc); + + Bucket *p = autoload_find_registered_function(&EG(autoloaders).class_autoload_functions, autoload_function_entry); + zend_autoload_callback_destroy(autoload_function_entry); + + if (p) { + zend_hash_del_bucket(&EG(autoloaders).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_autoload_func *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(&EG(autoloaders).class_autoload_functions, func_info) { + if (Z_TYPE(func_info->fci.function_name) == IS_OBJECT) { + add_next_index_zval(return_value, &func_info->fci.function_name); + } else if (func_info->fcc.function_handler->common.scope) { + zval tmp; + + array_init(&tmp); + if (func_info->fcc.object) { + add_next_index_object(&tmp, func_info->fcc.object); + } else { + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.calling_scope->name)); + } + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + add_next_index_zval(return_value, &tmp); + } else { + add_next_index_str(return_value, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + } + } ZEND_HASH_FOREACH_END(); +} + +void zend_autoload_shutdown(void) +{ + zend_hash_destroy(&EG(autoloaders.class_autoload_functions)); + zend_hash_destroy(&EG(autoloaders.function_autoload_functions)); +} diff --git a/Zend/zend_autoload.h b/Zend/zend_autoload.h new file mode 100644 index 0000000000000..cc56a5a586961 --- /dev/null +++ b/Zend/zend_autoload.h @@ -0,0 +1,37 @@ +/* + +----------------------------------------------------------------------+ + | 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); + +typedef struct { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} zend_autoload_func; + +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 529ade072aa24..29e45b5975fc1 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -28,12 +28,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 98a50c66194c7..25193c545a409 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -195,3 +195,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(): array {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index b358d313b60be..8d68468c9de7f 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: f87d92c002674c431827895a8d8b3a5da3b95482 */ + * Stub hash: 1951be1dbb5831684167a8a9b6f84b6510d7a645 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -216,6 +216,21 @@ 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() + +#define arginfo_autoload_list_class arginfo_func_get_args + ZEND_FUNCTION(zend_version); ZEND_FUNCTION(func_num_args); @@ -275,6 +290,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[] = { @@ -338,6 +357,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 8711c56c3aca3..a2d23e7ee6cd8 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -32,7 +32,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 ebaa077e8643d..0c57e7051f896 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -37,6 +37,7 @@ #include "zend_weakrefs.h" #include "zend_inheritance.h" #include "zend_observer.h" +#include "zend_autoload.h" #ifdef HAVE_SYS_TIME_H #include #endif @@ -46,7 +47,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}; @@ -143,6 +145,12 @@ void init_executor(void) /* {{{ */ EG(class_table) = CG(class_table); EG(in_autoload) = NULL; + zend_hash_init(&EG(autoloaders.class_autoload_functions), 8, NULL, zend_autoload_callback_zval_destroy, 0); + zend_hash_init(&EG(autoloaders.function_autoload_functions), 8, NULL, zend_autoload_callback_zval_destroy, 0); + /* Initialize as non-packed hash table for prepend functionality. */ + zend_hash_real_init_mixed(&EG(autoloaders).class_autoload_functions); + zend_hash_real_init_mixed(&EG(autoloaders).function_autoload_functions); + EG(error_handling) = EH_NORMAL; EG(flags) = EG_FLAGS_INITIAL; @@ -388,6 +396,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(); @@ -1086,7 +1096,7 @@ ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string * return NULL; } - if (!zend_autoload) { + if (!zend_autoload_function) { if (!lc_key) { zend_string_release_ex(lc_name, 0); } @@ -1181,7 +1191,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); } @@ -1213,7 +1223,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * } zend_exception_save(); - ce = zend_autoload(autoload_name, lc_name); + ce = zend_autoload_class(autoload_name, lc_name); zend_exception_restore(); zend_string_release_ex(autoload_name, 0); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 1726dee9ac12a..78a6ab9e5f89d 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -184,6 +184,12 @@ struct _zend_executor_globals { uint32_t persistent_classes_count; HashTable *in_autoload; + struct { + HashTable class_autoload_functions; + HashTable function_autoload_functions; + /*HashTable constant_autoload_functions;*/ + } autoloaders; + bool full_tables_cleanup; /* for extended information support */ diff --git a/configure.ac b/configure.ac index db1cac4ca6018..a9f8c18aa4715 100644 --- a/configure.ac +++ b/configure.ac @@ -1634,7 +1634,7 @@ PHP_ADD_SOURCES(Zend, \ zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \ zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \ zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \ - zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c \ + zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_autoload.c \ Optimizer/zend_optimizer.c \ Optimizer/pass1.c \ Optimizer/pass3.c \ diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 839e978ec61ad..68287e28e2307 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -36,6 +36,7 @@ #include "spl_heap.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_autoload.h" #include "ext/standard/php_mt_rand.h" #include "main/snprintf.h" @@ -352,149 +353,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) { - 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 @@ -508,121 +373,27 @@ 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, 0, 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); - } - + zend_string *name = zend_string_init("spl_autoload", strlen("spl_autoload"), false); + fci.size = sizeof(zend_fcall_info); + ZVAL_STR(&fci.function_name, name); + fci.object = NULL; + fci.params = NULL; + fci.param_count = 0; + fci.named_params = NULL; + fcc.function_handler = zend_hash_find_ptr(EG(function_table), name); + fcc.object = NULL; + zend_register_class_autoloader(&fci, &fcc, prepend); + zend_string_release_ex(name, false); + } + + /* 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")) { - /* Don't destroy the hash table, as we might be iterating over it right now. */ - zend_hash_clean(spl_autoload_functions); - RETURN_TRUE; - } - - 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) { @@ -700,8 +471,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 c879f1fa5e503..378b7d978e7b2 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 2565e935ae4fa..0b06176dc8365 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: 7359e9e5aa07c00d0e0be2642f11c1131a17e61e */ + * Stub hash: ab0402a8712a32f75be20bd85c36b4cd14aadd0e */ 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) @@ -66,11 +66,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); @@ -84,11 +84,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_FALIAS(spl_autoload_call, autoload_call_class, arginfo_spl_autoload_call) ZEND_FE(spl_autoload_extensions, arginfo_spl_autoload_extensions) - ZEND_FE(spl_autoload_functions, arginfo_spl_autoload_functions) + ZEND_FALIAS(spl_autoload_functions, autoload_list_class, arginfo_spl_autoload_functions) ZEND_FE(spl_autoload_register, arginfo_spl_autoload_register) - ZEND_FE(spl_autoload_unregister, arginfo_spl_autoload_unregister) + ZEND_FALIAS(spl_autoload_unregister, autoload_unregister_class, arginfo_spl_autoload_unregister) 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 fe71562273d58..0000000000000 --- a/ext/spl/tests/spl_autoload_013.phpt +++ /dev/null @@ -1,49 +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 (1) { - ["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 From 163d3d9f619e4e7b2fe7d906ca2fda2f843aa8fc Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 27 Nov 2021 14:25:16 +0000 Subject: [PATCH 03/17] Implementation of function autoloading --- .../function/autoload_call_basic.phpt | 14 ++ .../function/autoload_called_scope.phpt | 33 ++++ ...toloader_with_closures_and_invocables.phpt | 62 ++++++++ .../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 ++++++ .../function/innacessible_methods.phpt | 132 ++++++++++++++++ Zend/tests/autoloading/function/methods.phpt | 43 ++++++ .../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 ++ .../un-register_take_effect_immediately.phpt | 30 ++++ .../function/unregister_autoloader.phpt | 33 ++++ .../unregister_autoloader_from_list.phpt | 22 +++ Zend/zend_autoload.c | 145 ++++++++++++++++-- Zend/zend_autoload.h | 4 + Zend/zend_builtin_functions.c | 27 ++-- Zend/zend_builtin_functions.stub.php | 10 +- Zend/zend_builtin_functions_arginfo.h | 19 ++- Zend/zend_execute_API.c | 58 +++++-- Zend/zend_vm_def.h | 12 +- Zend/zend_vm_execute.h | 12 +- 26 files changed, 1024 insertions(+), 52 deletions(-) create mode 100644 Zend/tests/autoloading/function/autoload_call_basic.phpt create mode 100644 Zend/tests/autoloading/function/autoload_called_scope.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/innacessible_methods.phpt create mode 100644 Zend/tests/autoloading/function/methods.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/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_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/autoloader_with_closures_and_invocables.phpt b/Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt new file mode 100644 index 0000000000000..8c3987078385f --- /dev/null +++ b/Zend/tests/autoloading/function/autoloader_with_closures_and_invocables.phpt @@ -0,0 +1,62 @@ +--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(); + +?> +--EXPECT-- +array(3) { + [0]=> + object(Closure)#1 (1) { + ["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..d0a5cd24f88cf --- /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('ThisClassDoesNo...') +#1 %s(%d): function_exists('ThisClassDoesNo...') +#2 {main} + thrown in %s on line %d 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/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/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/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..614c3d0470361 --- /dev/null +++ b/Zend/tests/autoloading/function/unregister_autoloader_from_list.phpt @@ -0,0 +1,22 @@ +--TEST-- +Unregister all autoloades by traversing the registered list +--FILE-- + +--EXPECT-- +array(0) { +} diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c index ca1746af2fe1e..eb40daefce95d 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -128,8 +128,6 @@ ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_name, zend_string *lc_name) { - zend_function *fn; - zend_autoload_func *func_info; zval zname; ZEND_ASSERT(&EG(autoloaders).function_autoload_functions); @@ -138,25 +136,31 @@ ZEND_API zend_function *zend_perform_function_autoload(zend_string *function_nam HashTable *function_autoload_functions = &EG(autoloaders).function_autoload_functions; - // TODO Mimic Class - ZEND_HASH_MAP_FOREACH_PTR(function_autoload_functions, func_info) + /* 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_autoload_func *func_info = zend_hash_get_current_data_ptr_ex(function_autoload_functions, &pos); + if (!func_info) { + break; + } zval retval; func_info->fci.retval = &retval; zend_fcall_info_argn(&func_info->fci, 1, &zname); zend_call_function(&func_info->fci, &func_info->fcc); - zend_fcall_info_args_clear(&func_info->fci, /* free_mem */ true); - zend_exception_save(); + + if (EG(exception)) { + return NULL; + } if (zend_hash_exists(EG(function_table), lc_name)) { - break; + return (zend_function*) zend_hash_find_ptr(EG(function_table), lc_name); } - ZEND_HASH_FOREACH_END(); - - zend_exception_restore(); - fn = (zend_function*) zend_hash_find_ptr(EG(function_table), lc_name); - - return fn; + zend_hash_move_forward_ex(function_autoload_functions, &pos); + } + return NULL; } /* Needed for compatibility with spl_register_autoload() */ @@ -258,7 +262,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) { @@ -290,6 +293,120 @@ 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_autoload_func *autoload_function_entry; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(prepend) + ZEND_PARSE_PARAMETERS_END(); + + ZEND_ASSERT(&EG(autoloaders).function_autoload_functions); + + if (!fcc.function_handler) { + /* 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; + } + + autoload_function_entry = autoload_func_from_fci(&fci, &fcc); + + /* If function is already registered, don't do anything */ + if (autoload_find_registered_function(&EG(autoloaders).function_autoload_functions, autoload_function_entry)) { + zend_autoload_callback_destroy(autoload_function_entry); + return; + } + + zend_hash_next_index_insert_ptr(&EG(autoloaders).function_autoload_functions, autoload_function_entry); + if (prepend && zend_hash_num_elements(&EG(autoloaders).function_autoload_functions) > 1) { + /* Move the newly created element to the head of the hashtable */ + HT_MOVE_TAIL_TO_HEAD(&EG(autoloaders).function_autoload_functions); + } +} + +ZEND_FUNCTION(autoload_unregister_function) +{ + zend_fcall_info fci; + zend_fcall_info_cache fcc; + zend_autoload_func *autoload_function_entry; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + ZEND_ASSERT(&EG(autoloaders).function_autoload_functions); + + autoload_function_entry = autoload_func_from_fci(&fci, &fcc); + + Bucket *p = autoload_find_registered_function(&EG(autoloaders).function_autoload_functions, autoload_function_entry); + zend_autoload_callback_destroy(autoload_function_entry); + + if (p) { + zend_hash_del_bucket(&EG(autoloaders).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_autoload_func *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(&EG(autoloaders).function_autoload_functions, func_info) { + if (Z_TYPE(func_info->fci.function_name) == IS_OBJECT) { + add_next_index_zval(return_value, &func_info->fci.function_name); + } else if (func_info->fcc.function_handler->common.scope) { + zval tmp; + + array_init(&tmp); + if (func_info->fcc.object) { + add_next_index_object(&tmp, func_info->fcc.object); + } else { + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.calling_scope->name)); + } + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + add_next_index_zval(return_value, &tmp); + } else { + add_next_index_str(return_value, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + } + } ZEND_HASH_FOREACH_END(); +} + void zend_autoload_shutdown(void) { zend_hash_destroy(&EG(autoloaders.class_autoload_functions)); diff --git a/Zend/zend_autoload.h b/Zend/zend_autoload.h index cc56a5a586961..332d3a17acb5d 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); typedef struct { zend_fcall_info fci; diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 29e45b5975fc1..558c7104ad2cf 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -36,6 +36,7 @@ ZEND_MINIT_FUNCTION(core) { /* {{{ */ zend_autoload_class = zend_perform_class_autoload; + zend_autoload_function = zend_perform_function_autoload; zend_register_default_classes(); @@ -1034,24 +1035,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 25193c545a409..c2d03772f9182 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -74,7 +74,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 {} @@ -203,3 +203,11 @@ function autoload_unregister_class(callable $callback): bool {} function autoload_call_class(string $class): void {} function autoload_list_class(): array {} + +function autoload_register_function(callable $callback, bool $prepend = false): void {} + +function autoload_unregister_function(callable $callback): bool {} + +function autoload_call_function(string $class): void {} + +function autoload_list_function(): array {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index 8d68468c9de7f..8d97a217c8226 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: 1951be1dbb5831684167a8a9b6f84b6510d7a645 */ + * Stub hash: 3d21a38b5383fddee104e62733db941caffe342f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -115,6 +115,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) @@ -231,6 +232,14 @@ ZEND_END_ARG_INFO() #define arginfo_autoload_list_class arginfo_func_get_args +#define arginfo_autoload_register_function arginfo_autoload_register_class + +#define arginfo_autoload_unregister_function arginfo_autoload_unregister_class + +#define arginfo_autoload_call_function arginfo_autoload_call_class + +#define arginfo_autoload_list_function arginfo_func_get_args + ZEND_FUNCTION(zend_version); ZEND_FUNCTION(func_num_args); @@ -294,6 +303,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[] = { @@ -361,6 +374,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_API.c b/Zend/zend_execute_API.c index 0c57e7051f896..1306287028628 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1063,13 +1063,13 @@ ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string * zend_string *lc_name; zend_string *autoload_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); @@ -1081,37 +1081,61 @@ 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; } /* The compiler is not-reentrant. Make sure we autoload only during run-time. */ if (!use_autoload || zend_is_compiling()) { - if (!lc_key) { - zend_string_release_ex(lc_name, 0); - } + zend_string_release_ex(lc_name, 0); return NULL; } if (!zend_autoload_function) { - if (!lc_key) { - zend_string_release_ex(lc_name, 0); - } + zend_string_release_ex(lc_name, 0); return NULL; } - if (!lc_key) { + /* Verify function name before passing it to the autoloader. */ + /* + if (!lc_key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(name)) { zend_string_release_ex(lc_name, 0); + return NULL; } - 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; + } + + 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); + return zend_lookup_function_ex(name, NULL, true); } ZEND_API bool zend_is_valid_class_name(zend_string *name) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9cf73767f1e3b..6f50194f3557c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3752,9 +3752,11 @@ ZEND_VM_HOT_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); fbc = zend_lookup_function(Z_STR_P(function_name+1)); - //fbc = zend_lookup_function(Z_STR_P(function_name)); if (UNEXPECTED(fbc == NULL)) { - ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); + if (EXPECTED(!EG(exception))) { + ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); + } + HANDLE_EXCEPTION(); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); @@ -3893,12 +3895,12 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name+1)); + fbc = zend_lookup_function(Z_STR_P(function_name)); if (fbc == NULL) { - fbc = zend_lookup_function(Z_STR_P(function_name+2)); - if (fbc == NULL) { + if (EXPECTED(!EG(exception))) { ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } + HANDLE_EXCEPTION(); } 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 62ebd0c7a0ba0..d670d698d1ddc 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3577,9 +3577,11 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); fbc = zend_lookup_function(Z_STR_P(function_name+1)); - //fbc = zend_lookup_function(Z_STR_P(function_name)); if (UNEXPECTED(fbc == NULL)) { - ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + if (EXPECTED(!EG(exception))) { + ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + } + HANDLE_EXCEPTION(); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); @@ -3656,12 +3658,12 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name+1)); + fbc = zend_lookup_function(Z_STR_P(function_name)); if (fbc == NULL) { - fbc = zend_lookup_function(Z_STR_P(function_name+2)); - if (fbc == NULL) { + if (EXPECTED(!EG(exception))) { ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } + HANDLE_EXCEPTION(); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); From 8521e09355609addf2063cae368d9fdb0e03ed0d Mon Sep 17 00:00:00 2001 From: Danack Date: Fri, 3 Dec 2021 02:52:59 +0000 Subject: [PATCH 04/17] Check function found on fall back to root. --- .../autoloading/function/namespaces_basic.phpt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Zend/tests/autoloading/function/namespaces_basic.phpt diff --git a/Zend/tests/autoloading/function/namespaces_basic.phpt b/Zend/tests/autoloading/function/namespaces_basic.phpt new file mode 100644 index 0000000000000..3d27008899121 --- /dev/null +++ b/Zend/tests/autoloading/function/namespaces_basic.phpt @@ -0,0 +1,17 @@ +--TEST-- +Check functions found on fallback to root. +--FILE-- + +--EXPECT-- +I am just foo. From b01e12087130e1c31cf87507b092fcd7bdf4adda Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 3 Dec 2021 11:18:12 +0000 Subject: [PATCH 05/17] Fallback to global namespace --- Zend/zend_vm_def.h | 15 +++++++++++---- Zend/zend_vm_execute.h | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 6f50194f3557c..50cfb17605590 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3895,12 +3895,19 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name)); - if (fbc == NULL) { - if (EXPECTED(!EG(exception))) { + fbc = zend_lookup_function(Z_STR_P(function_name)+1); + if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } + /* Fallback onto global namespace */ + fbc = zend_lookup_function(Z_STR_P(function_name)+2); + if (fbc == NULL) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); } - HANDLE_EXCEPTION(); } 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 d670d698d1ddc..8d709d902f749 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3658,12 +3658,19 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name)); - if (fbc == NULL) { - if (EXPECTED(!EG(exception))) { + fbc = zend_lookup_function(Z_STR_P(function_name)+1); + if (UNEXPECTED(fbc == NULL)) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } + /* Fallback onto global namespace */ + fbc = zend_lookup_function(Z_STR_P(function_name)+2); + if (fbc == NULL) { + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } - HANDLE_EXCEPTION(); } if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) { init_func_run_time_cache(&fbc->op_array); From 57626b7bde8422f54feddf2830c1c70e3966d785 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 3 Dec 2021 12:21:47 +0000 Subject: [PATCH 06/17] Fix double autoloading issue in debug builds --- Zend/zend_API.c | 21 +++++++++------------ Zend/zend_API.h | 2 ++ Zend/zend_execute.c | 6 +++--- Zend/zend_execute.h | 5 ++++- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 1a30228234021..d873684932884 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3451,23 +3451,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) /* {{{ */ +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 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; @@ -3475,14 +3475,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)) { @@ -3768,7 +3765,7 @@ ZEND_API bool zend_is_callable_at_frame( } check_func: - ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error); + ret = zend_is_callable_check_func(callable, frame, fcc, strict_class, error, (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 e31745298d9da..829ce6ee01353 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -379,6 +379,8 @@ ZEND_API zend_result zend_disable_class(const char *class_name, size_t class_nam ZEND_API ZEND_COLD void zend_wrong_param_count(void); #define IS_CALLABLE_CHECK_SYNTAX_ONLY (1<<0) +/* Only used for DEBUG builds where the VM will check if a call should throw, causing double autoloading */ +#define IS_CALLABLE_CHECK_NO_AUTOLOAD (2<<0) 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_execute.c b/Zend/zend_execute.c index 1c91532ce89bc..e9036f4ea2255 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1045,7 +1045,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, 0, NULL)) { + if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_NO_AUTOLOAD, NULL)) { return 1; } if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { @@ -3783,9 +3783,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 a2d23e7ee6cd8..1834bf7f6824c 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -355,7 +355,10 @@ ZEND_API zend_class_entry *zend_fetch_class(zend_string *class_name, int fetch_t ZEND_API zend_class_entry *zend_fetch_class_with_scope(zend_string *class_name, int fetch_type, zend_class_entry *scope); ZEND_API zend_class_entry *zend_fetch_class_by_name(zend_string *class_name, zend_string *lcname, int 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); From b3c021988a22cb52ffacf3e4b6d5851d6a253334 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 4 Dec 2021 13:12:20 +0000 Subject: [PATCH 07/17] Fix autoloading of invalid symbol names --- .../function/autoload_call_invalid_name.phpt | 36 ++++++++++++++++ .../autoload_invalid_name_variable.phpt | 41 +++++++++++++++++++ Zend/zend_execute.h | 3 +- Zend/zend_execute_API.c | 27 ++++++------ 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/autoloading/function/autoload_call_invalid_name.phpt create mode 100644 Zend/tests/autoloading/function/autoload_invalid_name_variable.phpt 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_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/zend_execute.h b/Zend/zend_execute.h index 1834bf7f6824c..a36e2e969176c 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -46,7 +46,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); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1306287028628..ee45f1d4a34fc 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1056,6 +1056,17 @@ 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; @@ -1098,12 +1109,10 @@ ZEND_API zend_function *zend_lookup_function_ex(zend_string *name, zend_string * } /* Verify function name before passing it to the autoloader. */ - /* - if (!lc_key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(name)) { + 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)); @@ -1138,16 +1147,6 @@ ZEND_API zend_function *zend_lookup_function(zend_string *name) /* {{{ */ return zend_lookup_function_ex(name, NULL, true); } -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; -} - ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *key, uint32_t flags) /* {{{ */ { zend_class_entry *ce = NULL; @@ -1223,7 +1222,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; } From 340430d257a0600883753fa716c1c8a1660ab359 Mon Sep 17 00:00:00 2001 From: Danack Date: Sat, 4 Dec 2021 01:15:00 +0000 Subject: [PATCH 08/17] Add test for missing namespaced function. Function which doesn't have a global fallback. Closes #12 --- .../function/missing_function_in_namespace.phpt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Zend/tests/autoloading/function/missing_function_in_namespace.phpt 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..7864ebcaf922c --- /dev/null +++ b/Zend/tests/autoloading/function/missing_function_in_namespace.phpt @@ -0,0 +1,16 @@ +--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 From 308bf333549a5e6cbfec8dfb7deff3884387743a Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 4 Dec 2021 14:14:26 +0000 Subject: [PATCH 09/17] Fix test case, by actually using the literal OP slots And some optimizations at the same time --- .../function/missing_function_in_namespace.phpt | 2 -- Zend/zend_vm_def.h | 10 ++++++---- Zend/zend_vm_execute.h | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Zend/tests/autoloading/function/missing_function_in_namespace.phpt b/Zend/tests/autoloading/function/missing_function_in_namespace.phpt index 7864ebcaf922c..f7f0d960c2ef5 100644 --- a/Zend/tests/autoloading/function/missing_function_in_namespace.phpt +++ b/Zend/tests/autoloading/function/missing_function_in_namespace.phpt @@ -3,8 +3,6 @@ Missing function in namespace --FILE-- result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name+1)); + /* 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 (EXPECTED(!EG(exception))) { ZEND_VM_DISPATCH_TO_HELPER(zend_undefined_function_helper); @@ -3895,13 +3896,14 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name)+1); + /* 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 */ - fbc = zend_lookup_function(Z_STR_P(function_name)+2); + /* Fallback onto global namespace, by fetching the unqualified name stored in the second literal slot */ + fbc = zend_lookup_function(Z_STR_P(function_name+2)); if (fbc == NULL) { if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8d709d902f749..a7e5dccc50e34 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3576,7 +3576,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval*)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name+1)); + /* 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 (EXPECTED(!EG(exception))) { ZEND_VM_TAIL_CALL(zend_undefined_function_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); @@ -3658,13 +3659,14 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N fbc = CACHED_PTR(opline->result.num); if (UNEXPECTED(fbc == NULL)) { zval *function_name = (zval *)RT_CONSTANT(opline, opline->op2); - fbc = zend_lookup_function(Z_STR_P(function_name)+1); + /* 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 */ - fbc = zend_lookup_function(Z_STR_P(function_name)+2); + /* Fallback onto global namespace, by fetching the unqualified name stored in the second literal slot */ + fbc = zend_lookup_function(Z_STR_P(function_name+2)); if (fbc == NULL) { if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); From 08f714fe74ac2be85859de5259e51844c78ff5f3 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 5 Dec 2021 17:44:17 +0000 Subject: [PATCH 10/17] Use common function for autoload_list functions --- Zend/zend_autoload.c | 84 +++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/Zend/zend_autoload.c b/Zend/zend_autoload.c index eb40daefce95d..ac432524565f4 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -199,6 +199,36 @@ 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_autoload_func *func_info; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + array_init(return_value); + + ZEND_HASH_FOREACH_PTR(symbol_table, func_info) { + if (Z_TYPE(func_info->fci.function_name) == IS_OBJECT) { + add_next_index_zval(return_value, &func_info->fci.function_name); + } else if (func_info->fcc.function_handler->common.scope) { + zval tmp; + + array_init(&tmp); + if (func_info->fcc.object) { + add_next_index_object(&tmp, func_info->fcc.object); + } else { + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.calling_scope->name)); + } + add_next_index_str(&tmp, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + add_next_index_zval(return_value, &tmp); + } else { + add_next_index_str(return_value, zend_string_copy(func_info->fcc.function_handler->common.function_name)); + } + } ZEND_HASH_FOREACH_END(); +} + /* Register given function as a class autoloader */ ZEND_FUNCTION(autoload_register_class) { @@ -265,32 +295,7 @@ ZEND_FUNCTION(autoload_call_class) /* Return all registered class autoloader functions */ ZEND_FUNCTION(autoload_list_class) { - zend_autoload_func *func_info; - - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } - - array_init(return_value); - - ZEND_HASH_FOREACH_PTR(&EG(autoloaders).class_autoload_functions, func_info) { - if (Z_TYPE(func_info->fci.function_name) == IS_OBJECT) { - add_next_index_zval(return_value, &func_info->fci.function_name); - } else if (func_info->fcc.function_handler->common.scope) { - zval tmp; - - array_init(&tmp); - if (func_info->fcc.object) { - add_next_index_object(&tmp, func_info->fcc.object); - } else { - add_next_index_str(&tmp, zend_string_copy(func_info->fcc.calling_scope->name)); - } - add_next_index_str(&tmp, zend_string_copy(func_info->fcc.function_handler->common.function_name)); - add_next_index_zval(return_value, &tmp); - } else { - add_next_index_str(return_value, zend_string_copy(func_info->fcc.function_handler->common.function_name)); - } - } ZEND_HASH_FOREACH_END(); + autoload_list(INTERNAL_FUNCTION_PARAM_PASSTHRU, &EG(autoloaders).class_autoload_functions); } /* Register given function as a function autoloader */ @@ -379,32 +384,7 @@ ZEND_FUNCTION(autoload_call_function) /* Return all registered function autoloader functions */ ZEND_FUNCTION(autoload_list_function) { - zend_autoload_func *func_info; - - if (zend_parse_parameters_none() == FAILURE) { - RETURN_THROWS(); - } - - array_init(return_value); - - ZEND_HASH_FOREACH_PTR(&EG(autoloaders).function_autoload_functions, func_info) { - if (Z_TYPE(func_info->fci.function_name) == IS_OBJECT) { - add_next_index_zval(return_value, &func_info->fci.function_name); - } else if (func_info->fcc.function_handler->common.scope) { - zval tmp; - - array_init(&tmp); - if (func_info->fcc.object) { - add_next_index_object(&tmp, func_info->fcc.object); - } else { - add_next_index_str(&tmp, zend_string_copy(func_info->fcc.calling_scope->name)); - } - add_next_index_str(&tmp, zend_string_copy(func_info->fcc.function_handler->common.function_name)); - add_next_index_zval(return_value, &tmp); - } else { - add_next_index_str(return_value, zend_string_copy(func_info->fcc.function_handler->common.function_name)); - } - } ZEND_HASH_FOREACH_END(); + autoload_list(INTERNAL_FUNCTION_PARAM_PASSTHRU, &EG(autoloaders).function_autoload_functions); } void zend_autoload_shutdown(void) From 1a6b1dd45b176476999f9e43972d35e526f3ebeb Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 5 Dec 2021 17:45:57 +0000 Subject: [PATCH 11/17] Fix exception not being able to be caught in autoloader when function is autoloaded --- .../function/exceptions_during_autoloading003.phpt | 4 ++-- Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt b/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt index d0a5cd24f88cf..3c4aa9a73b1a0 100644 --- a/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading003.phpt @@ -44,7 +44,7 @@ autoload_first Fatal error: Uncaught Exception: first in %s:%d Stack trace: -#0 [internal function]: autoload_first('ThisClassDoesNo...') -#1 %s(%d): function_exists('ThisClassDoesNo...') +#0 [internal function]: autoload_first('foo') +#1 %s(%d): function_exists('foo') #2 {main} thrown in %s on line %d diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index dfc003ebf2d3c..8c7a557a0b933 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3748,6 +3748,7 @@ 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); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a7e5dccc50e34..3d89716f9c768 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3573,6 +3573,7 @@ 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); From 44f8d1e54b57fd0691187d3c02dee81ee12db3c7 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 6 Dec 2021 19:33:11 +0000 Subject: [PATCH 12/17] Add test and fix for exceptions thrown in autoloader in namespace --- .../exceptions_during_autoloading004.phpt | 50 +++++++++++++++++++ Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 1 + 3 files changed, 52 insertions(+) create mode 100644 Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt 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..fc3d2a374c0e8 --- /dev/null +++ b/Zend/tests/autoloading/function/exceptions_during_autoloading004.phpt @@ -0,0 +1,50 @@ +--TEST-- +Capturing multiple Exceptions during autoloading in a namespace +--FILE-- +getMessage()."\n"; + } while ($e = $e->getPrevious()); + } + try { + foo(); + } catch (\Exception $e) { + do { + echo $e->getMessage()."\n"; + } while ($e = $e->getPrevious()); + } + + \function_exists('foo'); +} +?> +--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/zend_vm_def.h b/Zend/zend_vm_def.h index 8c7a557a0b933..c97c7d631291f 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3894,6 +3894,7 @@ 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); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 3d89716f9c768..6e73ec1b55026 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3657,6 +3657,7 @@ 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); From 68ea343ddad2e5e2b4193becaa31d1303fae614a Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 6 Dec 2021 20:44:08 +0000 Subject: [PATCH 13/17] Prevent lowercasing of string already lowercased a compile time --- Zend/zend_vm_def.h | 4 ++-- Zend/zend_vm_execute.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c97c7d631291f..a702b18c55ef9 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3904,8 +3904,8 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); } - /* Fallback onto global namespace, by fetching the unqualified name stored in the second literal slot */ - fbc = zend_lookup_function(Z_STR_P(function_name+2)); + /* 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(EG(exception))) { HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6e73ec1b55026..1eac54456c719 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3667,8 +3667,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); } - /* Fallback onto global namespace, by fetching the unqualified name stored in the second literal slot */ - fbc = zend_lookup_function(Z_STR_P(function_name+2)); + /* 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(EG(exception))) { HANDLE_EXCEPTION(); From 72ba12ad0838e6a9761dd95c67a66b1727ae35a5 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 9 Dec 2021 20:10:44 +0000 Subject: [PATCH 14/17] 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 ac432524565f4..d91e0319d3e37 100644 --- a/Zend/zend_autoload.c +++ b/Zend/zend_autoload.c @@ -261,6 +261,7 @@ ZEND_FUNCTION(autoload_unregister_class) * This is forward-ported from SPL */ if (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(&EG(autoloaders).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 c38b11dd840b8921ee4378825acdfba06a874ed4 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Thu, 23 Sep 2021 01:20:28 +0100 Subject: [PATCH 15/17] Add todos for constant autoloading lookups --- Zend/zend_builtin_functions.c | 1 + Zend/zend_constants.c | 2 ++ Zend/zend_execute.c | 1 + Zend/zend_vm_def.h | 1 + Zend/zend_vm_execute.h | 1 + 5 files changed, 6 insertions(+) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 558c7104ad2cf..1d660918adf60 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -523,6 +523,7 @@ ZEND_FUNCTION(define) /* {{{ Check whether a constant exists Warning: This function is special-cased by zend_compile.c and so is usually bypassed */ + // TODO Update this and compiler ZEND_FUNCTION(defined) { zend_string *name; diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index 6ca402c61bf62..d814f1c575af5 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -276,6 +276,7 @@ ZEND_API bool zend_verify_const_access(zend_class_constant *c, zend_class_entry } /* }}} */ +// TODO Add support for auto-loading? static zend_constant *zend_get_constant_str_impl(const char *name, size_t name_len) { zend_constant *c = zend_hash_str_find_ptr(EG(zend_constants), name, name_len); @@ -302,6 +303,7 @@ ZEND_API zval *zend_get_constant_str(const char *name, size_t name_len) static zend_constant *zend_get_constant_impl(zend_string *name) { + // TODO Use lookup zend_constant *c = zend_hash_find_ptr(EG(zend_constants), name); if (c) { return c; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index e9036f4ea2255..f1c9a4f2f6782 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4500,6 +4500,7 @@ static zend_always_inline zend_result _zend_quick_get_constant( zend_constant *c = NULL; /* null/true/false are resolved during compilation, so don't check for them here. */ + // TODO Create a constant_lookup zv = zend_hash_find_known_hash(EG(zend_constants), Z_STR_P(key)); if (zv) { c = (zend_constant*)Z_PTR_P(zv); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a702b18c55ef9..2c938ebfe2a8d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -5889,6 +5889,7 @@ ZEND_VM_HOT_HANDLER(99, ZEND_FETCH_CONSTANT, UNUSED|CONST_FETCH, CONST, CACHE_SL } SAVE_OPLINE(); + // TODO Constant lookup zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num OPLINE_CC EXECUTE_DATA_CC); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 1eac54456c719..37b11600539ae 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -33321,6 +33321,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPE } SAVE_OPLINE(); + // TODO Constant lookup zend_quick_get_constant(RT_CONSTANT(opline, opline->op2) + 1, opline->op1.num OPLINE_CC EXECUTE_DATA_CC); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } From 4896e9f4c85f686b86474ab1633bdab1f2ba797b Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 12 Dec 2021 01:53:09 +0000 Subject: [PATCH 16/17] Do not run autoloader when resolving unqualified call --- ...aces_basic.phpt => global_fallback001.phpt} | 0 .../function/global_fallback002.phpt | 18 ++++++++++++++++++ Zend/zend_vm_def.h | 4 ++-- Zend/zend_vm_execute.h | 4 ++-- 4 files changed, 22 insertions(+), 4 deletions(-) rename Zend/tests/autoloading/function/{namespaces_basic.phpt => global_fallback001.phpt} (100%) create mode 100644 Zend/tests/autoloading/function/global_fallback002.phpt diff --git a/Zend/tests/autoloading/function/namespaces_basic.phpt b/Zend/tests/autoloading/function/global_fallback001.phpt similarity index 100% rename from Zend/tests/autoloading/function/namespaces_basic.phpt rename to Zend/tests/autoloading/function/global_fallback001.phpt diff --git a/Zend/tests/autoloading/function/global_fallback002.phpt b/Zend/tests/autoloading/function/global_fallback002.phpt new file mode 100644 index 0000000000000..5af856c4c5319 --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback002.phpt @@ -0,0 +1,18 @@ +--TEST-- +Fallback to global function should not trigger autoloading. +--FILE-- + +--EXPECT-- +string(5) "Hello" diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 2c938ebfe2a8d..70686a35df26e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3899,13 +3899,13 @@ ZEND_VM_HOT_HANDLER(69, ZEND_INIT_NS_FCALL_BY_NAME, ANY, CONST, NUM|CACHE_SLOT) 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); + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ false); 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); + fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ false); if (fbc == NULL) { if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 37b11600539ae..6eb89b1635c1e 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3662,13 +3662,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_NS_FCALL_BY_N 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); + fbc = zend_lookup_function_ex(Z_STR_P(function_name), Z_STR_P(function_name+1), /* use_autoload */ false); 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); + fbc = zend_lookup_function_ex(Z_STR_P(function_name+2), Z_STR_P(function_name+2), /* use_autoload */ false); if (fbc == NULL) { if (UNEXPECTED(EG(exception))) { HANDLE_EXCEPTION(); From 3cd71dfebb0c89ba23db5b117da6a59836509569 Mon Sep 17 00:00:00 2001 From: Danack Date: Mon, 13 Dec 2021 01:02:32 +0000 Subject: [PATCH 17/17] Add failing test with aguably the correct behaviour. --- .../global_fallback_error_on_redefine.phpt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Zend/tests/autoloading/function/global_fallback_error_on_redefine.phpt diff --git a/Zend/tests/autoloading/function/global_fallback_error_on_redefine.phpt b/Zend/tests/autoloading/function/global_fallback_error_on_redefine.phpt new file mode 100644 index 0000000000000..55e5d20703212 --- /dev/null +++ b/Zend/tests/autoloading/function/global_fallback_error_on_redefine.phpt @@ -0,0 +1,22 @@ +--TEST-- +Avoid function confusion when fallback is used. +--FILE-- + +--EXPECT-- +false +5 +true +Exception: cannot re-register function Foo\count, fallback to root has been used.