Skip to content

Commit b742cd7

Browse files
Sander Mullerclaude
andcommitted
Discard the cache when the cached results cannot be reconstructed
unserialize() has no reconstruction hook, so a cache written by a PHPStan whose classes have since changed can fail while the objects are rebuilt: a property the payload does not carry stays uninitialized and reading it throws. var_export absorbed this because __set_state() reconstructs through the constructor, which applies the declared defaults - renaming Error::$tip is harmless there and aborts the run here. cacheVersion and phpstanVersion keep a released version away from another release's objects, but phpstanVersion comes from composer's installed.php rather than the working tree, so a source checkout keeps one value across every edit of these classes. That is where it is reachable, and it is also where PHPStan is developed. The four callbacks are now invoked inside restore()'s failure path, so a cache that cannot be read back is discarded and everything re-analysed, the same as for a damaged file. Before this, the same shape aborted the command with an uncaught error and a usage dump - on both formats, since the var_export closures are evaluated at the same point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 85fe784 commit b742cd7

1 file changed

Lines changed: 23 additions & 4 deletions

File tree

src/Analyser/ResultCache/ResultCacheManager.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,12 +485,31 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
485485
$filesToAnalyse = [];
486486
$invertedDependenciesToReturn = [];
487487
$invertedUsedTraitDependenciesToReturn = [];
488-
$errors = $data['errorsCallback']();
489-
$locallyIgnoredErrors = $data['locallyIgnoredErrorsCallback']();
490488
$linesToIgnore = $data['linesToIgnore'];
491489
$unmatchedLineIgnores = $data['unmatchedLineIgnores'];
492-
$collectedData = $data['collectedDataCallback']();
493-
$exportedNodes = $data['exportedNodesCallback']();
490+
491+
try {
492+
// The cached objects are reconstructed here, and a cache written by a PHPStan whose classes
493+
// have since changed can fail at it: a property the payload does not carry stays
494+
// uninitialized, and reading it throws. The cacheVersion and phpstanVersion in the metadata
495+
// keep a released version away from another release's objects, but a source checkout keeps
496+
// one phpstanVersion across every edit of these classes, so this is reachable there. A cache
497+
// that cannot be reconstructed is discarded like any other unusable one.
498+
$errors = $data['errorsCallback']();
499+
$locallyIgnoredErrors = $data['locallyIgnoredErrorsCallback']();
500+
$collectedData = $data['collectedDataCallback']();
501+
$exportedNodes = $data['exportedNodesCallback']();
502+
} catch (Throwable $e) {
503+
@unlink($cacheFilePath);
504+
505+
return $this->fullAnalysis(
506+
sprintf('Result cache not used because the cached results could not be read back: %s', $e->getMessage()),
507+
$allAnalysedFiles,
508+
$meta,
509+
$currentFileHashes,
510+
$output,
511+
);
512+
}
494513
$filteredErrors = [];
495514
$filteredLocallyIgnoredErrors = [];
496515
$filteredLinesToIgnore = [];

0 commit comments

Comments
 (0)