psalm: False positives related to UndefinedClass

Over the past week, Psalm in our CI build has been returning a lot of errors related to UndefinedClass. It’s always related to the same one or two classed that it thinks are undefined, however they are not. I’ve fully checked the path to the classes multiple times and they definitely exist, in the right place, with the right character cases in their FQCN and path. Additionally, the app successfully loads the classes all the time and I’ve written a short script that requires the autoloader and instantiates the classes that psalm can’t find which I run right before running psalm.

I’m unable to reproduce this in my local dev environment, but it’s very consistent in the CI environment. I also can’t seem to make any sort of reproducer.

I’ve tried:

  • Both manually specifying the autoloader and not specifying it at all in psalm.xml
  • Clearing the psalm cache
  • Running with --no-cache
  • Running psalm multiple times in CI
  • Running composer dump-autoload and composer dump-autoload -o before running psalm

From the couple of classes it cannot find, it generates a bunch of other errors such as MoreSpecificReturnType, LessSpecificReturnType, MixedInferredReturnType, UndefinedDocBlockClass, MixedMethodCall, MixedInferredReturnType, etc. I believe these all stem from not being able to determine the type of the objects because their class is “undefined”.

Our codebase is very modern and we’ve been using psalm from day 1, so it’s not like we have a custom autoloader or anything weird. I know that it will be very hard for anyone to track this down and I’m certainly very happy to do the work. But I have no idea where to go now in order to debug further.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 6
  • Comments: 17 (9 by maintainers)

Most upvoted comments

I believe we are also affected by this, and the --threads=1 appears to reduce the impact.

./vendor/bin/psalm -v
Psalm 4.30.0@d0bc6e25d89f649e4f36a534f330f8bb4643dd69

We are using no psalm plug-ins on this product, (Symfony 3.x codebase)

Macbook Pro M1 running in PHP 8.1, although also happening within docker images built FROM php:7.4-fpm in CI.

php -v
PHP 8.1.12 (cli) (built: Nov  2 2022 15:48:23) (NTS)
<?xml version="1.0"?>
<psalm
    errorLevel="2"
    reportMixedIssues="false"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
    errorBaseline="./psalm-baseline.xml"
    autoloader="./vendor/autoload.php"
>

Re-running psalm successively in a script 10x times will lead to different number of errors outputted each time.

./vendor/bin/psalm --no-file-cache --no-reflection-cache --no-cache --long-progress --no-suggestions --config=psalm.xml

x10 will result in

31 errors found
31 errors found
100 errors found
31 errors found
31 errors found
31 errors found
100 errors found
31 errors found
31 errors found
31 errors found

The extra errors reported are typically:

  • Undefined<Things>

--threads=1 will consistently report 31 errors found over 10 iterations. We will be updating the CI to run in a single thread to keep things moving for our development team.

Posting as it might help others and as a ‘me too’, but I appreciate it might not be enough to debug

Experiencing a similar issue in 5.17. Very hard to reproduce. In CI (dh actions -> docker -> psalm) I fixed it by disabling cache, but running with multiple threads. Locally I run in docker, with persistent cache. My solution was to disable cache, still running with multiple threads though.

Seems like psalm fails to load some classes when caching is enabled and there’s cached data.

Experiencing the same issue on psalm 5.4 but in reverse where CI is fine, but locally the same class (every time) is undefined. Having read the comments here, running --threads=1 --no-cache solved the issue. I tried a mixture of options including --no-reflection-cache, individually and combined until I found the winning combination.

Disabling plugins had no effect. Issues reported are consistent every time. My baseline is empty, but removing it from config also had no effect.

@kieljohn I’d be interested to know if you can reproduce on our master branch (which will soon be released as Psalm 5). There has been some internal changes that can prevent some multithreading issues, it may resolve this hopefully

I’ve run with --debug-by-line with each of the following:

  • --threads=1 -> does not reproduce the issue
  • --threads=2 -> intermittently reproduce the issue
  • --threads=3 -> does reproduce the issue
  • --threads=4 -> does reproduce the issue

When the issue is reproduced, there are 2 classes that result in UndefinedClass and it’s always the same 2 classes. Occasionally only 1 of them will give UndefinedClass and the other does not. But there doesn’t seem to be any other classes this happens on. Both the classes are fairly simple Doctrine entities, there are plenty of other entities (both more simple and more complex) in the same namespace and other namespaces. There must be something unique about these classes for them to fail so consistently, but I can’t seem to identify it.

With --debug-by-line psalm prints the following logs for both the classes (regardless of whether the issue is reproduced or not):

  1. Parsing /src/Path/To/Entity.php
  2. Deep scanning /src/Path/To/Entity.php
  3. Have populated App\Path\To\Entity
  4. Getting /src/Path/To/Entity.php
  5. Analyzing /src/Path/To/Entity.php

1 and 2 always happen immediately after each other, as do 4 and 5. Although the order in which entity is processed can change. eg. in one build the order might happen like:

  • Parsing EntityA
  • Deep scanning EntityA
  • Parsing EntityB
  • Deep scanning EntityB
  • Have populated EntityA
  • Have populated EntityB
  • Getting EntityA
  • Analyzing EntityA
  • Getting EntityB
  • Analyzing EntityB

But in the next build, EntityB might come first:

  • Parsing EntityB
  • Deep scanning EntityB
  • Parsing EntityA
  • Deep scanning EntityA
  • Have populated EntityB
  • Have populated EntityA
  • Getting EntityB
  • Analyzing EntityB
  • Getting EntityA
  • Analyzing EntityA

The order doesn’t really seem to matter IMHO, since the issue is very consistent but the order is not. Even their order compared to other classes is different across all builds. Although I haven’t broken down the order of every class because there’s way to many to make much sense of that.

Additionally, for each of the classes the following errors are identified by psalm when the issue is reproduced:

- UnusedProperty - cannot find any references to private property` for each property in the class
- UndefinedClass - cannot set properties of undefined class` for each place the property is written in the class
- UndefinedClass - Class, interface or enum named App\Path\To\Entity does not exist` every time the entity is the type of a property or parameter
- UndefinedDocblockClass - Docblock-defined class, interface or enum App\Path\To\Entity does not exist` every time the entity
- MoreSpecificReturnType - The declared return type  'App\Path\To\Entity&App\Path\To\Entity' is more specific than the inferred return type 'object'
- LessSpecificReturnType - The type 'object' is more general than the declared return type 'App\Path\To\Entity&App\Path\To\Entity

After digging through this for a while, I cannot identify anything that seems to be the root cause. Not sure if any of the above give you any ideas as to where to dig further?

Happy to share the logs privately if you think that will help? Just flick me your email address on LinkedIn or something.