diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index be9b72ff74d..89f764c0da6 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -128,6 +128,10 @@ jobs: cd e2e/bug-14514 composer install ../../bin/phpstan analyze bug-14515.php + - script: | + cd e2e/bug-14724 + composer install + ../../bin/phpstan analyze app/ - script: | cd e2e/bug10449 ../../bin/phpstan analyze diff --git a/e2e/bug-14724/.gitignore b/e2e/bug-14724/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/e2e/bug-14724/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/bug-14724/README b/e2e/bug-14724/README new file mode 100644 index 00000000000..68670114dc5 --- /dev/null +++ b/e2e/bug-14724/README @@ -0,0 +1 @@ +this test case intentionally ships with a invalid composer.json (according to schema), as this is what made PHPStan crash at startup. diff --git a/e2e/bug-14724/app/classes/path-1/A.php b/e2e/bug-14724/app/classes/path-1/A.php new file mode 100644 index 00000000000..7643c3311a9 --- /dev/null +++ b/e2e/bug-14724/app/classes/path-1/A.php @@ -0,0 +1,3 @@ + (array) $namespacePaths, $package[$autoloadSection]['psr-4'] ?? []); + $psr4 = $package[$autoloadSection]['psr-4'] ?? []; + if (!is_array($psr4)) { + return []; // skip on invalid data + } + foreach ($psr4 as $key => $namespacePaths) { + $stringArray = $this->toStringArray($namespacePaths); + if (!is_string($key) || $stringArray === null) { + return []; // skip on invalid data + } + + $psr4[$key] = $stringArray; + } + return $psr4; } /** @@ -185,7 +199,19 @@ private function packageToPsr4AutoloadNamespaces(array $package, string $autoloa */ private function packageToPsr0AutoloadNamespaces(array $package, string $autoloadSection = 'autoload'): array { - return array_map(static fn ($namespacePaths): array => (array) $namespacePaths, $package[$autoloadSection]['psr-0'] ?? []); + $psr0 = $package[$autoloadSection]['psr-0'] ?? []; + if (!is_array($psr0)) { + return []; // skip on invalid data + } + foreach ($psr0 as $key => $namespacePaths) { + $stringArray = $this->toStringArray($namespacePaths); + if (!is_string($key) || $stringArray === null) { + return []; // skip on invalid data + } + + $psr0[$key] = $stringArray; + } + return $psr0; } /** @@ -195,7 +221,7 @@ private function packageToPsr0AutoloadNamespaces(array $package, string $autoloa */ private function packageToClassMapPaths(array $package, string $autoloadSection = 'autoload'): array { - return $package[$autoloadSection]['classmap'] ?? []; + return $this->toStringArray($package[$autoloadSection]['classmap'] ?? []) ?? []; } /** @@ -205,7 +231,7 @@ private function packageToClassMapPaths(array $package, string $autoloadSection */ private function packageToFilePaths(array $package, string $autoloadSection = 'autoload'): array { - return $package[$autoloadSection]['files'] ?? []; + return $this->toStringArray($package[$autoloadSection]['files'] ?? []) ?? []; } /** @@ -257,4 +283,22 @@ private function prefixPaths(array $paths, string $prefix): array return array_map(static fn (string $path): string => $prefix . $path, $paths); } + /** + * @param array|string $stringOrArray + * @return array|null + */ + private function toStringArray(array|string $stringOrArray): ?array + { + if (is_string($stringOrArray)) { + return (array) $stringOrArray; + } + + foreach ($stringOrArray as $stringOrArrayItem) { + if (!is_string($stringOrArrayItem)) { + return null; + } + } + return $stringOrArray; + } + }