psalm / psalm-plugin-wordpress

WordPress stubs and plugin for Psalm
MIT License
68 stars 17 forks source link

Crashed due to an uncaught Throwable: Uncaught Exception: UnexpectedValueException #34

Closed jmslbam closed 1 year ago

jmslbam commented 2 years ago

Hi all,

First of all thank you for the psalm plugin. I'm running into an crash while scanning my project after activating this Psalm plugin. After some Googling I landed on https://github.com/vimeo/psalm/issues/3216 and analysed the the error and saw that it had to do with the activated Psalm plugin:

`#4 [internal function]: PsalmWordpress\Plugin::PsalmWordpress{closure}(Object(PhpParser\Node\Arg))

5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(213): array_map(Object(Closure), Array)`

Uncaught Exception: UnexpectedValueException There should be a node type provider
Emitted in /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php:667
Stack trace in the forked worker:
#0 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\FileAnalyzer->getNodeTypeProvider()
#1 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
#2 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/SourceAnalyzer.php(180): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
#3 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(191): Psalm\Internal\Analyzer\SourceAnalyzer->getNodeTypeProvider()
**#4 [internal function]: PsalmWordpress\Plugin::PsalmWordpress\{closure}(Object(PhpParser\Node\Arg))
#5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/humanmade/psalm-plugin-wordpress/Plugin.php(213): array_map(Object(Closure), Array)**
#6 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/EventDispatcher.php(367): PsalmWordpress\Plugin::afterEveryFunctionCallAnalysis(Object(PhpParser\Node\Expr\FuncCall), 'apply_filters', Object(Psalm\Context), Object(Psalm\Internal\Analyzer\MethodAnalyzer), Object(Psalm\Codebase))
#7 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php(262): Psalm\Internal\EventDispatcher->dispatchAfterEveryFunctionCallAnalysis(Object(Psalm\Plugin\EventHandler\Event\AfterEveryFunctionCallAnalysisEvent))
#8 /Users/xyz/local-sites/rc/app/public/wp-content/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))
#9 /Users/xyz/local-sites/rc/app/public/wp-content/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)
#10 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php(240): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\FuncCall), Object(Psalm\Context))
#11 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(167): Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Variable), Object(PhpParser\Node\Expr\FuncCall), NULL, Object(Psalm\Context), NULL)
#12 /Users/xyz/local-sites/rc/app/public/wp-content/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\Assign), Object(Psalm\Context), false, NULL, true)
#13 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(571): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\Assign), Object(Psalm\Context), false, NULL, true)
#14 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(206): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), NULL)
#15 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), NULL, true)
#16 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php(131): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), NULL, true)
#17 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(441): Psalm\Internal\Analyzer\ClassLikeAnalyzer->getMethodMutations('__construct', Object(Psalm\Context))
#18 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(1413): Psalm\Internal\Analyzer\FileAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context), true)
#19 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(408): Psalm\Internal\Analyzer\ProjectAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context), '/Users/jmslbam/...', 'themes/reiscomp...')
#20 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php(127): Psalm\Internal\Analyzer\FileAnalyzer->getMethodMutations(Object(Psalm\Internal\MethodIdentifier), Object(Psalm\Context))
#21 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(853): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\ExistingAtomicStaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(PhpParser\Node\Identifier), Array, Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), Object(Psalm\Internal\MethodIdentifier), 'SecureHoliday\\S...', Object(Psalm\Storage\ClassLikeStorage), false)
#22 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(201): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\AtomicStaticCallAnalyzer::handleNamedCall(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(PhpParser\Node\Identifier), Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), Array, 'SecureHoliday\\S...', false, true)
#23 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php(215): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod\AtomicStaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context), Object(Psalm\Type\Atomic\TNamedObject), false, false, false, true)
#24 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(190): Psalm\Internal\Analyzer\Statements\Expression\Call\StaticCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context))
#25 /Users/xyz/local-sites/rc/app/public/wp-content/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\StaticCall), Object(Psalm\Context), false, Object(Psalm\Context), true)
#26 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(571): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context), false, Object(Psalm\Context), true)
#27 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(206): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Expression), Object(Psalm\Context), Object(Psalm\Context))
#28 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#29 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1241): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context), true)
#30 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(539): Psalm\Internal\Analyzer\ClassAnalyzer->checkPropertyInitialization(Object(Psalm\Codebase), Object(Psalm\Config), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Context), Object(Psalm\Context), Object(Psalm\Internal\Analyzer\MethodAnalyzer))
#31 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(229): Psalm\Internal\Analyzer\ClassAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#32 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(362): Psalm\Internal\Analyzer\FileAnalyzer->analyze()
#33 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(211): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(1, '/Users/jmslbam/...')
#34 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(428): Psalm\Internal\Fork\Pool->__construct(Object(Psalm\Config), Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#35 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9)
#36 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(685): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9, false, true)
#37 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/jmslbam/...', true)
#38 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/psalm(7): Psalm\Internal\Cli\Psalm::run(Array)
#39 {main} in /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php:402
Stack trace:
#0 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(436): Psalm\Internal\Fork\Pool->readResultsFromChildren()
#1 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(503): Psalm\Internal\Fork\Pool->wait()
#2 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9)
#3 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(685): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 9, false, true)
#4 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/jmslbam/...', true)
#5 /Users/xyz/local-sites/rc/app/public/wp-content/vendor/vimeo/psalm/psalm(7): Psalm\Internal\Cli\Psalm::run(Array)
#6 {main}
(Psalm 4.23.0@f1fe6ff483bf325c803df9f510d09a03fd796f88 crashed due to an uncaught Throwable)

Adding newlines to the code always solves the issue:

Before (crashes)

if ( ! empty( $output ) ) {
    return $output;
}
return $this->primaryAccommodation->get_the_excerpt( true );

After (passes)

if ( ! empty( $output ) ) {
    return $output;
}

return $this->primaryAccommodation->get_the_excerpt( true );

Before (crashes)

if ( is_a( $oldTerm, 'WP_Error' ) ) {
    error_log( 'SH > Linker: ' . $oldTerm->get_error_code() );
    continue;
}

After (passes)

if ( is_a( $oldTerm, 'WP_Error' ) ) {
    error_log( 'SH > Linker: ' . $oldTerm->get_error_code() );

    continue;
}

I don't have any experience with writing Psalm plugins or Psalm in general. This is my first project with it, so I did wanted to let you know about the error / crash and the "fix". I do understand if this will become a won't fix, but I did wanted to drop it here for future devs that are Googling for the error may they encouter this situation.

Don't really now what to do to fix it, or which coding convention to follow, because the lines it crashes on don't really have a pattern...

Let me know if I can provide some extra information if needed.

Kinds regards and have nice weekend!

Jaime

kkmuffme commented 2 years ago

Hi Jaime,

using your code examples I'm not able to reproduce the error. From what I can see in vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php this issue is bc of function analyze() encountering a has_visitor_issues or the PhpParser try/catch fails (most likely)

How you can help fix this issue: If you put only the code examples you sent above in the file (with opening php tag obv) do you still get the error? I assume not (otherwise I would have been able to reproduce this). Can you remove things from the file (except that code you know breaks it, which you sent above) until you do not get that error anymore? Then put that back and remove everything else. And post that code here, so we can analyze and fix the issue.

However, it looks like it will not be an issue with this plugin (I mean: yes, we can silence this notice), but probably an issue that will get fix upstream in phpparser then.

alpipego commented 2 years ago

I get this issue with one custom apply_filters call in my code. There are many more actions and filters, and they are fine, but this specific one leads to the same error as above. Any idea why this is happening and how I can prevent psalm from crashing?

kkmuffme commented 2 years ago

If you can provide a minimal, reproducable code sample I'm happy to debug and fix it for you. Any other psalm plugins you use? Please also post your psalm config

alpipego commented 2 years ago

So far, I wasn't able to create a minimal code sample. It crashes if I remove everything else besides that filter from the method, yet, it does not, if I simply add the filter to a test class.

I don't use any other plugins, and there are just paths in the psalm.xml.

mcaskill commented 1 year ago

@alpipego @jmslbam Can you test this again with the master branch. This might might have been fixed in b6ceeef.

alpipego commented 1 year ago

Thanks for the update @mcaskill. Unfortunately, it still exits on the same uncaught exception for the same apply_filters call.

mcaskill commented 1 year ago

I unintentionally stumbled upon this error in a new package I was implementing.

I can bypass the exception by generating a JSON representation of my custom hooks and registering them through the plugin's <hooks> option.

Once registered, the analyzing of types is skipped:

// Check if this hook is already documented.
if ( isset( static::$hooks[ $name ] ) ) {
    return;
}

This should help as a working solution until we figure out better logic in that method, such as emitting a custom UndefinedHook.

mcaskill commented 1 year ago

Hrm. I am no longer able to reproduce the issue.

kkmuffme commented 1 year ago

I'm unable to reproduce this too.

@mcaskill sorry for being MIA, but I am currently incredibly busy with urgent stuff that needs to be completed in the next month. Once that's done, I will PR all the stuff for v5 and get it going again.

marcosh commented 1 year ago

I got a similar error on the following code

<?php

namespace Foo;

do_action('', '');

I tried to debug the issue and it looks like that the call to $statements_source->getNodeTypeProvider() fails when it is using a NamespaceAnalyzer because the NodeDataProvider of the source FileAnalyzer has not been set, yet.

Does it make sense to avoid the error substituting

https://github.com/psalm/psalm-plugin-wordpress/blob/1547c814625ddd60abc604bc7f273344d71633d9/Plugin.php#L363

with

try {
    $type = $statements_source->getNodeTypeProvider()->getType( $arg->value );
} catch (\UnexpectedValueException $e) {
    $type = null;
}

?

kkmuffme commented 1 year ago

Thanks. So the issue happens when you use a do_action with an empty name?

I'll put this in my upcoming PR for v5 so it will be fixed there (coming soon, 2 weeks promised)

marcosh commented 1 year ago

I don't think the issue is caused by the empty name. It looks like the plugin looks for typing information before those are actually generated by Psalm