vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.57k stars 660 forks source link

4.25.0 Regression: Could not get class storage for resourcebundle for PHP < 8.0 #8346

Closed kkmuffme closed 2 years ago

kkmuffme commented 2 years ago

When I run --alter: EDIT: seems to be there without --alter too

php /opt/composer/vendor/bin/psalm --config=my-rules.xml --alter --plugin=vendor/orklah/psalm-strict-equality/src/Plugin.php --php-version=7.4 my-file.php

When I do --php-version=8.0 or --php-version=8.1 I don't get an error. For everythint <= 7.4 I get this error.

Uncaught InvalidArgumentException: Could not get class storage for resourcebundle in /path/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php:46
Stack trace:
#0 /path/vendor/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php(607): Psalm\Internal\Provider\ClassLikeStorageProvider->get('ResourceBundle')
#1 /path/vendor/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php(178): Psalm\Internal\Type\TypeExpander::expandNamedObject(Object(Psalm\Codebase), Object(Psalm\Type\Atomic\TNamedObject), NULL, NULL, NULL, false, true)
#2 /path/vendor/vimeo/psalm/src/Psalm/Internal/Type/TypeExpander.php(82): Psalm\Internal\Type\TypeExpander::expandAtomic(Object(Psalm\Codebase), Object(Psalm\Type\Atomic\TNamedObject), NULL, NULL, NULL, true, false, false, true, false)
#3 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php(284): Psalm\Internal\Type\TypeExpander::expandUnion(Object(Psalm\Codebase), Object(Psalm\Type\Union), NULL, NULL, NULL, true, false, false, true)
#4 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php(195): Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer::checkFunctionLikeTypeMatches(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(Psalm\Codebase), 'count', NULL, NULL, NULL, Object(Psalm\CodeLocation), Object(Psalm\Storage\FunctionLikeParameter), true, Object(Psalm\Type\Union), 0, 0, Object(PhpParser\Node\Arg), Object(Psalm\Context), Array, NULL, true, true)
#5 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php(828): Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentAnalyzer::checkArgumentMatches(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), 'count', NULL, NULL, NULL, Object(Psalm\CodeLocation), Object(Psalm\Storage\FunctionLikeParameter), 0, 0, true, Object(PhpParser\Node\Arg), Object(Psalm\Type\Union), Object(Psalm\Context), Array, NULL, true, true)
#6 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php(214): Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer::checkArgumentsMatch(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Array, 'count', Array, NULL, NULL, Object(Psalm\Internal\Type\TemplateResult), Object(Psalm\CodeLocation), Object(Psalm\Context))
#7 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(296): Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context))
#8 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context), false, NULL, false)
#9 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php(120): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context))
#10 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(266): Psalm\Internal\Analyzer\Statements\Expression\BinaryOpAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\BinaryOp\Identical), Object(Psalm\Context), 0, false)
#11 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\BinaryOp\Identical), Object(Psalm\Context), false, NULL, false)
#12 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php(117): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\BinaryOp\Identical), Object(Psalm\Context))
#13 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php(107): Psalm\Internal\Analyzer\Statements\Block\IfConditionalAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\BinaryOp\Identical), Object(Psalm\Context), Object(Psalm\Codebase), Object(Psalm\Internal\Scope\IfScope), 11411)
#14 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(518): Psalm\Internal\Analyzer\Statements\Block\IfElseAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context))
#15 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context), NULL)
#16 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php(68): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context))
#17 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php(365): Psalm\Internal\Analyzer\Statements\Block\IfElse\IfAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Internal\Scope\IfScope), Object(Psalm\Internal\Scope\IfConditionalScope), Object(Psalm\Context), Object(Psalm\Context), Object(Psalm\Context), Array)
#18 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(518): Psalm\Internal\Analyzer\Statements\Block\IfElseAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context))
#19 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context), Object(Psalm\Context))
#20 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#21 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php(96): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context))
#22 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(564): Psalm\Internal\Analyzer\FunctionAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context))
#23 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context), Object(Psalm\Context))
#24 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(205): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#25 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php(204): Psalm\Internal\Analyzer\FileAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#26 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(347): Psalm\Internal\Analyzer\Statements\Expression\IncludeAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), Object(Psalm\Context))
#27 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), false, Object(Psalm\Context), true)
#28 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(572): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), false, Object(Psalm\Context), true)
#29 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), Object(Psalm\Context))
#30 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#31 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php(96): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context))
#32 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(564): Psalm\Internal\Analyzer\FunctionAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context))
#33 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context), NULL)
#34 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(205): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), NULL, true)
#35 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php(204): Psalm\Internal\Analyzer\FileAnalyzer->analyze(Object(Psalm\Context), NULL)
#36 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(347): Psalm\Internal\Analyzer\Statements\Expression\IncludeAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), NULL)
#37 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(78): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::handleExpression(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), false, NULL, true)
#38 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(572): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Include_), Object(Psalm\Context), false, NULL, true)
#39 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), NULL)
#40 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php(274): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context))
#41 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php(390): Psalm\Internal\Analyzer\Statements\Block\IfElse\ElseIfAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\ElseIf_), Object(Psalm\Internal\Scope\IfScope), Object(Psalm\Context), Object(Psalm\Context), Object(Psalm\Codebase), 63749)
#42 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(518): Psalm\Internal\Analyzer\Statements\Block\IfElseAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context))
#43 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\If_), Object(Psalm\Context), Object(Psalm\Context))
#44 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#45 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php(96): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context))
#46 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(564): Psalm\Internal\Analyzer\FunctionAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context))
#47 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Function_), Object(Psalm\Context), NULL)
#48 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(205): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), NULL, true)
#49 /path/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(362): Psalm\Internal\Analyzer\FileAnalyzer->analyze()
#50 /path/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(619): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(0, '/path/to/my-file...')
#51 /path/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 1)
#52 /path/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(1161): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 1, true, false)
#53 /path/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalter.php(412): Psalm\Internal\Analyzer\ProjectAnalyzer->checkFile('/path/to/my-file...')
#54 /path/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(931): Psalm\Internal\Cli\Psalter::run(Array)
#55 /path/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(177): Psalm\Internal\Cli\Psalm::forwardCliCall(Array, Array)
#56 /path/vendor/vimeo/psalm/psalm(6): Psalm\Internal\Cli\Psalm::run(Array)
#57 /path/vendor/bin/psalm(115): include('/path/v...')
#58 {main}
(Psalm v4.25.0@d7cd84c4ebca74ba3419b9601f81d177bcbe2aac crashed due to an uncaught Throwable)
kkmuffme commented 2 years ago

No

orklah commented 2 years ago

Are you using ResourceBundle somewhere in your code? Could you check if it's properly cased or not if you do?

Could you run with --debug-by-line and copy/paste the few lines surrounding the last location it shows?

kkmuffme commented 2 years ago

Are you using ResourceBundle somewhere in your code?

No.

Could you run with --debug-by-line

Done. The last line before the "Uncaught:" is "some-of-my-files.php:288" If I check this file in my code base, this is the code there:

if ( count( $matches ) === 1 ) {

which is the "count" we see in initial stack trace #4 above. It seems the "count" triggers this issue, as I don't get the error when I remove all "count" from my file.

orklah commented 2 years ago

This PR: https://github.com/vimeo/psalm/pull/8217/files removed ResourceBundle from the list of types accepted in count starting from PHP8. It seems clearly related but I have no idea why Psalm would behave like that

kkmuffme commented 2 years ago

In case it helps: the value of $matches in the count above is from $matches = preg_grep(

orklah commented 2 years ago

Could you push a PR that reverts that change: https://github.com/vimeo/psalm/pull/8217/files#diff-b8eaee1f550652657daf0a771be5e785bdb01e91acd004b4bbb4c41def706713R1546 and add a test?

I'm not sure why it would crash like that but it's not needed for anything, it shouldn't hurt to revert it

kkmuffme commented 2 years ago

We upgraded to PHP8.1 bc of this bug. Added a PR for it now anyway for other people

AndrolGenhald commented 2 years ago

For future reference, this is probably because ResourceBundle comes from the intl extension. I would guess if that extension were installed the crash would stop happening.

We should avoid referencing extension-declared classes in the callmap (except in the case where a function from an extension uses a class declared by the same extension).

nikserg commented 2 years ago

Get similar error, but for predis\client

 Could not get class storage for predis\client

  at /docker/vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php:46
     42▕     public function get(string $fq_classlike_name): ClassLikeStorage
     43▕     {
     44▕         $fq_classlike_name_lc = strtolower($fq_classlike_name);
     45▕         if (!isset(self::$storage[$fq_classlike_name_lc])) {
  ➜  46▕             throw new InvalidArgumentException('Could not get class storage for ' . $fq_classlike_name_lc);
     47▕         }
     48▕
     49▕         return self::$storage[$fq_classlike_name_lc];
     50▕     }