mirror of
https://github.com/php/php-src.git
synced 2024-09-21 09:57:23 +00:00
Forward shutdown exceptions to user error handlers
Fixes GH-10695 Closes GH-110905
This commit is contained in:
parent
96ea06a1d9
commit
b3e33be443
@ -107,6 +107,7 @@ PHP 8.3 UPGRADE NOTES
|
||||
longer accepts paths containing the parent directory (`..`). Previously,
|
||||
only paths starting with `..` were disallowed. This could easily be
|
||||
circumvented by prepending `./` to the path.
|
||||
. User exception handlers now catch exceptions during shutdown.
|
||||
|
||||
- Dom:
|
||||
. Changed DOMCharacterData::appendData() tentative return type to true.
|
||||
|
@ -689,6 +689,7 @@ static const func_info_t func_infos[] = {
|
||||
F1("serialize", MAY_BE_STRING),
|
||||
F1("xml_error_string", MAY_BE_STRING|MAY_BE_NULL),
|
||||
F1("xml_parser_get_option", MAY_BE_STRING|MAY_BE_LONG|MAY_BE_BOOL),
|
||||
FN("zend_test_create_throwing_resource", MAY_BE_RESOURCE),
|
||||
FN("zip_open", MAY_BE_RESOURCE|MAY_BE_LONG|MAY_BE_FALSE),
|
||||
FN("zip_read", MAY_BE_RESOURCE|MAY_BE_FALSE),
|
||||
F1("ob_gzhandler", MAY_BE_STRING|MAY_BE_FALSE),
|
||||
|
16
Zend/tests/gh10695_1.phpt
Normal file
16
Zend/tests/gh10695_1.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
GH-10695: Exceptions in register_shutdown_function() are caught by set_exception_handler()
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
|
||||
register_shutdown_function(function () {
|
||||
echo "register_shutdown_function()\n";
|
||||
throw new \Exception('shutdown');
|
||||
});
|
||||
?>
|
||||
--EXPECT--
|
||||
register_shutdown_function()
|
||||
Caught: shutdown
|
18
Zend/tests/gh10695_2.phpt
Normal file
18
Zend/tests/gh10695_2.phpt
Normal file
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
GH-10695: Exceptions in destructor during shutdown are caught
|
||||
--FILE--
|
||||
<?php
|
||||
class Foo {
|
||||
public function __destruct() {
|
||||
throw new \Exception(__METHOD__);
|
||||
}
|
||||
}
|
||||
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
|
||||
const FOO = new Foo;
|
||||
?>
|
||||
--EXPECT--
|
||||
Caught: Foo::__destruct
|
19
Zend/tests/gh10695_3.phpt
Normal file
19
Zend/tests/gh10695_3.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
GH-10695: Uncaught exceptions are not caught again
|
||||
--FILE--
|
||||
<?php
|
||||
register_shutdown_function(function () {
|
||||
echo "shutdown\n";
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
});
|
||||
|
||||
throw new \Exception('main');
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Exception: main in %s:%d
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in %s on line %d
|
||||
shutdown
|
19
Zend/tests/gh10695_4.phpt
Normal file
19
Zend/tests/gh10695_4.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
GH-10695: Exception handlers are not called twice
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
throw new \Exception('exception handler');
|
||||
});
|
||||
|
||||
throw new \Exception('main');
|
||||
?>
|
||||
--EXPECTF--
|
||||
Caught: main
|
||||
|
||||
Fatal error: Uncaught Exception: exception handler in %s:%d
|
||||
Stack trace:
|
||||
#0 [internal function]: {closure}(Object(Exception))
|
||||
#1 {main}
|
||||
thrown in %s on line %d
|
17
Zend/tests/gh10695_5.phpt
Normal file
17
Zend/tests/gh10695_5.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
GH-10695: Exception handlers can register another exception handler
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
throw new \Exception('exception handler');
|
||||
});
|
||||
|
||||
throw new \Exception('main');
|
||||
?>
|
||||
--EXPECT--
|
||||
Caught: main
|
||||
Caught: exception handler
|
14
Zend/tests/gh10695_6.phpt
Normal file
14
Zend/tests/gh10695_6.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
GH-10695: Exceptions from output buffering handlers during shutdown are caught
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
|
||||
ob_start(function () {
|
||||
throw new \Exception('ob_start');
|
||||
});
|
||||
?>
|
||||
--EXPECT--
|
||||
Caught: ob_start
|
16
Zend/tests/gh10695_7.phpt
Normal file
16
Zend/tests/gh10695_7.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
GH-10695: Exceptions in error handlers during shutdown are caught
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
set_error_handler(function ($errno, $errstr) {
|
||||
throw new \Exception($errstr);
|
||||
});
|
||||
register_shutdown_function(function () {
|
||||
trigger_error('main', E_USER_ERROR);
|
||||
});
|
||||
?>
|
||||
--EXPECT--
|
||||
Caught: main
|
@ -1819,6 +1819,7 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */
|
||||
EG(exception) = NULL;
|
||||
ZVAL_OBJ(¶ms[0], old_exception);
|
||||
ZVAL_COPY_VALUE(&orig_user_exception_handler, &EG(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) {
|
||||
zval_ptr_dtor(&retval2);
|
||||
@ -1830,6 +1831,8 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */
|
||||
} else {
|
||||
EG(exception) = old_exception;
|
||||
}
|
||||
|
||||
zval_ptr_dtor(&orig_user_exception_handler);
|
||||
} /* }}} */
|
||||
|
||||
ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
|
||||
|
@ -199,7 +199,11 @@ ZEND_API ZEND_COLD void zend_throw_exception_internal(zend_object *exception) /*
|
||||
return;
|
||||
}
|
||||
if (EG(exception)) {
|
||||
zend_exception_error(EG(exception), E_ERROR);
|
||||
if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
|
||||
zend_user_exception_handler();
|
||||
} else {
|
||||
zend_exception_error(EG(exception), E_ERROR);
|
||||
}
|
||||
zend_bailout();
|
||||
}
|
||||
zend_error_noreturn(E_CORE_ERROR, "Exception thrown without a stack frame");
|
||||
|
@ -1010,7 +1010,11 @@ cleanup_args:
|
||||
|
||||
if (UNEXPECTED(EG(exception))) {
|
||||
if (UNEXPECTED(!EG(current_execute_data))) {
|
||||
zend_throw_exception_internal(NULL);
|
||||
if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
|
||||
zend_user_exception_handler();
|
||||
} else {
|
||||
zend_throw_exception_internal(NULL);
|
||||
}
|
||||
} else if (EG(current_execute_data)->func &&
|
||||
ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
|
||||
zend_rethrow_exception(EG(current_execute_data));
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "Zend/Optimizer/zend_optimizer.h"
|
||||
#include "test_arginfo.h"
|
||||
#include "zend_call_stack.h"
|
||||
#include "zend_exceptions.h"
|
||||
|
||||
ZEND_DECLARE_MODULE_GLOBALS(zend_test)
|
||||
|
||||
@ -53,6 +54,8 @@ static zend_class_entry *zend_test_string_enum;
|
||||
static zend_class_entry *zend_test_int_enum;
|
||||
static zend_object_handlers zend_test_class_handlers;
|
||||
|
||||
static int le_throwing_resource;
|
||||
|
||||
static ZEND_FUNCTION(zend_test_func)
|
||||
{
|
||||
RETVAL_STR_COPY(EX(func)->common.function_name);
|
||||
@ -910,6 +913,11 @@ static void custom_zend_execute_ex(zend_execute_data *execute_data)
|
||||
old_zend_execute_ex(execute_data);
|
||||
}
|
||||
|
||||
static void le_throwing_resource_dtor(zend_resource *rsrc)
|
||||
{
|
||||
zend_throw_exception(NULL, "Throwing resource destructor called", 0);
|
||||
}
|
||||
|
||||
PHP_MINIT_FUNCTION(zend_test)
|
||||
{
|
||||
zend_test_interface = register_class__ZendTestInterface();
|
||||
@ -981,6 +989,8 @@ PHP_MINIT_FUNCTION(zend_test)
|
||||
zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU);
|
||||
zend_test_fiber_init();
|
||||
|
||||
le_throwing_resource = zend_register_list_destructors_ex(le_throwing_resource_dtor, NULL, "throwing resource", module_number);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@ -1150,3 +1160,11 @@ PHP_ZEND_TEST_API ssize_t copy_file_range(int fd_in, off64_t *off_in, int fd_out
|
||||
return original_copy_file_range(fd_in, off_in, fd_out, off_out, len, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static PHP_FUNCTION(zend_test_create_throwing_resource)
|
||||
{
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
zend_resource *res = zend_register_resource(NULL, le_throwing_resource);
|
||||
ZVAL_RES(return_value, res);
|
||||
}
|
||||
|
@ -196,6 +196,9 @@ namespace {
|
||||
function zend_test_crash(?string $message = null): void {}
|
||||
|
||||
function zend_test_fill_packed_array(array &$array): void {}
|
||||
|
||||
/** @return resource */
|
||||
function zend_test_create_throwing_resource() {}
|
||||
}
|
||||
|
||||
namespace ZendTestNS {
|
||||
|
7
ext/zend_test/test_arginfo.h
generated
7
ext/zend_test/test_arginfo.h
generated
@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: eb6dd9bae381ca8163307e8a0f54bf3983b79cb5 */
|
||||
* Stub hash: 58ae12118458386ab204a2400966c25c833baeae */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
@ -122,6 +122,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_fill_packed_array, 0,
|
||||
ZEND_ARG_TYPE_INFO(1, array, IS_ARRAY, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_create_throwing_resource, 0, 0, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_namespaced_func, 0, 0, _IS_BOOL, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
@ -227,6 +230,7 @@ static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8);
|
||||
static ZEND_FUNCTION(zend_get_map_ptr_last);
|
||||
static ZEND_FUNCTION(zend_test_crash);
|
||||
static ZEND_FUNCTION(zend_test_fill_packed_array);
|
||||
static ZEND_FUNCTION(zend_test_create_throwing_resource);
|
||||
static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
|
||||
static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
|
||||
static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
|
||||
@ -289,6 +293,7 @@ static const zend_function_entry ext_functions[] = {
|
||||
ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last)
|
||||
ZEND_FE(zend_test_crash, arginfo_zend_test_crash)
|
||||
ZEND_FE(zend_test_fill_packed_array, arginfo_zend_test_fill_packed_array)
|
||||
ZEND_FE(zend_test_create_throwing_resource, arginfo_zend_test_create_throwing_resource)
|
||||
ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func)
|
||||
ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func)
|
||||
ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func)
|
||||
|
14
ext/zend_test/tests/gh10695_1.phpt
Normal file
14
ext/zend_test/tests/gh10695_1.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
GH-10695: Exceptions in resource dtors during shutdown are caught
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
echo 'Caught: ' . $exception->getMessage() . "\n";
|
||||
});
|
||||
|
||||
$resource = zend_test_create_throwing_resource();
|
||||
?>
|
||||
--EXPECT--
|
||||
Caught: Throwing resource destructor called
|
18
ext/zend_test/tests/gh10695_2.phpt
Normal file
18
ext/zend_test/tests/gh10695_2.phpt
Normal file
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
GH-10695: Uncaught exception in exception handler catching resource dtor exception
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
--FILE--
|
||||
<?php
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
throw new Exception('Caught');
|
||||
});
|
||||
|
||||
$resource = zend_test_create_throwing_resource();
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Exception: Caught in %s:%d
|
||||
Stack trace:
|
||||
#0 [internal function]: {closure}(Object(Exception))
|
||||
#1 {main}
|
||||
thrown in %s on line %d
|
Loading…
Reference in New Issue
Block a user