Skip to content

Report exceptions thrown from ResultCacheMetaExtension during result cache restore as internal errors#5853

Merged
staabm merged 6 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-blyb8rf
Jun 13, 2026
Merged

Report exceptions thrown from ResultCacheMetaExtension during result cache restore as internal errors#5853
staabm merged 6 commits into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-blyb8rf

Conversation

@phpstan-bot

@phpstan-bot phpstan-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

When a ResultCacheMetaExtension::getHash() threw during result-cache restore (before any file was analysed), the exception was not caught anywhere in PHPStan. On its own it surfaced as an uncaught fatal (exit 255), and — more dangerously — when a global set_exception_handler() was installed by a bootstrap file (e.g. larastan booting Laravel), that handler swallowed it and the process exited 0: a silent no-op with green CI and nothing analysed.

This change makes PHPStan catch such exceptions itself and report them as internal errors, so the run always fails with a non-zero exit code, exactly as it does under --debug.

Root cause

ResultCacheManager::restore()getMeta()getMetaFromPhpStanExtensions() invoked each ResultCacheMetaExtension::getKey()/getHash() with no exception handling, before any file was analysed. Because the throw happened while computing cache metadata (not while analysing a file), it was never attributed to a file and never converted into an internal error. In normal mode the bare exception escaped AnalyseApplication::analyse() and was rethrown by AnalyseCommand, leaving its fate to PHP's default handler or, worse, to a user-installed global exception handler that turned it into a silent exit 0.

The pattern is "third-party extension callback invoked during the result-cache lifecycle without exception guarding." Both extension methods on that axis (getKey() and getHash()) are now guarded, and the failure is funnelled through PHPStan's existing internal-error reporting so it can never be swallowed by external handlers.

Fixes phpstan/phpstan#14805

Comment thread tests/PHPStan/Analyser/ResultCache/ResultCacheMetaExtensionTest.php Outdated
Comment thread src/Analyser/ResultCache/ResultCacheManager.php Outdated
phpstan-bot and others added 6 commits June 13, 2026 08:42
…t cache restore as internal errors

- Wrap `ResultCacheMetaExtension::getKey()`/`getHash()` calls in `ResultCacheManager::getMetaFromPhpStanExtensions()` and rethrow any `Throwable` as a new `ResultCacheMetaExtensionException` carrying the extension class and original exception.
- Catch `ResultCacheMetaExtensionException` around `restore()` in `AnalyseApplication::analyse()` and convert it into an `InternalError` so the run fails with a non-zero exit code instead of the exception escaping as a fatal that a global `set_exception_handler()` can swallow into exit 0.
- In debug mode the original exception is rethrown, matching the previous `--debug` behaviour.
- Guard both `getKey()` and `getHash()` (sibling extension methods) since either can throw the same way.
- `process()` reuses the meta already computed during `restore()` and does not call `getHash()` again, so no guard is needed there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rows

Reproduces phpstan/phpstan#14805 end-to-end: a ResultCacheMetaExtension
whose getHash() throws during result cache restore, combined with a
bootstrap file that installs a global set_exception_handler (as a framework
like Laravel/larastan would). Without the fix the handler swallows the
uncaught exception and the process exits 0, silently skipping analysis;
with the fix the throw is reported as an internal error and the run exits
non-zero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverts the in-PHPStan handling of exceptions thrown from
ResultCacheMetaExtension::getKey()/getHash() during result cache restore.
This will be replaced by catching \Throwable in the Symfony Console
Application, which fixes the swallowed-exit-0 problem generally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An uncaught \Error (e.g. thrown from a ResultCacheMetaExtension during
result cache restore) escaped Symfony Console's `catch (\Exception $e)`
in Application::run() and propagated to a user-installed global
set_exception_handler() (e.g. a framework bootstrap), which could swallow
it into a clean exit 0 - silently skipping analysis with green CI.

Patch the catch to \Throwable (PHP 7.x compatible) so any uncaught
Error/Exception is rendered and produces a non-zero exit code, the same
way `--debug` (setCatchExceptions(false)) already rethrows it.

Update the e2e test to assert the throw surfaces (exit 1, 'boom from
getHash') and is not swallowed by the global handler.

Closes phpstan/phpstan#14805

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@staabm staabm force-pushed the create-pull-request/patch-blyb8rf branch from d25cde3 to 2723cfd Compare June 13, 2026 06:42
@staabm staabm merged commit a72f09c into phpstan:2.2.x Jun 13, 2026
666 of 670 checks passed
@staabm staabm deleted the create-pull-request/patch-blyb8rf branch June 13, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exception from ResultCacheMetaExtension::getHash() during cache restore is swallowed → exits 0, analysis silently skipped

3 participants