diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c
index 9c2d1cdf51c4e..a8ce1eb5c057f 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 1d20306a17018..cb29729ec45da 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 0000000000000..5ea619f32414c
--- /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..959544e9b8e9a
--- /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..ca2532a06ba9d
--- /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 8a860505f98c6..f4c34366ae906 100644
--- a/main/main.c
+++ b/main/main.c
@@ -1747,6 +1747,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();