Skip to content

Commit 66c3e90

Browse files
morrisonlevinikicSammyK
committed
Add zend_observer API
Closes GH-5857. Co-authored-by: Nikita Popov <[email protected]> Co-authored-by: Sammy Powers <[email protected]>
1 parent bd8e0a9 commit 66c3e90

32 files changed

+4604
-1385
lines changed

Zend/zend.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "zend_smart_string.h"
3434
#include "zend_cpuinfo.h"
3535
#include "zend_attributes.h"
36+
#include "zend_observer.h"
3637

3738
static size_t global_map_ptr_last = 0;
3839

@@ -1205,6 +1206,7 @@ ZEND_API void zend_activate(void) /* {{{ */
12051206
if (CG(map_ptr_last)) {
12061207
memset(ZEND_MAP_PTR_REAL_BASE(CG(map_ptr_base)), 0, CG(map_ptr_last) * sizeof(void*));
12071208
}
1209+
zend_observer_activate();
12081210
}
12091211
/* }}} */
12101212

@@ -1221,6 +1223,8 @@ ZEND_API void zend_deactivate(void) /* {{{ */
12211223
/* we're no longer executing anything */
12221224
EG(current_execute_data) = NULL;
12231225

1226+
zend_observer_deactivate();
1227+
12241228
zend_try {
12251229
shutdown_scanner();
12261230
} zend_end_try();

Zend/zend_execute.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "zend_inheritance.h"
4141
#include "zend_type_info.h"
4242
#include "zend_smart_str.h"
43+
#include "zend_observer.h"
4344

4445
/* Virtual current working directory support */
4546
#include "zend_virtual_cwd.h"

Zend/zend_execute_API.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "zend_float.h"
3636
#include "zend_weakrefs.h"
3737
#include "zend_inheritance.h"
38+
#include "zend_observer.h"
3839
#ifdef HAVE_SYS_TIME_H
3940
#include <sys/time.h>
4041
#endif
@@ -869,6 +870,9 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
869870
uint32_t orig_jit_trace_num = EG(jit_trace_num);
870871

871872
zend_init_func_execute_data(call, &func->op_array, fci->retval);
873+
if (ZEND_OBSERVER_ENABLED) {
874+
zend_observer_maybe_fcall_call_begin(call);
875+
}
872876
zend_execute_ex(call);
873877
EG(jit_trace_num) = orig_jit_trace_num;
874878
EG(opline_before_exception) = current_opline_before_exception;

Zend/zend_generators.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "zend_generators.h"
2525
#include "zend_closures.h"
2626
#include "zend_generators_arginfo.h"
27+
#include "zend_observer.h"
2728

2829
ZEND_API zend_class_entry *zend_ce_generator;
2930
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
@@ -774,7 +775,25 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
774775

775776
/* Resume execution */
776777
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
777-
zend_execute_ex(generator->execute_data);
778+
if (!ZEND_OBSERVER_ENABLED) {
779+
zend_execute_ex(generator->execute_data);
780+
} else {
781+
zend_op_array *op_array = &generator->execute_data->func->op_array;
782+
void *observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
783+
if (!observer_handlers) {
784+
zend_observer_fcall_install((zend_function *)op_array);
785+
observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
786+
}
787+
ZEND_ASSERT(observer_handlers);
788+
if (observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
789+
zend_observe_fcall_begin(observer_handlers, generator->execute_data);
790+
}
791+
zend_execute_ex(generator->execute_data);
792+
if (generator->execute_data) {
793+
/* On the final return, this will be called from ZEND_GENERATOR_RETURN */
794+
zend_observer_maybe_fcall_call_end(generator->execute_data, &generator->value);
795+
}
796+
}
778797
generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
779798

780799
generator->frozen_call_stack = NULL;

Zend/zend_observer.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Zend Engine |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 2.00 of the Zend license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.zend.com/license/2_00.txt. |
11+
| If you did not receive a copy of the Zend license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| [email protected] so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Levi Morrison <[email protected]> |
16+
| Sammy Kaye Powers <[email protected]> |
17+
+----------------------------------------------------------------------+
18+
*/
19+
20+
#include "zend_observer.h"
21+
22+
#include "zend_extensions.h"
23+
#include "zend_llist.h"
24+
#include "zend_vm.h"
25+
26+
zend_llist zend_observers_fcall_list;
27+
int zend_observer_fcall_op_array_extension = -1;
28+
29+
ZEND_TLS zend_arena *fcall_handlers_arena = NULL;
30+
31+
ZEND_API extern inline void zend_observer_maybe_fcall_call_begin(
32+
zend_execute_data *execute_data);
33+
ZEND_API extern inline void zend_observer_maybe_fcall_call_end(
34+
zend_execute_data *execute_data,
35+
zval *return_value);
36+
37+
// Call during minit/startup ONLY
38+
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) {
39+
/* We don't want to get an extension handle unless an ext installs an observer */
40+
if (!ZEND_OBSERVER_ENABLED) {
41+
zend_observer_fcall_op_array_extension =
42+
zend_get_op_array_extension_handle();
43+
44+
/* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op()
45+
* is called before any extensions have registered as an observer. So we
46+
* adjust the offset to the observed handler when we know we need to observe. */
47+
ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op));
48+
49+
/* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions
50+
* exist when zend_init_exception_op() is called. */
51+
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op));
52+
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+1);
53+
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+2);
54+
}
55+
zend_llist_add_element(&zend_observers_fcall_list, &init);
56+
}
57+
58+
// Called by engine before MINITs
59+
ZEND_API void zend_observer_startup(void) {
60+
zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1);
61+
}
62+
63+
ZEND_API void zend_observer_activate(void) {
64+
if (ZEND_OBSERVER_ENABLED) {
65+
fcall_handlers_arena = zend_arena_create(4096);
66+
}
67+
}
68+
69+
ZEND_API void zend_observer_deactivate(void) {
70+
if (fcall_handlers_arena) {
71+
zend_arena_destroy(fcall_handlers_arena);
72+
}
73+
}
74+
75+
ZEND_API void zend_observer_shutdown(void) {
76+
zend_llist_destroy(&zend_observers_fcall_list);
77+
}
78+
79+
ZEND_API void zend_observer_fcall_install(zend_function *function) {
80+
zend_llist_element *element;
81+
zend_llist *list = &zend_observers_fcall_list;
82+
zend_op_array *op_array = &function->op_array;
83+
84+
if (fcall_handlers_arena == NULL) {
85+
return;
86+
}
87+
88+
ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);
89+
90+
zend_llist handlers_list;
91+
zend_llist_init(&handlers_list, sizeof(zend_observer_fcall), NULL, 0);
92+
for (element = list->head; element; element = element->next) {
93+
zend_observer_fcall_init init;
94+
memcpy(&init, element->data, sizeof init);
95+
zend_observer_fcall handlers = init(function);
96+
if (handlers.begin || handlers.end) {
97+
zend_llist_add_element(&handlers_list, &handlers);
98+
}
99+
}
100+
101+
ZEND_ASSERT(RUN_TIME_CACHE(op_array));
102+
void *ext;
103+
if (handlers_list.count) {
104+
size_t size = sizeof(zend_observer_fcall_cache) + (handlers_list.count - 1) * sizeof(zend_observer_fcall);
105+
zend_observer_fcall_cache *cache = zend_arena_alloc(&fcall_handlers_arena, size);
106+
zend_observer_fcall *handler = cache->handlers;
107+
for (element = handlers_list.head; element; element = element->next) {
108+
memcpy(handler++, element->data, sizeof *handler);
109+
}
110+
cache->end = handler;
111+
ext = cache;
112+
} else {
113+
ext = ZEND_OBSERVER_NOT_OBSERVED;
114+
}
115+
116+
ZEND_OBSERVER_HANDLERS(op_array) = ext;
117+
zend_llist_destroy(&handlers_list);
118+
}
119+
120+
ZEND_API void zend_observe_fcall_begin(
121+
zend_observer_fcall_cache *cache,
122+
zend_execute_data *execute_data)
123+
{
124+
zend_observer_fcall *handler, *end = cache->end;
125+
for (handler = cache->handlers; handler != end; ++handler) {
126+
if (handler->begin) {
127+
handler->begin(execute_data);
128+
}
129+
}
130+
}
131+
132+
ZEND_API void zend_observer_fcall_call_end_helper(
133+
zend_execute_data *execute_data,
134+
zval *return_value)
135+
{
136+
zend_function *func = execute_data->func;
137+
ZEND_ASSUME(ZEND_OBSERVABLE_FN(func->common.fn_flags));
138+
void *observer_handlers = ZEND_OBSERVER_HANDLERS(&func->op_array);
139+
// TODO: Fix exceptions from generators
140+
// ZEND_ASSERT(observer_handlers);
141+
if (observer_handlers && observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
142+
zend_observer_fcall_cache *cache = observer_handlers;
143+
zend_observe_fcall_end(cache, execute_data, return_value);
144+
}
145+
}
146+
147+
ZEND_API void zend_observe_fcall_end(
148+
zend_observer_fcall_cache *cache,
149+
zend_execute_data *execute_data,
150+
zval *return_value)
151+
{
152+
zend_observer_fcall *handler = cache->end, *end = cache->handlers;
153+
while (handler-- != end) {
154+
if (handler->end) {
155+
handler->end(execute_data, return_value);
156+
}
157+
}
158+
}
159+
160+

Zend/zend_observer.h

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Zend Engine |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 2.00 of the Zend license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.zend.com/license/2_00.txt. |
11+
| If you did not receive a copy of the Zend license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| [email protected] so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Levi Morrison <[email protected]> |
16+
| Sammy Kaye Powers <[email protected]> |
17+
+----------------------------------------------------------------------+
18+
*/
19+
20+
#ifndef ZEND_OBSERVER_H
21+
#define ZEND_OBSERVER_H
22+
23+
#include "zend.h"
24+
#include "zend_compile.h"
25+
26+
BEGIN_EXTERN_C()
27+
28+
extern ZEND_API int zend_observer_fcall_op_array_extension;
29+
30+
#define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1)
31+
32+
#define ZEND_OBSERVER_HANDLERS(op_array) \
33+
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)
34+
35+
#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)
36+
37+
#define ZEND_OBSERVABLE_FN(fn_flags) \
38+
(ZEND_OBSERVER_ENABLED && \
39+
!(fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_FAKE_CLOSURE)))
40+
41+
struct zend_observer_fcall {
42+
void (*begin)(zend_execute_data *execute_data);
43+
void (*end)(zend_execute_data *execute_data, zval *retval);
44+
};
45+
typedef struct zend_observer_fcall zend_observer_fcall;
46+
47+
struct zend_observer_fcall_cache {
48+
// points after the last handler
49+
zend_observer_fcall *end;
50+
// a variadic array using "struct hack"
51+
zend_observer_fcall handlers[1];
52+
};
53+
typedef struct zend_observer_fcall_cache zend_observer_fcall_cache;
54+
55+
/* If the fn should not be observed then return {NULL, NULL} */
56+
typedef zend_observer_fcall(*zend_observer_fcall_init)(zend_function *func);
57+
58+
// Call during minit/startup ONLY
59+
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init);
60+
61+
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
62+
ZEND_API void zend_observer_activate(void);
63+
ZEND_API void zend_observer_deactivate(void);
64+
ZEND_API void zend_observer_shutdown(void);
65+
66+
ZEND_API void zend_observer_fcall_install(zend_function *function);
67+
68+
ZEND_API void zend_observe_fcall_begin(
69+
zend_observer_fcall_cache *cache,
70+
zend_execute_data *execute_data);
71+
72+
ZEND_API void zend_observe_fcall_end(
73+
zend_observer_fcall_cache *cache,
74+
zend_execute_data *execute_data,
75+
zval *return_value);
76+
77+
ZEND_API void zend_observer_fcall_call_end_helper(
78+
zend_execute_data *execute_data,
79+
zval *return_value);
80+
81+
ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_begin(
82+
zend_execute_data *execute_data)
83+
{
84+
ZEND_ASSUME(execute_data->func);
85+
zend_op_array *op_array = &execute_data->func->op_array;
86+
uint32_t fn_flags = op_array->fn_flags;
87+
if (ZEND_OBSERVABLE_FN(fn_flags) && !(fn_flags & ZEND_ACC_GENERATOR)) {
88+
void *observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
89+
if (!observer_handlers) {
90+
zend_observer_fcall_install((zend_function *)op_array);
91+
observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
92+
}
93+
94+
ZEND_ASSERT(observer_handlers);
95+
if (observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
96+
zend_observe_fcall_begin(
97+
(zend_observer_fcall_cache *)observer_handlers,
98+
execute_data);
99+
}
100+
}
101+
}
102+
103+
ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_end(
104+
zend_execute_data *execute_data,
105+
zval *return_value)
106+
{
107+
zend_function *func = execute_data->func;
108+
if (ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
109+
zend_observer_fcall_call_end_helper(execute_data, return_value);
110+
}
111+
}
112+
113+
END_EXTERN_C()
114+
115+
#endif /* ZEND_OBSERVER_H */

0 commit comments

Comments
 (0)