From bf427b732abaffbbb776bfc93a7a8fc83fc39452 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 11 Jul 2022 16:19:26 +0200 Subject: [PATCH] Add an API to observe functions and classes being linked To observe when the functions and classes start being officially available to the user Signed-off-by: Bob Weinand --- Zend/zend_API.c | 2 + Zend/zend_compile.c | 5 ++ Zend/zend_compile.h | 3 ++ Zend/zend_inheritance.c | 9 +++- Zend/zend_observer.c | 46 ++++++++++++++++ Zend/zend_observer.h | 11 ++++ ext/opcache/ZendAccelerator.c | 1 + ext/opcache/zend_accelerator_util_funcs.c | 11 ++++ ext/zend_test/observer.c | 16 ++++++ ext/zend_test/php_test.h | 1 + .../tests/observer_declarations_01.phpt | 54 +++++++++++++++++++ 11 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 ext/zend_test/tests/observer_declarations_01.phpt diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 133281235fe..56db0311020 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -31,6 +31,7 @@ #include "zend_inheritance.h" #include "zend_ini.h" #include "zend_enum.h" +#include "zend_observer.h" #include @@ -3263,6 +3264,7 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { ce->refcount++; } + zend_observer_class_linked_notify(ce, lcname); return SUCCESS; } return FAILURE; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 487c15b589e..41b4075493d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1093,6 +1093,7 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{ if (func->common.function_name) { zend_string_addref(func->common.function_name); } + zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); return SUCCESS; } /* }}} */ @@ -1116,12 +1117,14 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot( } if (ce->ce_flags & ZEND_ACC_LINKED) { + zend_observer_class_linked_notify(ce, Z_STR_P(lcname)); return ce; } ce = zend_do_link_class(ce, lc_parent_name, Z_STR_P(lcname)); if (ce) { ZEND_ASSERT(!EG(exception)); + zend_observer_class_linked_notify(ce, Z_STR_P(lcname)); return ce; } @@ -7245,6 +7248,7 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) { do_bind_function_error(lcname, op_array, 1); } + zend_observer_function_declared_notify(op_array, lcname); zend_string_release_ex(lcname, 0); return; } @@ -7877,6 +7881,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_string_release(lcname); zend_build_properties_info_table(ce); ce->ce_flags |= ZEND_ACC_LINKED; + zend_observer_class_linked_notify(ce, lcname); return; } } else if (!extends_ast) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index afca2917820..21c1f6d18af 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1181,6 +1181,9 @@ END_EXTERN_C() /* this flag is set when compiler invoked during preloading in separate process */ #define ZEND_COMPILE_PRELOAD_IN_CHILD (1<<17) +/* ignore observer notifications, e.g. to manually notify afterwards in a post-processing step after compilation */ +#define ZEND_COMPILE_IGNORE_OBSERVER (1<<18) + /* The default value for CG(compiler_options) */ #define ZEND_COMPILE_DEFAULT ZEND_COMPILE_HANDLE_OP_ARRAY diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 52c974bece4..3eaaca3409a 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -29,6 +29,7 @@ #include "zend_enum.h" #include "zend_attributes.h" #include "zend_constants.h" +#include "zend_observer.h" ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL; ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies) = NULL; @@ -3134,18 +3135,24 @@ static zend_always_inline bool register_early_bound_ce(zval *delayed_early_bindi if (EXPECTED(!(ce->ce_flags & ZEND_ACC_PRELOADED))) { if (zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) != NULL) { Z_CE_P(delayed_early_binding) = ce; + zend_observer_class_linked_notify(ce, lcname); return true; } } else { /* If preloading is used, don't replace the existing bucket, add a new one. */ if (zend_hash_add_ptr(EG(class_table), lcname, ce) != NULL) { + zend_observer_class_linked_notify(ce, lcname); return true; } } zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name)); return false; } - return zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL; + if (zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL) { + zend_observer_class_linked_notify(ce, lcname); + return true; + } + return false; } ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce, zend_string *lcname, zval *delayed_early_binding) /* {{{ */ diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index c70844a7762..80d1fbd1876 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -32,12 +32,17 @@ (ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) zend_llist zend_observers_fcall_list; +zend_llist zend_observer_function_declared_callbacks; +zend_llist zend_observer_class_linked_callbacks; zend_llist zend_observer_error_callbacks; zend_llist zend_observer_fiber_init; zend_llist zend_observer_fiber_switch; zend_llist zend_observer_fiber_destroy; int zend_observer_fcall_op_array_extension; +bool zend_observer_errors_observed; +bool zend_observer_function_declared_observed; +bool zend_observer_class_linked_observed; ZEND_TLS zend_execute_data *current_observed_frame; @@ -51,6 +56,8 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) ZEND_API void zend_observer_startup(void) { zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1); + zend_llist_init(&zend_observer_function_declared_callbacks, sizeof(zend_observer_function_declared_cb), NULL, 1); + zend_llist_init(&zend_observer_class_linked_callbacks, sizeof(zend_observer_class_linked_cb), NULL, 1); zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1); zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1); zend_llist_init(&zend_observer_fiber_switch, sizeof(zend_observer_fiber_switch_handler), NULL, 1); @@ -100,6 +107,8 @@ ZEND_API void zend_observer_activate(void) ZEND_API void zend_observer_shutdown(void) { zend_llist_destroy(&zend_observers_fcall_list); + zend_llist_destroy(&zend_observer_function_declared_callbacks); + zend_llist_destroy(&zend_observer_class_linked_callbacks); zend_llist_destroy(&zend_observer_error_callbacks); zend_llist_destroy(&zend_observer_fiber_init); zend_llist_destroy(&zend_observer_fiber_switch); @@ -286,8 +295,45 @@ ZEND_API void zend_observer_fcall_end_all(void) EG(current_execute_data) = original_execute_data; } +ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb) +{ + zend_observer_function_declared_observed = true; + zend_llist_add_element(&zend_observer_function_declared_callbacks, &cb); +} + +ZEND_API void ZEND_FASTCALL zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name) +{ + if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) { + return; + } + + for (zend_llist_element *element = zend_observer_function_declared_callbacks.head; element; element = element->next) { + zend_observer_function_declared_cb callback = *(zend_observer_function_declared_cb *) (element->data); + callback(op_array, name); + } +} + +ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb) +{ + zend_observer_class_linked_observed = true; + zend_llist_add_element(&zend_observer_class_linked_callbacks, &cb); +} + +ZEND_API void ZEND_FASTCALL zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name) +{ + if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) { + return; + } + + for (zend_llist_element *element = zend_observer_class_linked_callbacks.head; element; element = element->next) { + zend_observer_class_linked_cb callback = *(zend_observer_class_linked_cb *) (element->data); + callback(ce, name); + } +} + ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) { + zend_observer_errors_observed = true; zend_llist_add_element(&zend_observer_error_callbacks, &cb); } diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index ebd5c0ce62a..0cd660db7e5 100644 --- a/Zend/zend_observer.h +++ b/Zend/zend_observer.h @@ -27,6 +27,9 @@ BEGIN_EXTERN_C() extern ZEND_API int zend_observer_fcall_op_array_extension; +extern ZEND_API bool zend_observer_errors_observed; +extern ZEND_API bool zend_observer_function_declared_observed; +extern ZEND_API bool zend_observer_class_linked_observed; #define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1) @@ -80,6 +83,14 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end( ZEND_API void zend_observer_fcall_end_all(void); +typedef void (*zend_observer_function_declared_cb)(zend_op_array *op_array, zend_string *name); +typedef void (*zend_observer_class_linked_cb)(zend_class_entry *ce, zend_string *name); + +ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb); +ZEND_API void ZEND_FASTCALL zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name); +ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb); +ZEND_API void ZEND_FASTCALL zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name); + typedef void (*zend_observer_error_cb)(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message); ZEND_API void zend_observer_error_register(zend_observer_error_cb callback); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 570e6a248b8..fd2792028b1 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1806,6 +1806,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING; CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION; CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES; + CG(compiler_options) |= ZEND_COMPILE_IGNORE_OBSERVER; if (ZCG(accel_directives).file_cache) { CG(compiler_options) |= ZEND_COMPILE_WITH_FILE_CACHE; } diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 6a454a0a9e4..f0fb9cc56b9 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -25,6 +25,7 @@ #include "zend_accelerator_util_funcs.h" #include "zend_persist.h" #include "zend_shared_alloc.h" +#include "zend_observer.h" typedef int (*id_function_t)(void *, void *); typedef void (*unique_copy_ctor_func_t)(void *pElement); @@ -145,6 +146,8 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source) Bucket *p, *end; zval *t; + bool call_observers = zend_observer_function_declared_observed; + zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0); p = source->arData; end = p + source->nNumUsed; @@ -156,6 +159,9 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source) goto failure; } _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1); + if (UNEXPECTED(call_observers) && *ZSTR_VAL(p->key)) { // if not rtd key + zend_observer_function_declared_notify(Z_PTR(p->val), p->key); + } } target->nInternalPointer = 0; return; @@ -182,6 +188,8 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source) Bucket *p, *end; zval *t; + bool call_observers = zend_observer_class_linked_observed; + zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0); p = source->arData; end = p + source->nNumUsed; @@ -221,6 +229,9 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source) && ZSTR_HAS_CE_CACHE(ce->name) && ZSTR_VAL(p->key)[0]) { ZSTR_SET_CE_CACHE_EX(ce->name, ce, 0); + if (UNEXPECTED(call_observers)) { + zend_observer_class_linked_notify(ce, p->key); + } } } } diff --git a/ext/zend_test/observer.c b/ext/zend_test/observer.c index 14d08af18b3..e6be124b444 100644 --- a/ext/zend_test/observer.c +++ b/ext/zend_test/observer.c @@ -256,6 +256,18 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context } } +void declared_function_observer(zend_op_array *op_array, zend_string *name) { + if (ZT_G(observer_observe_declaring)) { + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name)); + } +} + +void declared_class_observer(zend_class_entry *ce, zend_string *name) { + if (ZT_G(observer_observe_declaring)) { + php_printf("%*s\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name)); + } +} + static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList) { zend_array **p = (zend_array **) ZEND_INI_GET_ADDR(); @@ -301,6 +313,7 @@ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals) + STD_PHP_INI_BOOLEAN("zend_test.observer.observe_declaring", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_declaring, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_ENTRY("zend_test.observer.observe_function_names", "", PHP_INI_ALL, zend_test_observer_OnUpdateCommaList, observer_observe_function_names, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_type", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_type, zend_zend_test_globals, zend_test_globals) STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_value", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_value, zend_zend_test_globals, zend_test_globals) @@ -334,6 +347,9 @@ void zend_test_observer_init(INIT_FUNC_ARGS) zend_observer_fiber_switch_register(fiber_enter_observer); zend_observer_fiber_switch_register(fiber_suspend_observer); zend_observer_fiber_destroy_register(fiber_destroy_observer); + + zend_observer_function_declared_register(declared_function_observer); + zend_observer_class_linked_register(declared_class_observer); } } diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 1c0b24f0d08..70ec15ec0dc 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -38,6 +38,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test) int observer_observe_all; int observer_observe_includes; int observer_observe_functions; + int observer_observe_declaring; zend_array *observer_observe_function_names; int observer_show_return_type; int observer_show_return_value; diff --git a/ext/zend_test/tests/observer_declarations_01.phpt b/ext/zend_test/tests/observer_declarations_01.phpt new file mode 100644 index 00000000000..6d91834ef9f --- /dev/null +++ b/ext/zend_test/tests/observer_declarations_01.phpt @@ -0,0 +1,54 @@ +--TEST-- +Observer: Observe function and class declarations +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.observe_declaring=1 +--FILE-- + 0) { + function nested() + { + } + + class C + { + } + + class D extends A + { + } +} + +foo(); +?> +--EXPECTF-- + + + + + + + + + + + + +foo + +