[php-src] master: Fix GH-24491: Static method stream error handler replaces pending exception (#24520)
| From: | Lazizbek Ergashev via GitHub | Date: | Fri, 23 Oct 2026 19:57:35 +0000 |
| Subject: | [php-src] master: Fix GH-24491: Static method stream error handler replaces pending exception (#24520) | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142641@lists.php.net to get a copy of this message | ||
Author: Lazizbek Ergashev (lazerg)
Committer: GitHub (web-flow)
Pusher: Girgias
Date: 2026-10-23T20:57:32+01:00
Commit: https://github.com/php/php-src/commit/551967dd910e3d541c0d40bfbf49e3e3e099f5dc
Raw diff: https://github.com/php/php-src/commit/551967dd910e3d541c0d40bfbf49e3e3e099f5dc.diff
Fix GH-24491: Static method stream error handler replaces pending exception (#24520)
Since 2f7b01b86c,
zend_is_callable() fills in an error string even when it fails
because an exception is already pending, so php_stream_call_error_handler() threw a
TypeError over the StreamException (or whatever the user error handler threw) for any
Class::method handler. It now only throws when nothing is pending, the same way curl
already does.
The same commit made zend_parse_arg() leak that error string when it bails out on a
pending exception, so it's freed there now too.
Fixes GH-24491
Changed paths:
A Zend/tests/zpp/gh24491.phpt
A ext/standard/tests/streams/gh24491.phpt
M Zend/zend_API.c
M main/streams/stream_errors.c
Diff:
diff --git a/Zend/tests/zpp/gh24491.phpt b/Zend/tests/zpp/gh24491.phpt
new file mode 100644
index 000000000000..ec6e77198350
--- /dev/null
+++ b/Zend/tests/zpp/gh24491.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-24491: Callable error string leaks when an exception is thrown during zpp
+--FILE--
+<?php
+set_error_handler(function (int $errno, string $errstr) {
+ throw new Exception($errstr);
+});
+
+class A {
+ public static function foo() {}
+
+ public static function test() {
+ register_shutdown_function(['self', 'foo']);
+ }
+}
+
+try {
+ A::test();
+} catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Use of "self" in callables is deprecated
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index d83ea43add1b..28439b6e3015 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -1096,6 +1096,9 @@ static zend_result zend_parse_arg(uint32_t arg_num, zval *arg, va_list *va,
cons
zend_expected_type expected_type = zend_parse_arg_impl(arg, va, spec, &error, arg_num);
if (expected_type != Z_EXPECTED_LAST) {
if (EG(exception)) {
+ if (error && (expected_type == Z_EXPECTED_FUNC || expected_type ==
Z_EXPECTED_FUNC_OR_NULL)) {
+ efree(error);
+ }
return FAILURE;
}
diff --git a/ext/standard/tests/streams/gh24491.phpt b/ext/standard/tests/streams/gh24491.phpt
new file mode 100644
index 000000000000..86a07d566621
--- /dev/null
+++ b/ext/standard/tests/streams/gh24491.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-24491 (Stream error_handler given as 'Class::method' turns the real exception into a
TypeError)
+--FILE--
+<?php
+class H {
+ public static function handle(array $errors): void {
+ echo "handler called\n";
+ }
+}
+
+function run(array $options): void {
+ try {
+ fopen('/nonexistent/dir/file', 'r', false,
stream_context_create(['stream' => $options]));
+ } catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ var_dump($e->getPrevious());
+ }
+}
+
+run(['error_mode' => StreamErrorMode::Exception, 'error_handler' =>
'H::handle']);
+
+set_error_handler(function (int $errno, string $errstr) {
+ throw new ErrorException($errstr);
+});
+run(['error_handler' => ['H', 'handle']]);
+?>
+--EXPECT--
+StreamException: Failed to open stream: No such file or directory
+NULL
+ErrorException: fopen(): Failed to open stream: No such file or directory
+NULL
diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c
index e2dfa310e614..211cf4df025a 100644
--- a/main/streams/stream_errors.c
+++ b/main/streams/stream_errors.c
@@ -370,7 +370,9 @@ static void php_stream_call_error_handler(const zval *handler, zval *errors_arra
char *is_callable_error = NULL;
if (!zend_is_callable(handler, &fcc, &is_callable_error)) {
- zend_type_error("stream error handler must be a valid callback, %s",
is_callable_error);
+ if (!EG(exception)) {
+ zend_type_error("stream error handler must be a valid callback, %s",
is_callable_error);
+ }
efree(is_callable_error);
return;
}