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
This commit is contained in:
Sammy Kaye Powers 2020-09-24 12:57:43 -07:00
parent a91cb2f48c
commit e42abeafec
No known key found for this signature in database
GPG key ID: B1A4FBE6EEB0FBA6
4 changed files with 139 additions and 6 deletions

View file

@ -89,9 +89,10 @@ ZEND_API void zend_observer_shutdown(void) {
zend_llist_destroy(&zend_observer_error_callbacks);
}
static void zend_observer_fcall_install(zend_function *function) {
static void zend_observer_fcall_install(zend_execute_data *execute_data) {
zend_llist_element *element;
zend_llist *list = &zend_observers_fcall_list;
zend_function *function = execute_data->func;
zend_op_array *op_array = &function->op_array;
if (fcall_handlers_arena == NULL) {
@ -105,7 +106,7 @@ static void zend_observer_fcall_install(zend_function *function) {
for (element = list->head; element; element = element->next) {
zend_observer_fcall_init init;
memcpy(&init, element->data, sizeof init);
zend_observer_fcall_handlers handlers = init(function);
zend_observer_fcall_handlers handlers = init(execute_data);
if (handlers.begin || handlers.end) {
zend_llist_add_element(&handlers_list, &handlers);
}
@ -150,7 +151,7 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
fcall_data = ZEND_OBSERVER_DATA(op_array);
if (!fcall_data) {
zend_observer_fcall_install((zend_function *)op_array);
zend_observer_fcall_install(execute_data);
fcall_data = ZEND_OBSERVER_DATA(op_array);
}

View file

@ -50,7 +50,7 @@ typedef struct _zend_observer_fcall_handlers {
} zend_observer_fcall_handlers;
/* If the fn should not be observed then return {NULL, NULL} */
typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_function *func);
typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_data *execute_data);
// Call during minit/startup ONLY
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);

View file

@ -36,6 +36,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
int observer_observe_functions;
int observer_show_return_type;
int observer_show_return_value;
int observer_show_init_backtrace;
int observer_nesting_depth;
ZEND_END_MODULE_GLOBALS(zend_test)
@ -315,9 +316,10 @@ PHP_INI_BEGIN()
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.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)
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)
PHP_INI_END()
static zend_observer_fcall_handlers observer_fcall_init(zend_function *fbc);
static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
PHP_MINIT_FUNCTION(zend_test)
{
@ -498,10 +500,34 @@ static void observer_show_init(zend_function *fbc)
}
}
static zend_observer_fcall_handlers observer_fcall_init(zend_function *fbc)
static void observer_show_init_backtrace(zend_execute_data *execute_data)
{
zend_execute_data *ex = execute_data;
php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
do {
zend_function *fbc = ex->func;
int indent = 2 * ZT_G(observer_nesting_depth) + 4;
if (fbc->common.function_name) {
if (fbc->common.scope) {
php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
} else {
php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
}
} else {
php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
}
} while ((ex = ex->prev_execute_data) != NULL);
php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
}
static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
{
zend_function *fbc = execute_data->func;
if (ZT_G(observer_show_output)) {
observer_show_init(fbc);
if (ZT_G(observer_show_init_backtrace)) {
observer_show_init_backtrace(execute_data);
}
}
if (ZT_G(observer_observe_all)) {

View file

@ -0,0 +1,106 @@
--TEST--
Observer: Show backtrace on init
--SKIPIF--
<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
--INI--
zend_test.observer.enabled=1
zend_test.observer.observe_all=1
zend_test.observer.show_init_backtrace=1
--FILE--
<?php
class TestClass
{
private function bar($number)
{
return $number + 2;
}
public function foo()
{
return array_map(function ($value) {
return $this->bar($value);
}, [40, 1335]);
}
}
function gen()
{
$test = new TestClass();
yield $test->foo();
}
function foo()
{
return gen()->current();
}
var_dump(foo());
?>
--EXPECTF--
<!-- init '%s/observer_backtrace_%d.php' -->
<!--
{main} %s/observer_backtrace_%d.php
-->
<file '%s/observer_backtrace_%d.php'>
<!-- init foo() -->
<!--
foo()
{main} %s/observer_backtrace_%d.php
-->
<foo>
<!-- init gen() -->
<!--
gen()
Generator::current()
foo()
{main} %s/observer_backtrace_%d.php
-->
<gen>
<!-- init TestClass::foo() -->
<!--
TestClass::foo()
gen()
Generator::current()
foo()
{main} %s/observer_backtrace_%d.php
-->
<TestClass::foo>
<!-- init TestClass::{closure}() -->
<!--
TestClass::{closure}()
array_map()
TestClass::foo()
gen()
Generator::current()
foo()
{main} %s/observer_backtrace_%d.php
-->
<TestClass::{closure}>
<!-- init TestClass::bar() -->
<!--
TestClass::bar()
TestClass::{closure}()
array_map()
TestClass::foo()
gen()
Generator::current()
foo()
{main} %s/observer_backtrace_%d.php
-->
<TestClass::bar>
</TestClass::bar>
</TestClass::{closure}>
<TestClass::{closure}>
<TestClass::bar>
</TestClass::bar>
</TestClass::{closure}>
</TestClass::foo>
</gen>
</foo>
array(2) {
[0]=>
int(42)
[1]=>
int(1337)
}
</file '%s/observer_backtrace_%d.php'>