From 44f9a695cd7223844ffaa88a2d23b3f7ce658db0 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Wed, 9 Feb 2022 08:50:14 +0100 Subject: [PATCH] [Twig taint analysis] Support `AbstractController::render()` and Symfony+custom twig extensions (#245) * Allow analysing twig templates with custom extensions * Add support for AbstractController::render() and ::renderView() --- src/Plugin.php | 16 ++++++++++++++++ src/Twig/AnalyzedTemplatesTainter.php | 3 ++- src/Twig/TemplateFileAnalyzer.php | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Plugin.php b/src/Plugin.php index 9c02228..f11eb2f 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -22,6 +22,7 @@ use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesMapping; use Psalm\SymfonyPsalmPlugin\Twig\CachedTemplatesTainter; use Psalm\SymfonyPsalmPlugin\Twig\TemplateFileAnalyzer; use SimpleXMLElement; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\HttpKernel\Kernel; /** @@ -65,6 +66,21 @@ class Plugin implements PluginEntryPointInterface $containerMeta = new ContainerMeta((array) $config->containerXml); ContainerHandler::init($containerMeta); + try { + TemplateFileAnalyzer::initExtensions(array_filter(array_map(function (array $m) use ($containerMeta) { + if ('addExtension' !== $m[0]) { + return null; + } + + try { + return $containerMeta->get($m[1][0])->getClass(); + } catch (ServiceNotFoundException $e) { + return null; + } + }, $containerMeta->get('twig')->getMethodCalls()))); + } catch (ServiceNotFoundException $e) { + } + require_once __DIR__.'/Handler/ParameterBagHandler.php'; ParameterBagHandler::init($containerMeta); $api->registerHooksFromClass(ParameterBagHandler::class); diff --git a/src/Twig/AnalyzedTemplatesTainter.php b/src/Twig/AnalyzedTemplatesTainter.php index ed41181..9e115c6 100644 --- a/src/Twig/AnalyzedTemplatesTainter.php +++ b/src/Twig/AnalyzedTemplatesTainter.php @@ -17,6 +17,7 @@ use Psalm\StatementsSource; use Psalm\SymfonyPsalmPlugin\Exception\TemplateNameUnresolvedException; use Psalm\Type\Atomic\TKeyedArray; use RuntimeException; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Twig\Environment; /** @@ -34,7 +35,7 @@ class AnalyzedTemplatesTainter implements AfterMethodCallAnalysisInterface if ( null === $codebase->taint_flow_graph - || !$expr instanceof MethodCall || $method_id !== Environment::class.'::render' || empty($expr->args) + || !$expr instanceof MethodCall || !\in_array($method_id, [Environment::class.'::render', AbstractController::class.'::render', AbstractController::class.'::renderView'], true) || empty($expr->args) || !isset($expr->args[0]->value) || !isset($expr->args[1]->value) ) { diff --git a/src/Twig/TemplateFileAnalyzer.php b/src/Twig/TemplateFileAnalyzer.php index a05084b..0ad37c2 100644 --- a/src/Twig/TemplateFileAnalyzer.php +++ b/src/Twig/TemplateFileAnalyzer.php @@ -7,6 +7,7 @@ namespace Psalm\SymfonyPsalmPlugin\Twig; use Psalm\Context as PsalmContext; use Psalm\Internal\Analyzer\FileAnalyzer; use Twig\Environment; +use Twig\Extension\ExtensionInterface; use Twig\Loader\FilesystemLoader; use Twig\NodeTraverser; @@ -22,6 +23,16 @@ class TemplateFileAnalyzer extends FileAnalyzer */ private static $rootPath = 'templates'; + /** + * @var list + */ + private static $extensionClasses = []; + + public static function initExtensions(array $extensionClasses): void + { + self::$extensionClasses = $extensionClasses; + } + public function analyze( PsalmContext $file_context = null, PsalmContext $global_context = null @@ -41,6 +52,11 @@ class TemplateFileAnalyzer extends FileAnalyzer 'optimizations' => 0, 'strict_variables' => false, ]); + foreach (self::$extensionClasses as $extensionClass) { + if (class_exists($extensionClass) && is_a($extensionClass, ExtensionInterface::class, true)) { + $twig->addExtension((new \ReflectionClass($extensionClass))->newInstanceWithoutConstructor()); + } + } $local_file_name = str_replace(self::$rootPath.'/', '', $this->file_name); $twig_source = $loader->getSourceContext($local_file_name);