From 3301d9602a2307007858c299c41795d3d95e3843 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 12 Mar 2024 16:32:25 +0100 Subject: [PATCH] Restore error handler after running it Symfony relies on finding the exception handler in the handler stack. There's currently no clean API to find it, so they pop all the handlers, and push them again once the stack is empty. This PR attempts to minimize the BC break by pushing the current handler onto the stack and clearing the current handler, and restoring it once it has finished. This is essentially equivalent to set_exception_handler(null) and restore_exception_handler(). restore_exception_handler() however is only called if the exception handler is still unset. If the handler has pushed a new handler in the meantime, we assume it knows what it's doing. Fixes GH-13446 Closes GH-13686 --- NEWS | 1 + Zend/tests/gh13446_1.phpt | 19 +++++++++++++++++++ Zend/tests/gh13446_2.phpt | 16 ++++++++++++++++ Zend/tests/gh13446_3.phpt | 25 +++++++++++++++++++++++++ Zend/tests/gh13446_4.phpt | 21 +++++++++++++++++++++ Zend/zend.c | 10 +++++++++- 6 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/gh13446_1.phpt create mode 100644 Zend/tests/gh13446_2.phpt create mode 100644 Zend/tests/gh13446_3.phpt create mode 100644 Zend/tests/gh13446_4.phpt diff --git a/NEWS b/NEWS index 14d9aa5ef6f..bc19e391603 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ PHP NEWS scanning WeakMaps). (Arnaud) . Fixed bug GH-13612 (Corrupted memory in destructor with weak references). (nielsdos) + . Fixed bug GH-13446 (Restore exception handler after it finishes). (ilutov) - DOM: . Add some missing ZPP checks. (nielsdos) diff --git a/Zend/tests/gh13446_1.phpt b/Zend/tests/gh13446_1.phpt new file mode 100644 index 00000000000..cb51d041c96 --- /dev/null +++ b/Zend/tests/gh13446_1.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-13446: Exception handler is restored after is has finished +--FILE-- +getMessage(), "\n"; +} +set_exception_handler('exception_handler'); + +register_shutdown_function(function () { + echo set_exception_handler(null), "\n"; + restore_exception_handler(); +}); + +throw new Exception('Test'); +?> +--EXPECT-- +Exception caught: Test +exception_handler diff --git a/Zend/tests/gh13446_2.phpt b/Zend/tests/gh13446_2.phpt new file mode 100644 index 00000000000..c1bab44ce80 --- /dev/null +++ b/Zend/tests/gh13446_2.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-13446: Exception handler attempting to free itself +--FILE-- + +--EXPECT-- +object(stdClass)#1 (0) { +} diff --git a/Zend/tests/gh13446_3.phpt b/Zend/tests/gh13446_3.phpt new file mode 100644 index 00000000000..eb56eb1b32d --- /dev/null +++ b/Zend/tests/gh13446_3.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-13446: Exception handler isn't restored if it is previously modified +--FILE-- + +--EXPECT-- +Handler 1 +exception_handler_2 diff --git a/Zend/tests/gh13446_4.phpt b/Zend/tests/gh13446_4.phpt new file mode 100644 index 00000000000..634d16f38a0 --- /dev/null +++ b/Zend/tests/gh13446_4.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-13446: Exception handler isn't restored if stack is empty +--FILE-- + +--EXPECT-- +Handler +NULL diff --git a/Zend/zend.c b/Zend/zend.c index d154c67382a..28922647bba 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1839,7 +1839,9 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */ old_exception = EG(exception); EG(exception) = NULL; ZVAL_OBJ(¶ms[0], old_exception); + ZVAL_COPY_VALUE(&orig_user_exception_handler, &EG(user_exception_handler)); + zend_stack_push(&EG(user_exception_handlers), &orig_user_exception_handler); ZVAL_UNDEF(&EG(user_exception_handler)); if (call_user_function(CG(function_table), NULL, &orig_user_exception_handler, &retval2, 1, params) == SUCCESS) { @@ -1853,7 +1855,13 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */ EG(exception) = old_exception; } - zval_ptr_dtor(&orig_user_exception_handler); + if (Z_TYPE(EG(user_exception_handler)) == IS_UNDEF) { + zval *tmp = zend_stack_top(&EG(user_exception_handlers)); + if (tmp) { + ZVAL_COPY_VALUE(&EG(user_exception_handler), tmp); + zend_stack_del_top(&EG(user_exception_handlers)); + } + } } /* }}} */ ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */