Skip to content

Commit c8db9f9

Browse files
authored
PHPStan should not crash on startup when projects' composer.json is invalid (#5779)
1 parent f1a3110 commit c8db9f9

9 files changed

Lines changed: 98 additions & 4 deletions

File tree

.github/workflows/e2e-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ jobs:
128128
cd e2e/bug-14514
129129
composer install
130130
../../bin/phpstan analyze bug-14515.php
131+
- script: |
132+
cd e2e/bug-14724
133+
composer install
134+
../../bin/phpstan analyze app/
131135
- script: |
132136
cd e2e/bug10449
133137
../../bin/phpstan analyze

e2e/bug-14724/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor

e2e/bug-14724/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
this test case intentionally ships with a invalid composer.json (according to schema), as this is what made PHPStan crash at startup.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class A {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class B {
4+
public function doA(A $a) {}
5+
}

e2e/bug-14724/app/file.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
// intentional empty file

e2e/bug-14724/composer.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"autoload-dev": {
3+
"classmap": [
4+
[
5+
"app/classes/path-1",
6+
"app/classes/path-2"
7+
]
8+
],
9+
"files": [
10+
[
11+
"app/file.php"
12+
]
13+
]
14+
}
15+
}

e2e/bug-14724/composer.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Reflection/BetterReflection/SourceLocator/ComposerJsonAndInstalledJsonSourceLocatorMaker.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
use function count;
2222
use function dirname;
2323
use function glob;
24+
use function is_array;
2425
use function is_dir;
2526
use function is_file;
27+
use function is_string;
2628
use function str_contains;
2729
use const GLOB_ONLYDIR;
2830

@@ -175,7 +177,19 @@ public function create(string $projectInstallationPath): ?SourceLocator
175177
*/
176178
private function packageToPsr4AutoloadNamespaces(array $package, string $autoloadSection = 'autoload'): array
177179
{
178-
return array_map(static fn ($namespacePaths): array => (array) $namespacePaths, $package[$autoloadSection]['psr-4'] ?? []);
180+
$psr4 = $package[$autoloadSection]['psr-4'] ?? [];
181+
if (!is_array($psr4)) {
182+
return []; // skip on invalid data
183+
}
184+
foreach ($psr4 as $key => $namespacePaths) {
185+
$stringArray = $this->toStringArray($namespacePaths);
186+
if (!is_string($key) || $stringArray === null) {
187+
return []; // skip on invalid data
188+
}
189+
190+
$psr4[$key] = $stringArray;
191+
}
192+
return $psr4;
179193
}
180194

181195
/**
@@ -185,7 +199,19 @@ private function packageToPsr4AutoloadNamespaces(array $package, string $autoloa
185199
*/
186200
private function packageToPsr0AutoloadNamespaces(array $package, string $autoloadSection = 'autoload'): array
187201
{
188-
return array_map(static fn ($namespacePaths): array => (array) $namespacePaths, $package[$autoloadSection]['psr-0'] ?? []);
202+
$psr0 = $package[$autoloadSection]['psr-0'] ?? [];
203+
if (!is_array($psr0)) {
204+
return []; // skip on invalid data
205+
}
206+
foreach ($psr0 as $key => $namespacePaths) {
207+
$stringArray = $this->toStringArray($namespacePaths);
208+
if (!is_string($key) || $stringArray === null) {
209+
return []; // skip on invalid data
210+
}
211+
212+
$psr0[$key] = $stringArray;
213+
}
214+
return $psr0;
189215
}
190216

191217
/**
@@ -195,7 +221,7 @@ private function packageToPsr0AutoloadNamespaces(array $package, string $autoloa
195221
*/
196222
private function packageToClassMapPaths(array $package, string $autoloadSection = 'autoload'): array
197223
{
198-
return $package[$autoloadSection]['classmap'] ?? [];
224+
return $this->toStringArray($package[$autoloadSection]['classmap'] ?? []) ?? [];
199225
}
200226

201227
/**
@@ -205,7 +231,7 @@ private function packageToClassMapPaths(array $package, string $autoloadSection
205231
*/
206232
private function packageToFilePaths(array $package, string $autoloadSection = 'autoload'): array
207233
{
208-
return $package[$autoloadSection]['files'] ?? [];
234+
return $this->toStringArray($package[$autoloadSection]['files'] ?? []) ?? [];
209235
}
210236

211237
/**
@@ -257,4 +283,22 @@ private function prefixPaths(array $paths, string $prefix): array
257283
return array_map(static fn (string $path): string => $prefix . $path, $paths);
258284
}
259285

286+
/**
287+
* @param array<mixed>|string $stringOrArray
288+
* @return array<string>|null
289+
*/
290+
private function toStringArray(array|string $stringOrArray): ?array
291+
{
292+
if (is_string($stringOrArray)) {
293+
return (array) $stringOrArray;
294+
}
295+
296+
foreach ($stringOrArray as $stringOrArrayItem) {
297+
if (!is_string($stringOrArrayItem)) {
298+
return null;
299+
}
300+
}
301+
return $stringOrArray;
302+
}
303+
260304
}

0 commit comments

Comments
 (0)