Skip to content

Commit e42abea

Browse files
committed
Pass zend_execute_data instead of zend_function to fcall init
The motivation for this change is to prevent extensions from having to check executor globals for the current execute_data during function call init. A previous implementation of the observer API initialized the function call from runtime cache initialization before execute_data was allocated which is why zend_function was passed in. But now that the observer API is implemented via opcode specialization, it makes sense to pass in the execute_data. This also keeps the API a bit more consistent for existing extensions that already hook zend_execute_ex. Closes GH-6209
1 parent a91cb2f commit e42abea

File tree

4 files changed

+139
-6
lines changed

4 files changed

+139
-6
lines changed

Zend/zend_observer.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ ZEND_API void zend_observer_shutdown(void) {
8989
zend_llist_destroy(&zend_observer_error_callbacks);
9090
}
9191

92-
static void zend_observer_fcall_install(zend_function *function) {
92+
static void zend_observer_fcall_install(zend_execute_data *execute_data) {
9393
zend_llist_element *element;
9494
zend_llist *list = &zend_observers_fcall_list;
95+
zend_function *function = execute_data->func;
9596
zend_op_array *op_array = &function->op_array;
9697

9798
if (fcall_handlers_arena == NULL) {
@@ -105,7 +106,7 @@ static void zend_observer_fcall_install(zend_function *function) {
105106
for (element = list->head; element; element = element->next) {
106107
zend_observer_fcall_init init;
107108
memcpy(&init, element->data, sizeof init);
108-
zend_observer_fcall_handlers handlers = init(function);
109+
zend_observer_fcall_handlers handlers = init(execute_data);
109110
if (handlers.begin || handlers.end) {
110111
zend_llist_add_element(&handlers_list, &handlers);
111112
}
@@ -150,7 +151,7 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
150151

151152
fcall_data = ZEND_OBSERVER_DATA(op_array);
152153
if (!fcall_data) {
153-
zend_observer_fcall_install((zend_function *)op_array);
154+
zend_observer_fcall_install(execute_data);
154155
fcall_data = ZEND_OBSERVER_DATA(op_array);
155156
}
156157

Zend/zend_observer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ typedef struct _zend_observer_fcall_handlers {
5050
} zend_observer_fcall_handlers;
5151

5252
/* If the fn should not be observed then return {NULL, NULL} */
53-
typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_function *func);
53+
typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_data *execute_data);
5454

5555
// Call during minit/startup ONLY
5656
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);

ext/zend_test/test.c

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
3636
int observer_observe_functions;
3737
int observer_show_return_type;
3838
int observer_show_return_value;
39+
int observer_show_init_backtrace;
3940
int observer_nesting_depth;
4041
ZEND_END_MODULE_GLOBALS(zend_test)
4142

@@ -315,9 +316,10 @@ PHP_INI_BEGIN()
315316
STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
316317
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)
317318
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)
319+
STD_PHP_INI_BOOLEAN("zend_test.observer.show_init_backtrace", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_init_backtrace, zend_zend_test_globals, zend_test_globals)
318320
PHP_INI_END()
319321

320-
static zend_observer_fcall_handlers observer_fcall_init(zend_function *fbc);
322+
static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
321323

322324
PHP_MINIT_FUNCTION(zend_test)
323325
{
@@ -498,10 +500,34 @@ static void observer_show_init(zend_function *fbc)
498500
}
499501
}
500502

501-
static zend_observer_fcall_handlers observer_fcall_init(zend_function *fbc)
503+
static void observer_show_init_backtrace(zend_execute_data *execute_data)
502504
{
505+
zend_execute_data *ex = execute_data;
506+
php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
507+
do {
508+
zend_function *fbc = ex->func;
509+
int indent = 2 * ZT_G(observer_nesting_depth) + 4;
510+
if (fbc->common.function_name) {
511+
if (fbc->common.scope) {
512+
php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
513+
} else {
514+
php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
515+
}
516+
} else {
517+
php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
518+
}
519+
} while ((ex = ex->prev_execute_data) != NULL);
520+
php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
521+
}
522+
523+
static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
524+
{
525+
zend_function *fbc = execute_data->func;
503526
if (ZT_G(observer_show_output)) {
504527
observer_show_init(fbc);
528+
if (ZT_G(observer_show_init_backtrace)) {
529+
observer_show_init_backtrace(execute_data);
530+
}
505531
}
506532

507533
if (ZT_G(observer_observe_all)) {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
--TEST--
2+
Observer: Show backtrace on init
3+
--SKIPIF--
4+
<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
5+
--INI--
6+
zend_test.observer.enabled=1
7+
zend_test.observer.observe_all=1
8+
zend_test.observer.show_init_backtrace=1
9+
--FILE--
10+
<?php
11+
class TestClass
12+
{
13+
private function bar($number)
14+
{
15+
return $number + 2;
16+
}
17+
18+
public function foo()
19+
{
20+
return array_map(function ($value) {
21+
return $this->bar($value);
22+
}, [40, 1335]);
23+
}
24+
}
25+
26+
function gen()
27+
{
28+
$test = new TestClass();
29+
yield $test->foo();
30+
}
31+
32+
function foo()
33+
{
34+
return gen()->current();
35+
}
36+
37+
var_dump(foo());
38+
?>
39+
--EXPECTF--
40+
<!-- init '%s/observer_backtrace_%d.php' -->
41+
<!--
42+
{main} %s/observer_backtrace_%d.php
43+
-->
44+
<file '%s/observer_backtrace_%d.php'>
45+
<!-- init foo() -->
46+
<!--
47+
foo()
48+
{main} %s/observer_backtrace_%d.php
49+
-->
50+
<foo>
51+
<!-- init gen() -->
52+
<!--
53+
gen()
54+
Generator::current()
55+
foo()
56+
{main} %s/observer_backtrace_%d.php
57+
-->
58+
<gen>
59+
<!-- init TestClass::foo() -->
60+
<!--
61+
TestClass::foo()
62+
gen()
63+
Generator::current()
64+
foo()
65+
{main} %s/observer_backtrace_%d.php
66+
-->
67+
<TestClass::foo>
68+
<!-- init TestClass::{closure}() -->
69+
<!--
70+
TestClass::{closure}()
71+
array_map()
72+
TestClass::foo()
73+
gen()
74+
Generator::current()
75+
foo()
76+
{main} %s/observer_backtrace_%d.php
77+
-->
78+
<TestClass::{closure}>
79+
<!-- init TestClass::bar() -->
80+
<!--
81+
TestClass::bar()
82+
TestClass::{closure}()
83+
array_map()
84+
TestClass::foo()
85+
gen()
86+
Generator::current()
87+
foo()
88+
{main} %s/observer_backtrace_%d.php
89+
-->
90+
<TestClass::bar>
91+
</TestClass::bar>
92+
</TestClass::{closure}>
93+
<TestClass::{closure}>
94+
<TestClass::bar>
95+
</TestClass::bar>
96+
</TestClass::{closure}>
97+
</TestClass::foo>
98+
</gen>
99+
</foo>
100+
array(2) {
101+
[0]=>
102+
int(42)
103+
[1]=>
104+
int(1337)
105+
}
106+
</file '%s/observer_backtrace_%d.php'>

0 commit comments

Comments
 (0)