From 0425a6697a21327880e36975c733bba2b6a6890a Mon Sep 17 00:00:00 2001 From: Sammy Kaye Powers Date: Fri, 23 Oct 2020 11:43:31 -0700 Subject: [PATCH] Fire open observer end handlers after a zend_bailout Closes GH-6377 --- Zend/zend_observer.c | 28 ++++++++++++- Zend/zend_observer.h | 2 + ext/zend_test/tests/observer_error_01.phpt | 29 ++++++++++++++ ext/zend_test/tests/observer_error_02.phpt | 28 +++++++++++++ ext/zend_test/tests/observer_error_03.phpt | 39 ++++++++++++++++++ ext/zend_test/tests/observer_error_04.phpt | 46 ++++++++++++++++++++++ main/main.c | 5 +++ 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 ext/zend_test/tests/observer_error_01.phpt create mode 100644 ext/zend_test/tests/observer_error_02.phpt create mode 100644 ext/zend_test/tests/observer_error_03.phpt create mode 100644 ext/zend_test/tests/observer_error_04.phpt diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index 9c2d1cdf51c..a8ce1eb5c05 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -44,11 +44,13 @@ zend_llist zend_observer_error_callbacks; int zend_observer_fcall_op_array_extension = -1; ZEND_TLS zend_arena *fcall_handlers_arena = NULL; +ZEND_TLS zend_execute_data *first_observed_frame = NULL; +ZEND_TLS zend_execute_data *current_observed_frame = NULL; // Call during minit/startup ONLY ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) { - /* We don't want to get an extension handle unless an ext installs an observer */ if (!ZEND_OBSERVER_ENABLED) { + /* We don't want to get an extension handle unless an ext installs an observer */ zend_observer_fcall_op_array_extension = zend_get_op_array_extension_handle("Zend Observer"); @@ -160,6 +162,11 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d return; } + if (first_observed_frame == NULL) { + first_observed_frame = execute_data; + } + current_observed_frame = execute_data; + end = fcall_data->end; for (handlers = fcall_data->handlers; handlers != end; ++handlers) { if (handlers->begin) { @@ -208,6 +215,25 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end( handlers->end(execute_data, return_value); } } + + if (first_observed_frame == execute_data) { + first_observed_frame = NULL; + current_observed_frame = NULL; + } else { + current_observed_frame = execute_data->prev_execute_data; + } +} + +ZEND_API void zend_observer_fcall_end_all(void) +{ + zend_execute_data *ex = current_observed_frame; + while (ex != NULL) { + if (ex->func->type != ZEND_INTERNAL_FUNCTION) { + zend_observer_fcall_end(ex, NULL); + } + ex = ex->prev_execute_data; + } + current_observed_frame = NULL; } ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index 1d20306a170..cb29729ec45 100644 --- a/Zend/zend_observer.h +++ b/Zend/zend_observer.h @@ -70,6 +70,8 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end( zend_execute_data *execute_data, zval *return_value); +ZEND_API void zend_observer_fcall_end_all(void); + typedef void (*zend_observer_error_cb)(int type, const char *error_filename, uint32_t error_lineno, zend_string *message); ZEND_API void zend_observer_error_register(zend_observer_error_cb callback); diff --git a/ext/zend_test/tests/observer_error_01.phpt b/ext/zend_test/tests/observer_error_01.phpt new file mode 100644 index 00000000000..5ea619f3241 --- /dev/null +++ b/ext/zend_test/tests/observer_error_01.phpt @@ -0,0 +1,29 @@ +--TEST-- +Observer: End handlers fire after a fatal error +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_return_value=1 +memory_limit=1M +--FILE-- + +--EXPECTF-- + + + + + +Fatal error: Allowed memory size of 2097152 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d + + diff --git a/ext/zend_test/tests/observer_error_02.phpt b/ext/zend_test/tests/observer_error_02.phpt new file mode 100644 index 00000000000..959544e9b8e --- /dev/null +++ b/ext/zend_test/tests/observer_error_02.phpt @@ -0,0 +1,28 @@ +--TEST-- +Observer: End handlers fire after a userland fatal error +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_return_value=1 +--FILE-- + +--EXPECTF-- + + + + + +Fatal error: Foo error in %s on line %d + + diff --git a/ext/zend_test/tests/observer_error_03.phpt b/ext/zend_test/tests/observer_error_03.phpt new file mode 100644 index 00000000000..3d8150a4407 --- /dev/null +++ b/ext/zend_test/tests/observer_error_03.phpt @@ -0,0 +1,39 @@ +--TEST-- +Observer: non-fatal errors do not fire end handlers prematurely +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_return_value=1 +--FILE-- + +--EXPECTF-- + + + +
+ + + +Warning: Undefined variable $this_does_not_exit in %s on line %d + +After error. + +Done. + diff --git a/ext/zend_test/tests/observer_error_04.phpt b/ext/zend_test/tests/observer_error_04.phpt new file mode 100644 index 00000000000..ca2532a06ba --- /dev/null +++ b/ext/zend_test/tests/observer_error_04.phpt @@ -0,0 +1,46 @@ +--TEST-- +Observer: fatal errors caught with zend_try will not fire end handlers prematurely +--SKIPIF-- + + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_return_value=1 +--FILE-- +getMessage() . PHP_EOL; +} + +echo 'Done.' . PHP_EOL; +?> +--EXPECTF-- + + + +
+ + + + + + +SOAP-ERROR: Parsing WSDL: Couldn't load from 'foo' : failed to load external entity "foo" + +Done. + diff --git a/main/main.c b/main/main.c index 60fdb89efe4..d2d19a5ee82 100644 --- a/main/main.c +++ b/main/main.c @@ -1740,6 +1740,11 @@ void php_request_shutdown(void *dummy) php_deactivate_ticks(); + /* 0. Call any open observer end handlers that are still open after a zend_bailout */ + if (ZEND_OBSERVER_ENABLED) { + zend_observer_fcall_end_all(); + } + /* 1. Call all possible shutdown functions registered with register_shutdown_function() */ if (PG(modules_activated)) { php_call_shutdown_functions();