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 <bobwei9@hotmail.com>
This commit is contained in:
Bob Weinand 2022-07-11 16:19:26 +02:00
parent 9b984f52ea
commit bf427b732a
11 changed files with 158 additions and 1 deletions

View File

@ -31,6 +31,7 @@
#include "zend_inheritance.h"
#include "zend_ini.h"
#include "zend_enum.h"
#include "zend_observer.h"
#include <stdarg.h>
@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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) /* {{{ */

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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<!-- declared function '%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<!-- declared class '%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);
}
}

View File

@ -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;

View File

@ -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--
<?php
function foo()
{
echo "foo\n";
}
class A {
}
class B extends A {
}
if (time() > 0) {
function nested()
{
}
class C
{
}
class D extends A
{
}
}
foo();
?>
--EXPECTF--
<!-- declared function 'foo' -->
<!-- declared class 'a' -->
<!-- declared class 'b' -->
<!-- init '%s' -->
<file '%s'>
<!-- init time() -->
<time>
</time>
<!-- declared function 'nested' -->
<!-- declared class 'c' -->
<!-- declared class 'd' -->
<!-- init foo() -->
<foo>
foo
</foo>
</file '%s'>