diff --git a/Zend/zend.c b/Zend/zend.c index cbd5aef42b9fe..4916c6bb05994 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1323,6 +1323,21 @@ static ZEND_COLD void zend_error_impl( zend_observer_error_notify(type, error_filename, error_lineno, message); + /* Call all the observer end handlers for fatal errors */ + if (ZEND_OBSERVER_ENABLED && (type & E_FATAL_ERRORS) && EG(current_execute_data)) { + zend_execute_data *ex = EG(current_execute_data); + do { + if (ex->func->type != ZEND_INTERNAL_FUNCTION) { + zend_observer_fcall_end(ex, NULL); + /* If an extension catches a fatal error and raises another one, the + * observer end handlers will fire more than once. This requires us + * to "unobserve" the functions after the end handlers have fired the + * first time. */ + zend_observer_fcall_unobserve(ex->func); + } + } while ((ex = ex->prev_execute_data) != NULL); + } + /* if we don't have a user defined error handler */ if (Z_TYPE(EG(user_error_handler)) == IS_UNDEF || !(EG(user_error_handler_error_reporting) & type) diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index 9c2d1cdf51c4e..ac8653f931a9c 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -210,6 +210,15 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end( } } +ZEND_API void zend_observer_fcall_unobserve(zend_function *func) +{ + if (ZEND_OBSERVER_ENABLED + && ZEND_OBSERVABLE_FN(func->common.fn_flags) + && ZEND_OBSERVER_DATA(&func->op_array)) { + ZEND_OBSERVER_DATA(&func->op_array) = ZEND_OBSERVER_NOT_OBSERVED; + } +} + ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) { zend_llist_add_element(&zend_observer_error_callbacks, &cb); diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index 1d20306a17018..997d8a25dd14c 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_unobserve(zend_function *func); + 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_call_user_func_01.phpt b/ext/zend_test/tests/observer_call_user_func_01.phpt new file mode 100644 index 0000000000000..0f12fa6835f00 --- /dev/null +++ b/ext/zend_test/tests/observer_call_user_func_01.phpt @@ -0,0 +1,40 @@ +--TEST-- +Observer: call_user_func() from root namespace +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + +MyClass::myMethod called + + + +my_function called + + diff --git a/ext/zend_test/tests/observer_call_user_func_02.phpt b/ext/zend_test/tests/observer_call_user_func_02.phpt new file mode 100644 index 0000000000000..28dd6a9825440 --- /dev/null +++ b/ext/zend_test/tests/observer_call_user_func_02.phpt @@ -0,0 +1,40 @@ +--TEST-- +Observer: call_user_func_array() from root namespace +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + +MyClass::myMethod called + + + +my_function called + + diff --git a/ext/zend_test/tests/observer_call_user_func_03.phpt b/ext/zend_test/tests/observer_call_user_func_03.phpt new file mode 100644 index 0000000000000..1ff841d4340d5 --- /dev/null +++ b/ext/zend_test/tests/observer_call_user_func_03.phpt @@ -0,0 +1,39 @@ +--TEST-- +Observer: call_user_func() from namespace +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + +MyClass::myMethod called + + + +my_function called + + diff --git a/ext/zend_test/tests/observer_call_user_func_04.phpt b/ext/zend_test/tests/observer_call_user_func_04.phpt new file mode 100644 index 0000000000000..9df131db4dd0d --- /dev/null +++ b/ext/zend_test/tests/observer_call_user_func_04.phpt @@ -0,0 +1,39 @@ +--TEST-- +Observer: call_user_func_array() from namespace +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + +MyClass::myMethod called + + + +my_function called + + 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 0000000000000..08de38f601156 --- /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 0000000000000..70e2a57a50f9c --- /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 0000000000000..3d8150a440754 --- /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 0000000000000..8aea004187e80 --- /dev/null +++ b/ext/zend_test/tests/observer_error_04.phpt @@ -0,0 +1,44 @@ +--TEST-- +Observer: fatal errors caught with zend_try will fire end handlers once +--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/ext/zend_test/tests/observer_generator_05.phpt b/ext/zend_test/tests/observer_generator_05.phpt new file mode 100644 index 0000000000000..d13f6fa39b198 --- /dev/null +++ b/ext/zend_test/tests/observer_generator_05.phpt @@ -0,0 +1,53 @@ +--TEST-- +Observer: Generator with uncaught exception +--SKIPIF-- + +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +zend_test.observer.show_return_value=1 +--FILE-- + +--EXPECTF-- + + + + + + + +0 + + +1 + + + + + + + + +Fatal error: Uncaught RuntimeException: Oops! in %s/observer_generator_%d.php:%d +Stack trace: +#0 %s/observer_generator_%d.php(%d): fooResults() +#1 %s/observer_generator_%d.php(%d): doSomething() +#2 {main} + thrown in %s/observer_generator_%d.php on line %d