[php-src] Issue #24491: Stream error_handler given as 'Class::method' turns the real exception into a TypeError

From: Date: Wed, 21 Oct 2026 18:00:42 +0000
Subject: [php-src] Issue #24491: Stream error_handler given as 'Class::method' turns the real exception into a TypeError
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253058@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24491 Author: dev-null-undefined ### Description Since 2f7b01b86c, if a stream context's error_handler is a static method given as a string or an array, the exception that is already pending gets replaced by a TypeError about self/parent/static. That pending exception is the StreamException in exception mode, or whatever a user error handler threw. The original exception is only reachable through getPrevious(), so catch (StreamException) no longer catches it. Closures work fine. The following code: ```php <?php class H { static function handle($errors) { echo "handler called\n"; } } function run(string $label, array $opts) { try { fopen('/nonexistent/dir/file', 'r', false, stream_context_create(['stream' => $opts])); } catch (Throwable $e) { echo "$label: ", $e::class, ": ", $e->getMessage(), "\n"; if ($p = $e->getPrevious()) echo " previous: ", $p::class, "\n"; } } run('exception mode', ['error_mode' => StreamErrorMode::Exception, 'error_handler' => 'H::handle']); set_error_handler(fn($no, $str) => throw new ErrorException($str)); run('error handler', ['error_handler' => ['H', 'handle']]); ``` Resulted in this output: ``` exception mode: TypeError: stream error handler must be a valid callback, cannot use "self"/"parent"/"static" in callables previous: StreamException error handler: TypeError: stream error handler must be a valid callback, cannot use "self"/"parent"/"static" in callables previous: ErrorException ``` But I expected this output instead: ``` exception mode: StreamException: Failed to open stream: No such file or directory error handler: ErrorException: fopen(): Failed to open stream: No such file or directory ``` zend_is_callable_check_class() gives up when EG(exception) is already set, and since that commit it fills in the self/parent/static message. php_stream_call_error_handler() now throws it unconditionally. Skipping the handler when an exception is already pending, or only throwing when there isn't one, would avoid this. The same commit also introduced a small leak. With a throwing error handler, register_shutdown_function(['self', 'foo']) inside a class leaks the error string: zend_parse_arg() returns FAILURE on EG(exception) without freeing it. A debug build reports zend_API.c(3835) : Freeing ... (49 bytes). ### PHP Version ``` PHP 8.7.0-dev (cli) (built: Oct 21 2026 18:50:46) (NTS DEBUG) ``` ### Operating System Linux x86_64

« previous php.bugs (#253058) next »