From 8a986b360598ba4b5b1e6b1d2178250e55baf3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20=22Marc=C3=A3o=22=20Aurelio?= Date: Fri, 10 Apr 2026 14:35:57 -0300 Subject: [PATCH 1/2] Fix GH-21699: don't treat callables as valid if exception pending after deprecations When resolving string callables using self::, parent::, or static::, zend_is_callable_check_class() emits E_DEPRECATED. If the user error handler throws, EG(exception) is set but the function could still return true, leading to trampoline allocation and a failed assertion in shutdown_executor(). Return false from zend_is_callable_check_class() when EG(exception) is set after handling. Add regression tests for self::, parent::, and static:: forms. --- NEWS | 2 ++ Zend/tests/gh_21699.phpt | 31 +++++++++++++++++++++++++++++++ Zend/tests/gh_21699_parent.phpt | 32 ++++++++++++++++++++++++++++++++ Zend/tests/gh_21699_static.phpt | 31 +++++++++++++++++++++++++++++++ Zend/zend_API.c | 4 ++++ 5 files changed, 100 insertions(+) create mode 100644 Zend/tests/gh_21699.phpt create mode 100644 Zend/tests/gh_21699_parent.phpt create mode 100644 Zend/tests/gh_21699_static.phpt diff --git a/NEWS b/NEWS index 07653ef6a37f..224b0879a9cc 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ PHP NEWS . Fixed bug GH-21478 (Forward property operations to real instance for initialized lazy proxies). (iliaal) . Fixed bug GH-21605 (Missing addref for Countable::count()). (ilutov) + . Fixed bug GH-21699 (Assertion failure in shutdown_executor when resolving + self::/parent::/static:: callables if the error handler throws). - Curl: . Add support for brotli and zstd on Windows. (Shivam Mathur) diff --git a/Zend/tests/gh_21699.phpt b/Zend/tests/gh_21699.phpt new file mode 100644 index 000000000000..49b58365dabf --- /dev/null +++ b/Zend/tests/gh_21699.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-21699: Assertion failure in shutdown_executor when error handler throws during self:: callable resolution +--FILE-- +test(); +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): {closure:%s}(%d, 'Use of "self" i%s', '%s', %d) +#1 %s(%d): bar->test() +#2 {main} + +Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d +Stack trace: +#0 %s(%d): bar->test() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_21699_parent.phpt b/Zend/tests/gh_21699_parent.phpt new file mode 100644 index 000000000000..73cae41f3f5b --- /dev/null +++ b/Zend/tests/gh_21699_parent.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-21699 (parent::): no shutdown_executor trampoline assertion when error handler throws during parent:: callable resolution +--FILE-- +test(); +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): {closure:%s}(%d, 'Use of "parent"%s', '%s', %d) +#1 %s(%d): Child->test() +#2 {main} + +Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d +Stack trace: +#0 %s(%d): Child->test() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_21699_static.phpt b/Zend/tests/gh_21699_static.phpt new file mode 100644 index 000000000000..4d9604ebe77b --- /dev/null +++ b/Zend/tests/gh_21699_static.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-21699 (static::): no shutdown_executor trampoline assertion when error handler throws during static:: callable resolution +--FILE-- +test(); +?> +--EXPECTF-- +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): {closure:%s}(%d, 'Use of "static"%s', '%s', %d) +#1 %s(%d): bar->test() +#2 {main} + +Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d +Stack trace: +#0 %s(%d): bar->test() +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e529c48b5ac2..3c9891a00e92 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3849,6 +3849,10 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc if (error) zend_spprintf(error, 0, "class \"%.*s\" not found", (int)name_len, ZSTR_VAL(name)); } ZSTR_ALLOCA_FREE(lcname, use_heap); + /* User error handlers may throw from deprecations above; do not report callable as valid. */ + if (UNEXPECTED(EG(exception))) { + return false; + } return ret; } /* }}} */ From 94efc8f16722bd6a05e0738bb5901f325b858ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20=22Marc=C3=A3o=22=20Aurelio?= Date: Fri, 10 Apr 2026 15:05:18 -0300 Subject: [PATCH 2/2] Fix GH-16799 test: update expected output for static callables and handle TypeError in stack trace --- Zend/tests/gh16799.phpt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Zend/tests/gh16799.phpt b/Zend/tests/gh16799.phpt index ce1dbd5b1655..d31d1a5705c6 100644 --- a/Zend/tests/gh16799.phpt +++ b/Zend/tests/gh16799.phpt @@ -15,7 +15,12 @@ Test::test(); --EXPECTF-- Fatal error: Uncaught Exception: Use of "static" in callables is deprecated in %s:%d Stack trace: -#0 %s(%d): {closure:%s:%d}(8192, 'Use of "static"...', %s, %d) +#0 %s(%d): {closure:%s}(8192, 'Use of "static"%s', '%s', %d) #1 %s(%d): Test::test() #2 {main} + +Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d +Stack trace: +#0 %s(%d): Test::test() +#1 {main} thrown in %s on line %d