Repository navigation
Do not resolve include paths through a stream wrapper that is not registered - #6397
Merged
Merged
Conversation
…istered
`include_once 'vfs://drupal/sites/default/modules/module_a/module_a.post_update.php'` - Drupal
core has one in UpdateRegistryTest - names a stream wrapper, not a place on the filesystem.
vfsStream registers that wrapper from a test's setUp(), which never runs inside PHPStan, so
is_file() on the path cannot answer: it raises "Unable to find the wrapper" and returns false.
IncludedFilePathResolver handed the path back as a candidate anyway, and three callers stat'd
it. Two are quiet - FileAnalyser's error handler swallows the warning during the analysis - but
the third is not: RequireFileExistsRule records the candidates on its error so that the verdict
is rechecked when the file appears, the result cache stores that as a missing file dependency,
and every warm run then prints
PHP Warning: is_file(): Unable to find the wrapper "vfs" ... in ResultCacheManager.php
twice, before the analysis starts.
A path whose wrapper is not registered has no filesystem candidates, and cannot come to have
any, so resolve() now returns none for it. The include is still reported as not found - PHPStan
cannot see through the wrapper either way - it just no longer gets there by stat'ing, and no
longer leaves a dependency behind that every restore stats again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDcVRMjAXM7WQGSzysw7WH
absolutizePath() joins with DIRECTORY_SEPARATOR, so on Windows the resolver returns `D:\...\File\lorem.php` while the expectation built from `__DIR__ . '/lorem.php'` kept the forward slash and the assertion could not match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDcVRMjAXM7WQGSzysw7WH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Analysing Drupal core prints this before the analysis starts, on every run:
How it happens
Drupal core has a literal
include_once 'vfs://drupal/sites/default/modules/module_a/module_a.post_update.php'incore/tests/Drupal/Tests/Core/Update/UpdateRegistryTest.php. vfsStream registers that wrapper from a test'ssetUp(), which never runs inside PHPStan, sois_file()on the path cannot answer - it raises the warning and returns false.FileHelper::absolutizePath()recognisesscheme://and returns such a path unchanged, soIncludedFilePathResolver::resolve()handed it back as a filesystem candidate. Three callers then stat it:RequireFileExistsRule::doesFileExist()- quiet,FileAnalyserinstalls an error handler that swallows warnings during the analysis.DependencyResolver'sInclude_branch - also quiet, and its ownis_file()guard means the path is not recorded there.FileDependenciesRuleError, so the verdict is rechecked if the file ever appears. That lands in the dependency graph, the result cache stores it withMISSING_FILE_HASH, and every restore stats it again - outside any error handler.The fix
A path whose stream wrapper is not registered has no filesystem candidates, and cannot come to have any, so
resolve()returns none for it.The include is still reported as not found - PHPStan cannot see through the wrapper either way - it just no longer gets there by stat'ing, and no longer leaves behind a dependency that every restore stats again.
Verification
On Drupal core: 8 warnings -> 0 on both a cold and a warm run, analysis output byte-identical (the same 22 pre-existing
ignore.unmatchederrors).Note for reviewers: a
RuleTestCasecannot catch this -FileAnalyser's error handler eats the warning, so a rule test passes with and without the fix. The regression test is a unit test on the resolver; it fails before the fix with the path still in the candidate list.🤖 Generated with Claude Code
https://claude.ai/code/session_01VDcVRMjAXM7WQGSzysw7WH
Closes phpstan/phpstan#15192