Skip to content

Commit effa1e1

Browse files
committed
Send internal errors with stack traces to PHPStan Pro
1 parent f76d836 commit effa1e1

File tree

10 files changed

+108
-18
lines changed

10 files changed

+108
-18
lines changed

src/Analyser/Analyser.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ public function analyse(
106106
"\n",
107107
'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml',
108108
);
109-
$errors[] = (new Error($internalErrorMessage, $file, null, $t))->withIdentifier('phpstan.internal');
109+
$errors[] = (new Error($internalErrorMessage, $file, null, $t))
110+
->withIdentifier('phpstan.internal')
111+
->withMetadata([
112+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($t),
113+
]);
110114
if ($internalErrorsCount >= $this->internalErrorsCountLimit) {
111115
$reachedInternalErrorsCountLimit = true;
112116
break;

src/Analyser/AnalyserResultFinalizer.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles): Final
4646
try {
4747
$ruleErrors = $rule->processNode($node, $scope);
4848
} catch (AnalysedCodeException $e) {
49-
$tempCollectorErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))->withIdentifier('phpstan.internal');
49+
$tempCollectorErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))
50+
->withIdentifier('phpstan.internal')
51+
->withMetadata([
52+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
53+
]);
5054
continue;
5155
} catch (IdentifierNotFound $e) {
5256
$tempCollectorErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'))->withIdentifier('phpstan.reflection');

src/Analyser/Error.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,30 @@ public function withIdentifier(string $identifier): self
184184
);
185185
}
186186

187+
/**
188+
* @param mixed[] $metadata
189+
*/
190+
public function withMetadata(array $metadata): self
191+
{
192+
if ($this->metadata !== []) {
193+
throw new ShouldNotHappenException('Error already has metadata');
194+
}
195+
196+
return new self(
197+
$this->message,
198+
$this->file,
199+
$this->line,
200+
$this->canBeIgnored,
201+
$this->filePath,
202+
$this->traitFilePath,
203+
$this->tip,
204+
$this->nodeLine,
205+
$this->nodeType,
206+
$this->identifier,
207+
$metadata,
208+
);
209+
}
210+
187211
public function getNodeLine(): ?int
188212
{
189213
return $this->nodeLine;

src/Analyser/FileAnalyser.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@ public function analyseFile(
115115
}
116116

117117
$uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true;
118-
$fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))->withIdentifier('phpstan.internal');
118+
$fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))
119+
->withIdentifier('phpstan.internal')
120+
->withMetadata([
121+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
122+
]);
119123
continue;
120124
} catch (IdentifierNotFound $e) {
121125
$fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'))->withIdentifier('phpstan.reflection');
@@ -139,7 +143,11 @@ public function analyseFile(
139143
}
140144

141145
$uniquedAnalysedCodeExceptionMessages[$e->getMessage()] = true;
142-
$fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))->withIdentifier('phpstan.internal');
146+
$fileErrors[] = (new Error($e->getMessage(), $file, $node->getStartLine(), $e, null, null, $e->getTip()))
147+
->withIdentifier('phpstan.internal')
148+
->withMetadata([
149+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
150+
]);
143151
continue;
144152
} catch (IdentifierNotFound $e) {
145153
$fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, $node->getStartLine(), $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'))->withIdentifier('phpstan.reflection');
@@ -205,7 +213,11 @@ public function analyseFile(
205213
$fileErrors[] = (new Error($error->getMessage(), $e->getParsedFile() ?? $file, $error->getLine() !== -1 ? $error->getStartLine() : null, $e))->withIdentifier('phpstan.parse');
206214
}
207215
} catch (AnalysedCodeException $e) {
208-
$fileErrors[] = (new Error($e->getMessage(), $file, null, $e, null, null, $e->getTip()))->withIdentifier('phpstan.internal');
216+
$fileErrors[] = (new Error($e->getMessage(), $file, null, $e, null, null, $e->getTip()))
217+
->withIdentifier('phpstan.internal')
218+
->withMetadata([
219+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
220+
]);
209221
} catch (IdentifierNotFound $e) {
210222
$fileErrors[] = (new Error(sprintf('Reflection error: %s not found.', $e->getIdentifier()->getName()), $file, null, $e, null, null, 'Learn more at https://phpstan.org/user-guide/discovering-symbols'))->withIdentifier('phpstan.reflection');
211223
} catch (UnableToCompileNode | CircularReference $e) {

src/Analyser/InternalError.php

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,65 @@
44

55
use JsonSerializable;
66
use ReturnTypeWillChange;
7+
use Throwable;
8+
use function array_map;
9+
use function array_unshift;
710

11+
/**
12+
* @phpstan-type Trace = list<array{file: string|null, line: int|null}>
13+
*/
814
class InternalError implements JsonSerializable
915
{
1016

17+
public const STACK_TRACE_METADATA_KEY = 'stackTrace';
18+
19+
/**
20+
* @param Trace $trace
21+
*/
1122
public function __construct(
1223
private string $message,
24+
private array $trace,
1325
)
1426
{
1527
}
1628

29+
/**
30+
* @return Trace
31+
*/
32+
public static function prepareTrace(Throwable $exception): array
33+
{
34+
$trace = array_map(static fn (array $trace) => [
35+
'file' => $trace['file'] ?? null,
36+
'line' => $trace['line'] ?? null,
37+
], $exception->getTrace());
38+
39+
array_unshift($trace, [
40+
'file' => $exception->getFile(),
41+
'line' => $exception->getLine(),
42+
]);
43+
44+
return $trace;
45+
}
46+
1747
public function getMessage(): string
1848
{
1949
return $this->message;
2050
}
2151

52+
/**
53+
* @return Trace
54+
*/
55+
public function getTrace(): array
56+
{
57+
return $this->trace;
58+
}
59+
2260
/**
2361
* @param mixed[] $json
2462
*/
2563
public static function decode(array $json): self
2664
{
27-
return new self($json['message']);
65+
return new self($json['message'], $json['trace']);
2866
}
2967

3068
/**
@@ -35,6 +73,7 @@ public function jsonSerialize()
3573
{
3674
return [
3775
'message' => $this->message,
76+
'trace' => $this->trace,
3877
];
3978
}
4079

src/Command/FixerApplication.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Nette\Utils\Json;
1212
use Phar;
1313
use PHPStan\Analyser\Ignore\IgnoredErrorHelper;
14+
use PHPStan\Analyser\InternalError;
1415
use PHPStan\File\FileMonitor;
1516
use PHPStan\File\FileMonitorResult;
1617
use PHPStan\File\FileReader;
@@ -297,7 +298,7 @@ private function downloadPhar(
297298
): void
298299
{
299300
$currentVersion = null;
300-
$branch = 'main';
301+
$branch = '1.1.x';
301302
if (is_file($pharPath) && is_file($infoPath)) {
302303
/** @var array{version: string, date: string, branch?: string} $currentInfo */
303304
$currentInfo = Json::decode(FileReader::read($infoPath), Json::FORCE_ARRAY);
@@ -479,7 +480,7 @@ private function analyse(
479480
$server->close();
480481
$output->writeln('<error>Worker process exited: ' . $e->getMessage() . '</error>');
481482
$phpstanFixerEncoder->write(['action' => 'analysisCrash', 'data' => [
482-
'errors' => [$e->getMessage()],
483+
'internalErrors' => [new InternalError($e->getMessage(), InternalError::prepareTrace($e))],
483484
]]);
484485
throw $e;
485486
});

src/Command/FixerWorkerCommand.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Analyser\Error;
99
use PHPStan\Analyser\Ignore\IgnoredErrorHelper;
1010
use PHPStan\Analyser\Ignore\IgnoredErrorHelperResult;
11+
use PHPStan\Analyser\InternalError;
1112
use PHPStan\Analyser\ResultCache\ResultCacheManager;
1213
use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory;
1314
use PHPStan\DependencyInjection\Container;
@@ -235,8 +236,8 @@ function (array $errors, array $locallyIgnoredErrors, array $analysedFiles) use
235236

236237
if ($hasInternalErrors) {
237238
$out->write(['action' => 'analysisCrash', 'data' => [
238-
'errors' => count($finalizerResult->getAnalyserResult()->getInternalErrors()) > 0 ? $finalizerResult->getAnalyserResult()->getInternalErrors() : [
239-
'Internal error occurred',
239+
'internalErrors' => count($finalizerResult->getAnalyserResult()->getInternalErrors()) > 0 ? $finalizerResult->getAnalyserResult()->getInternalErrors() : [
240+
new InternalError('Internal error occurred', []),
240241
],
241242
]]);
242243
}

src/Command/WorkerCommand.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ private function runWorker(
165165
'result' => [
166166
'errors' => [],
167167
'internalErrors' => [
168-
new InternalError($error->getMessage()),
168+
new InternalError($error->getMessage(), InternalError::prepareTrace($error)),
169169
],
170170
'filteredPhpErrors' => [],
171171
'allPhpErrors' => [],
@@ -186,7 +186,7 @@ private function runWorker(
186186
$fileAnalyser = $container->getByType(FileAnalyser::class);
187187
$ruleRegistry = $container->getByType(RuleRegistry::class);
188188
$collectorRegistry = $container->getByType(CollectorRegistry::class);
189-
$in->on('data', function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles, $output): void {
189+
$in->on('data', static function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles, $output): void {
190190
$action = $json['action'];
191191
if ($action !== 'analyse') {
192192
return;
@@ -236,7 +236,7 @@ private function runWorker(
236236
$internalErrorMessage .= sprintf('%sRun PHPStan with -v option and post the stack trace to:%s%s', "\n", "\n", $bugReportUrl);
237237
}
238238

239-
$internalErrors[] = new InternalError($internalErrorMessage);
239+
$internalErrors[] = new InternalError($internalErrorMessage, InternalError::prepareTrace($t));
240240
}
241241
}
242242

src/Parallel/ParallelAnalyser.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public function analyse(
9090
$server = new TcpServer('127.0.0.1:0', $loop);
9191
$this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$exportedNodes, &$peakMemoryUsages): void {
9292
if (count($jobs) > 0 && $internalErrorsCount === 0) {
93-
$internalErrors[] = new InternalError('Some parallel worker jobs have not finished.');
93+
$internalErrors[] = new InternalError('Some parallel worker jobs have not finished.', []);
9494
$internalErrorsCount++;
9595
}
9696

@@ -139,7 +139,7 @@ public function analyse(
139139
$serverPort = parse_url($serverAddress, PHP_URL_PORT);
140140

141141
$handleError = function (Throwable $error) use (&$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit): void {
142-
$internalErrors[] = new InternalError($error->getMessage());
142+
$internalErrors[] = new InternalError($error->getMessage(), InternalError::prepareTrace($error));
143143
$internalErrorsCount++;
144144
$reachedInternalErrorsCountLimit = true;
145145
$this->processPool->quitAll();
@@ -289,12 +289,12 @@ public function analyse(
289289
$memoryLimitMessage,
290290
ini_get('memory_limit'),
291291
'Increase your memory limit in php.ini or run PHPStan with --memory-limit CLI option.',
292-
));
292+
), []);
293293
$internalErrorsCount++;
294294
return;
295295
}
296296

297-
$internalErrors[] = new InternalError(sprintf('<error>Child process error</error> (exit code %d): %s', $exitCode, $output));
297+
$internalErrors[] = new InternalError(sprintf('<error>Child process error</error> (exit code %d): %s', $exitCode, $output), []);
298298
$internalErrorsCount++;
299299
});
300300
$this->processPool->attachProcess($processIdentifier, $process);

src/PhpDoc/StubValidator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Analyser\Error;
66
use PHPStan\Analyser\FileAnalyser;
7+
use PHPStan\Analyser\InternalError;
78
use PHPStan\Analyser\NodeScopeResolver;
89
use PHPStan\Broker\Broker;
910
use PHPStan\Collectors\Registry as CollectorRegistry;
@@ -129,7 +130,11 @@ static function (): void {
129130
}
130131

131132
$internalErrorMessage = sprintf('Internal error: %s', $e->getMessage());
132-
$errors[] = (new Error($internalErrorMessage, $stubFile, null, $e))->withIdentifier('phpstan.internal');
133+
$errors[] = (new Error($internalErrorMessage, $stubFile, null, $e))
134+
->withIdentifier('phpstan.internal')
135+
->withMetadata([
136+
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($e),
137+
]);
133138
}
134139
}
135140

0 commit comments

Comments
 (0)