[php-src] master: Fix GH-24491: Static method stream error handler replaces pending exception (#24520)

From: 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; }

« previous php.cvs (#142641) next »