mortenson / psalm-plugin-drupal

A project to add Psalm support for Drupal for security testing, focused only on taint analysis.
Other
43 stars 7 forks source link

non-existent service "locale.plural.formula" #11

Closed FlorentTorregrosa closed 1 year ago

FlorentTorregrosa commented 1 year ago

Hi,

On either Drupal 9 and Drupal 10 with the latest version of psalm drupal plugin, when trying to scan files. I get this error.

I have rolled back on https://github.com/mortenson/psalm-plugin-drupal/commit/48e3cbc34dcc90ec7b196b1130c68cdaabb63118 (the version before recent changes). And I also obtain the error.

So I guess this is due to an update in Symfony?

Target PHP version: 8.1 (inferred from current PHP version)
Scanning files...
Analyzing files...

░░Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "locale.plural.formula". in /project/vendor/symfony/dependency-injection/ContainerBuilder.php on line 935 #0 /project/vendor/psalm/plugin-symfony/src/Symfony/ContainerMeta.php(155): Symfony\Component\DependencyInjection\ContainerBuilder->getDefinition('locale.plural.f...')
#1 /project/vendor/psalm/plugin-symfony/src/Symfony/ContainerMeta.php(64): Psalm\SymfonyPsalmPlugin\Symfony\ContainerMeta->getDefinition('locale.plural.f...')
#2 /project/vendor/mortenson/psalm-plugin-drupal/ContainerHandler.php(59): Psalm\SymfonyPsalmPlugin\Symfony\ContainerMeta->get('locale.plural.f...')
#3 /project/vendor/vimeo/psalm/src/Psalm/Internal/EventDispatcher.php(319): mortenson\PsalmPluginDrupal\ContainerHandler::afterMethodCallAnalysis(Object(PhpParser\Node\Expr\StaticCall), 'Drupal::service', 'Drupal::service', 'Drupal::service', Object(Psalm\Context), Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(Psalm\Codebase), Array, Object(Psalm\Type\Union))
#4 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php(409): Psalm\Internal\EventDispatcher->dispatchAfterMethodCallAnalysis(Object(Psalm\Plugin\EventHandler\Event\AfterMethodCallAnalysisEvent))
#5 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(877): 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), 'Drupal::service', Object(Psalm\Storage\ClassLikeStorage), false)
#6 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php(202): 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, 'Drupal', false, true)
#7 /project/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)
#8 /project/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))
#9 /project/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, NULL, false)
#10 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php(60): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\StaticCall), Object(Psalm\Context))
#11 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php(186): Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\MethodCall), Object(Psalm\Context))
#12 /project/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\MethodCall), Object(Psalm\Context), false, NULL, false)
#13 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php(156): Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Expr\MethodCall), Object(Psalm\Context))
#14 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(546): Psalm\Internal\Analyzer\Statements\ReturnAnalyzer::analyze(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Return_), Object(Psalm\Context))
#15 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php(207): Psalm\Internal\Analyzer\StatementsAnalyzer::analyzeStatement(Object(Psalm\Internal\Analyzer\StatementsAnalyzer), Object(PhpParser\Node\Stmt\Return_), Object(Psalm\Context), NULL)
#16 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php(68): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context))
#17 /project/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 /project/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 /project/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 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php(476): Psalm\Internal\Analyzer\StatementsAnalyzer->analyze(Array, Object(Psalm\Context), Object(Psalm\Context), true)
#21 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1798): Psalm\Internal\Analyzer\FunctionLikeAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Internal\Provider\NodeDataProvider), Object(Psalm\Context))
#22 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1471): Psalm\Internal\Analyzer\ClassAnalyzer->analyzeClassMethod(Object(PhpParser\Node\Stmt\ClassMethod), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Internal\Analyzer\TraitAnalyzer), Object(Psalm\Context), Object(Psalm\Context))
#23 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(437): Psalm\Internal\Analyzer\ClassAnalyzer->analyzeTraitUse(Object(Psalm\Aliases), Object(PhpParser\Node\Stmt\TraitUse), Object(Psalm\Internal\Analyzer\ProjectAnalyzer), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Context), Object(Psalm\Context), NULL)
#24 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(229): Psalm\Internal\Analyzer\ClassAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#25 /project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(362): Psalm\Internal\Analyzer\FileAnalyzer->analyze()
#26 /project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(619): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(2, '/project/app/mo...')
#27 /project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(291): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 1)
#28 /project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(691): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 1, false, true)
#29 /project/vendor/vimeo/psalm/src/Psalm/Internal/Cli/Psalm.php(373): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/project/app/', false)
#30 /project/vendor/vimeo/psalm/psalm(7): Psalm\Internal\Cli\Psalm::run(Array)
#31 /project/vendor/bin/psalm(120): include('/project/vendor...')
#32 {main}
Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "locale.plural.formula". in Symfony\Component\DependencyInjection\ContainerBuilder->getDefinition() (line 935 of /project/vendor/symfony/dependency-injection/ContainerBuilder.php).
mortenson commented 1 year ago

@FlorentTorregrosa Is the locale module enabled in your psalm config? Ex:

        <pluginClass class="mortenson\PsalmPluginDrupal\Plugin">
            <containerXml>DrupalContainerDump.xml</containerXml>
            <extensions>
              <module name="field" />
              <module name="language" />
              <module name="file" />
              <module name="locale" />
            </extensions>
        </pluginClass>
FlorentTorregrosa commented 1 year ago

Thanks @mortenson for the quick feedback.

It was not present. But I still got the error with the module listed.

FlorentTorregrosa commented 1 year ago

I tried to debug in the DrupalKernel to follow the flow of the error stacktrace.

In app/core/lib/Drupal/Core/DrupalKernel.php, I put some debug:

$yaml_loader = new YamlFileLoader($container);
    foreach ($this->serviceYamls['app'] as $filename) {
      if ($filename == 'core/modules/locale/locale.services.yml') {
        $container->has('locale.plural.formula') ? var_dump('yes') : var_dump('no');
      }
      $yaml_loader->load($filename);
      if ($filename == 'core/modules/locale/locale.services.yml') {
        $container->has('locale.plural.formula') ? var_dump('yes') : var_dump('no');
      }
    }

It gave:

Target PHP version: 8.1 (inferred from current PHP version)
Scanning files...
string(2) "no"
string(3) "yes"
Analyzing files...

Exception: Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException You have requested a non-existent service "locale.plural.formula".

So I guess the services definitions are loaded correctly. But lost during analysis.

Also I realized that all the modules enabled in my current instance are loaded, not only the one listed in the psalm config. If I put:

    // Register application services.
    $yaml_loader = new YamlFileLoader($container);
    foreach ($this->serviceYamls['app'] as $filename) {
      var_dump($filename);
      $yaml_loader->load($filename);
    }

I see all the services.yml files of my enabled modules.

When looking at the Drupal psalm plugin I saw:

// Copies Psalm\SymfonyPsalmPlugin\Handler\ContainerHandler to support Drupal::service.

in vendor/mortenson/psalm-plugin-drupal/ContainerHandler.php

So I wonder if this class should not be updated because there are been changes between the 2.x and 4.x version of the Symfony plugin.

FlorentTorregrosa commented 1 year ago

About "Also I realized that all the modules enabled in my current instance are loaded, not only the one listed in the psalm config.". This is because in vendor/mortenson/psalm-plugin-drupal/scripts/PsalmDrupalKernel.php::getPsalmModuleList:

 foreach ($xml->xpath('//pluginClass[@class="mortenson\PsalmPluginDrupal\Plugin"]/extensions/module') as $node) {

        }

This xpath expression is not working (at least for me). If I dump the $xml I have my xml file loaded but not getting in the foreach.

Edit:

Found the problem. at the beginning of my psalm.xml I have:

<psalm
  ...
  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"
>

The xpath does not like those 3 lines.

FlorentTorregrosa commented 1 year ago

WTF, and now it is working...

Edit: I mean, without those 3 lines, I do not have the problem of service not found... Checking on somewhere else than my computer.

FlorentTorregrosa commented 1 year ago

Checked on some else project. I was those 3 lines...