From 92313447d6234af021a43643bb4ad00d19fe5b42 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 27 Jan 2021 15:02:19 +0100 Subject: [PATCH] Filter out exclude-from-classmap rules to avoid generating very long regexes, fixes #9487 --- src/Composer/Autoload/AutoloadGenerator.php | 27 +++++++++++++++++-- src/Composer/Autoload/ClassMapGenerator.php | 2 +- .../Test/Autoload/AutoloadGeneratorTest.php | 10 +++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 863ceac43..88d7046c6 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -231,7 +231,7 @@ EOF; $excluded = null; if (!empty($autoloads['exclude-from-classmap'])) { - $excluded = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; + $excluded = $autoloads['exclude-from-classmap']; } $classMap = array(); @@ -350,8 +350,31 @@ EOF; return $classMap; } + /** + * @param ?array $excluded + */ private function generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, $showAmbiguousWarning, array &$scannedFiles) { + if ($excluded) { + // filter excluded patterns here to only use those matching $dir + // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work + // if $dir does not exist, it should anyway not find anything there so no trouble + if (file_exists($dir)) { + // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other + $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/')); + foreach ($excluded as $index => $pattern) { + // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character + $pattern = preg_replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern); + // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it + if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) { + unset($excluded[$index]); + } + } + } + + $excluded = $excluded ? '{(' . implode('|', $excluded) . ')}' : null; + } + return ClassMapGenerator::createMap($dir, $excluded, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles); } @@ -458,7 +481,7 @@ EOF; if (isset($autoloads['classmap'])) { $excluded = null; if (!empty($autoloads['exclude-from-classmap'])) { - $excluded = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; + $excluded = $autoloads['exclude-from-classmap']; } $scannedFiles = array(); diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 4adbcc8be..0724d5dd6 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -51,7 +51,7 @@ class ClassMapGenerator * Iterate over all files in the given directory searching for classes * * @param \Iterator|string $path The path to search in or an iterator - * @param string $excluded Regex that matches against the file path that exclude from the classmap. + * @param string $excluded Regex that matches file paths to be excluded from the classmap * @param IOInterface $io IO object * @param string $namespace Optional namespace prefix to filter by * @param string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 3cd8f7ff2..1811d06c0 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -1377,9 +1377,9 @@ EOF; $package->setAutoload(array( 'psr-0' => array('Foo' => '../path/../src'), 'psr-4' => array('Acme\Foo\\' => '../path/../src-psr4'), - 'classmap' => array('../classmap'), + 'classmap' => array('../classmap', '../classmap2/subdir', 'classmap3', 'classmap4'), 'files' => array('../test.php'), - 'exclude-from-classmap' => array('./../classmap/excluded'), + 'exclude-from-classmap' => array('./../classmap/excluded', '../classmap2', 'classmap3/classes.php', 'classmap4/*/classes.php'), )); $this->repository->expects($this->once()) @@ -1388,9 +1388,15 @@ EOF; $this->fs->ensureDirectoryExists($this->workingDir.'/src/Foo'); $this->fs->ensureDirectoryExists($this->workingDir.'/classmap/excluded'); + $this->fs->ensureDirectoryExists($this->workingDir.'/classmap2/subdir'); + $this->fs->ensureDirectoryExists($this->workingDir.'/working-dir/classmap3'); + $this->fs->ensureDirectoryExists($this->workingDir.'/working-dir/classmap4/foo/'); file_put_contents($this->workingDir.'/src/Foo/Bar.php', 'workingDir.'/classmap/classes.php', 'workingDir.'/classmap/excluded/classes.php', 'workingDir.'/classmap2/subdir/classes.php', 'workingDir.'/working-dir/classmap3/classes.php', 'workingDir.'/working-dir/classmap4/foo/classes.php', 'workingDir.'/test.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_14');