Skip to content

Commit bf427b7

Browse files
committed
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>
1 parent 9b984f5 commit bf427b7

11 files changed

+158
-1
lines changed

Zend/zend_API.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "zend_inheritance.h"
3232
#include "zend_ini.h"
3333
#include "zend_enum.h"
34+
#include "zend_observer.h"
3435

3536
#include <stdarg.h>
3637

@@ -3263,6 +3264,7 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_
32633264
if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
32643265
ce->refcount++;
32653266
}
3267+
zend_observer_class_linked_notify(ce, lcname);
32663268
return SUCCESS;
32673269
}
32683270
return FAILURE;

Zend/zend_compile.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ ZEND_API zend_result do_bind_function(zend_function *func, zval *lcname) /* {{{
10931093
if (func->common.function_name) {
10941094
zend_string_addref(func->common.function_name);
10951095
}
1096+
zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname));
10961097
return SUCCESS;
10971098
}
10981099
/* }}} */
@@ -1116,12 +1117,14 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot(
11161117
}
11171118

11181119
if (ce->ce_flags & ZEND_ACC_LINKED) {
1120+
zend_observer_class_linked_notify(ce, Z_STR_P(lcname));
11191121
return ce;
11201122
}
11211123

11221124
ce = zend_do_link_class(ce, lc_parent_name, Z_STR_P(lcname));
11231125
if (ce) {
11241126
ZEND_ASSERT(!EG(exception));
1127+
zend_observer_class_linked_notify(ce, Z_STR_P(lcname));
11251128
return ce;
11261129
}
11271130

@@ -7245,6 +7248,7 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as
72457248
if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) {
72467249
do_bind_function_error(lcname, op_array, 1);
72477250
}
7251+
zend_observer_function_declared_notify(op_array, lcname);
72487252
zend_string_release_ex(lcname, 0);
72497253
return;
72507254
}
@@ -7877,6 +7881,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
78777881
zend_string_release(lcname);
78787882
zend_build_properties_info_table(ce);
78797883
ce->ce_flags |= ZEND_ACC_LINKED;
7884+
zend_observer_class_linked_notify(ce, lcname);
78807885
return;
78817886
}
78827887
} else if (!extends_ast) {

Zend/zend_compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,9 @@ END_EXTERN_C()
11811181
/* this flag is set when compiler invoked during preloading in separate process */
11821182
#define ZEND_COMPILE_PRELOAD_IN_CHILD (1<<17)
11831183

1184+
/* ignore observer notifications, e.g. to manually notify afterwards in a post-processing step after compilation */
1185+
#define ZEND_COMPILE_IGNORE_OBSERVER (1<<18)
1186+
11841187
/* The default value for CG(compiler_options) */
11851188
#define ZEND_COMPILE_DEFAULT ZEND_COMPILE_HANDLE_OP_ARRAY
11861189

Zend/zend_inheritance.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "zend_enum.h"
3030
#include "zend_attributes.h"
3131
#include "zend_constants.h"
32+
#include "zend_observer.h"
3233

3334
ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL;
3435
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
31343135
if (EXPECTED(!(ce->ce_flags & ZEND_ACC_PRELOADED))) {
31353136
if (zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) != NULL) {
31363137
Z_CE_P(delayed_early_binding) = ce;
3138+
zend_observer_class_linked_notify(ce, lcname);
31373139
return true;
31383140
}
31393141
} else {
31403142
/* If preloading is used, don't replace the existing bucket, add a new one. */
31413143
if (zend_hash_add_ptr(EG(class_table), lcname, ce) != NULL) {
3144+
zend_observer_class_linked_notify(ce, lcname);
31423145
return true;
31433146
}
31443147
}
31453148
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));
31463149
return false;
31473150
}
3148-
return zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL;
3151+
if (zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL) {
3152+
zend_observer_class_linked_notify(ce, lcname);
3153+
return true;
3154+
}
3155+
return false;
31493156
}
31503157

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

Zend/zend_observer.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,17 @@
3232
(ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
3333

3434
zend_llist zend_observers_fcall_list;
35+
zend_llist zend_observer_function_declared_callbacks;
36+
zend_llist zend_observer_class_linked_callbacks;
3537
zend_llist zend_observer_error_callbacks;
3638
zend_llist zend_observer_fiber_init;
3739
zend_llist zend_observer_fiber_switch;
3840
zend_llist zend_observer_fiber_destroy;
3941

4042
int zend_observer_fcall_op_array_extension;
43+
bool zend_observer_errors_observed;
44+
bool zend_observer_function_declared_observed;
45+
bool zend_observer_class_linked_observed;
4146

4247
ZEND_TLS zend_execute_data *current_observed_frame;
4348

@@ -51,6 +56,8 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init)
5156
ZEND_API void zend_observer_startup(void)
5257
{
5358
zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1);
59+
zend_llist_init(&zend_observer_function_declared_callbacks, sizeof(zend_observer_function_declared_cb), NULL, 1);
60+
zend_llist_init(&zend_observer_class_linked_callbacks, sizeof(zend_observer_class_linked_cb), NULL, 1);
5461
zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1);
5562
zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1);
5663
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)
100107
ZEND_API void zend_observer_shutdown(void)
101108
{
102109
zend_llist_destroy(&zend_observers_fcall_list);
110+
zend_llist_destroy(&zend_observer_function_declared_callbacks);
111+
zend_llist_destroy(&zend_observer_class_linked_callbacks);
103112
zend_llist_destroy(&zend_observer_error_callbacks);
104113
zend_llist_destroy(&zend_observer_fiber_init);
105114
zend_llist_destroy(&zend_observer_fiber_switch);
@@ -286,8 +295,45 @@ ZEND_API void zend_observer_fcall_end_all(void)
286295
EG(current_execute_data) = original_execute_data;
287296
}
288297

298+
ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb)
299+
{
300+
zend_observer_function_declared_observed = true;
301+
zend_llist_add_element(&zend_observer_function_declared_callbacks, &cb);
302+
}
303+
304+
ZEND_API void ZEND_FASTCALL zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name)
305+
{
306+
if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) {
307+
return;
308+
}
309+
310+
for (zend_llist_element *element = zend_observer_function_declared_callbacks.head; element; element = element->next) {
311+
zend_observer_function_declared_cb callback = *(zend_observer_function_declared_cb *) (element->data);
312+
callback(op_array, name);
313+
}
314+
}
315+
316+
ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb)
317+
{
318+
zend_observer_class_linked_observed = true;
319+
zend_llist_add_element(&zend_observer_class_linked_callbacks, &cb);
320+
}
321+
322+
ZEND_API void ZEND_FASTCALL zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name)
323+
{
324+
if (CG(compiler_options) & ZEND_COMPILE_IGNORE_OBSERVER) {
325+
return;
326+
}
327+
328+
for (zend_llist_element *element = zend_observer_class_linked_callbacks.head; element; element = element->next) {
329+
zend_observer_class_linked_cb callback = *(zend_observer_class_linked_cb *) (element->data);
330+
callback(ce, name);
331+
}
332+
}
333+
289334
ZEND_API void zend_observer_error_register(zend_observer_error_cb cb)
290335
{
336+
zend_observer_errors_observed = true;
291337
zend_llist_add_element(&zend_observer_error_callbacks, &cb);
292338
}
293339

Zend/zend_observer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
BEGIN_EXTERN_C()
2828

2929
extern ZEND_API int zend_observer_fcall_op_array_extension;
30+
extern ZEND_API bool zend_observer_errors_observed;
31+
extern ZEND_API bool zend_observer_function_declared_observed;
32+
extern ZEND_API bool zend_observer_class_linked_observed;
3033

3134
#define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1)
3235

@@ -80,6 +83,14 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(
8083

8184
ZEND_API void zend_observer_fcall_end_all(void);
8285

86+
typedef void (*zend_observer_function_declared_cb)(zend_op_array *op_array, zend_string *name);
87+
typedef void (*zend_observer_class_linked_cb)(zend_class_entry *ce, zend_string *name);
88+
89+
ZEND_API void zend_observer_function_declared_register(zend_observer_function_declared_cb cb);
90+
ZEND_API void ZEND_FASTCALL zend_observer_function_declared_notify(zend_op_array *op_array, zend_string *name);
91+
ZEND_API void zend_observer_class_linked_register(zend_observer_class_linked_cb cb);
92+
ZEND_API void ZEND_FASTCALL zend_observer_class_linked_notify(zend_class_entry *ce, zend_string *name);
93+
8394
typedef void (*zend_observer_error_cb)(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message);
8495

8596
ZEND_API void zend_observer_error_register(zend_observer_error_cb callback);

ext/opcache/ZendAccelerator.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl
18061806
CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING;
18071807
CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION;
18081808
CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES;
1809+
CG(compiler_options) |= ZEND_COMPILE_IGNORE_OBSERVER;
18091810
if (ZCG(accel_directives).file_cache) {
18101811
CG(compiler_options) |= ZEND_COMPILE_WITH_FILE_CACHE;
18111812
}

ext/opcache/zend_accelerator_util_funcs.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "zend_accelerator_util_funcs.h"
2626
#include "zend_persist.h"
2727
#include "zend_shared_alloc.h"
28+
#include "zend_observer.h"
2829

2930
typedef int (*id_function_t)(void *, void *);
3031
typedef void (*unique_copy_ctor_func_t)(void *pElement);
@@ -145,6 +146,8 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source)
145146
Bucket *p, *end;
146147
zval *t;
147148

149+
bool call_observers = zend_observer_function_declared_observed;
150+
148151
zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0);
149152
p = source->arData;
150153
end = p + source->nNumUsed;
@@ -156,6 +159,9 @@ static void zend_accel_function_hash_copy(HashTable *target, HashTable *source)
156159
goto failure;
157160
}
158161
_zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1);
162+
if (UNEXPECTED(call_observers) && *ZSTR_VAL(p->key)) { // if not rtd key
163+
zend_observer_function_declared_notify(Z_PTR(p->val), p->key);
164+
}
159165
}
160166
target->nInternalPointer = 0;
161167
return;
@@ -182,6 +188,8 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source)
182188
Bucket *p, *end;
183189
zval *t;
184190

191+
bool call_observers = zend_observer_class_linked_observed;
192+
185193
zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0);
186194
p = source->arData;
187195
end = p + source->nNumUsed;
@@ -221,6 +229,9 @@ static void zend_accel_class_hash_copy(HashTable *target, HashTable *source)
221229
&& ZSTR_HAS_CE_CACHE(ce->name)
222230
&& ZSTR_VAL(p->key)[0]) {
223231
ZSTR_SET_CE_CACHE_EX(ce->name, ce, 0);
232+
if (UNEXPECTED(call_observers)) {
233+
zend_observer_class_linked_notify(ce, p->key);
234+
}
224235
}
225236
}
226237
}

ext/zend_test/observer.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,18 @@ static void fiber_suspend_observer(zend_fiber_context *from, zend_fiber_context
256256
}
257257
}
258258

259+
void declared_function_observer(zend_op_array *op_array, zend_string *name) {
260+
if (ZT_G(observer_observe_declaring)) {
261+
php_printf("%*s<!-- declared function '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
262+
}
263+
}
264+
265+
void declared_class_observer(zend_class_entry *ce, zend_string *name) {
266+
if (ZT_G(observer_observe_declaring)) {
267+
php_printf("%*s<!-- declared class '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(name));
268+
}
269+
}
270+
259271
static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
260272
{
261273
zend_array **p = (zend_array **) ZEND_INI_GET_ADDR();
@@ -301,6 +313,7 @@ PHP_INI_BEGIN()
301313
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
302314
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
303315
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
316+
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_declaring", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_declaring, zend_zend_test_globals, zend_test_globals)
304317
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)
305318
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)
306319
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)
334347
zend_observer_fiber_switch_register(fiber_enter_observer);
335348
zend_observer_fiber_switch_register(fiber_suspend_observer);
336349
zend_observer_fiber_destroy_register(fiber_destroy_observer);
350+
351+
zend_observer_function_declared_register(declared_function_observer);
352+
zend_observer_class_linked_register(declared_class_observer);
337353
}
338354
}
339355

ext/zend_test/php_test.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
3838
int observer_observe_all;
3939
int observer_observe_includes;
4040
int observer_observe_functions;
41+
int observer_observe_declaring;
4142
zend_array *observer_observe_function_names;
4243
int observer_show_return_type;
4344
int observer_show_return_value;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Observer: Observe function and class declarations
3+
--EXTENSIONS--
4+
zend_test
5+
--INI--
6+
zend_test.observer.enabled=1
7+
zend_test.observer.observe_all=1
8+
zend_test.observer.observe_declaring=1
9+
--FILE--
10+
<?php
11+
function foo()
12+
{
13+
echo "foo\n";
14+
}
15+
16+
class A {
17+
}
18+
19+
class B extends A {
20+
}
21+
22+
if (time() > 0) {
23+
function nested()
24+
{
25+
}
26+
27+
class C
28+
{
29+
}
30+
31+
class D extends A
32+
{
33+
}
34+
}
35+
36+
foo();
37+
?>
38+
--EXPECTF--
39+
<!-- declared function 'foo' -->
40+
<!-- declared class 'a' -->
41+
<!-- declared class 'b' -->
42+
<!-- init '%s' -->
43+
<file '%s'>
44+
<!-- init time() -->
45+
<time>
46+
</time>
47+
<!-- declared function 'nested' -->
48+
<!-- declared class 'c' -->
49+
<!-- declared class 'd' -->
50+
<!-- init foo() -->
51+
<foo>
52+
foo
53+
</foo>
54+
</file '%s'>

0 commit comments

Comments
 (0)