Skip to content

Commit

Permalink
When internal errors occur, do not show other reported errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 16, 2024
1 parent c90dcdd commit aa92113
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 59 deletions.
10 changes: 1 addition & 9 deletions src/Analyser/Analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use function array_merge;
use function count;
use function memory_get_peak_usage;
use function sprintf;

class Analyser
{
Expand Down Expand Up @@ -99,14 +98,7 @@ public function analyse(
throw $t;
}
$internalErrorsCount++;
$internalErrorMessage = sprintf('Internal error: %s', $t->getMessage());
$internalErrorMessage .= sprintf(
'%sRun PHPStan with --debug option and post the stack trace to:%s%s',
"\n",
"\n",
'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml',
);
$errors[] = (new Error($internalErrorMessage, $file, null, $t))
$errors[] = (new Error($t->getMessage(), $file, null, $t))
->withIdentifier('phpstan.internal')
->withMetadata([
InternalError::STACK_TRACE_METADATA_KEY => InternalError::prepareTrace($t),
Expand Down
16 changes: 15 additions & 1 deletion src/Analyser/InternalError.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class InternalError implements JsonSerializable
*/
public function __construct(
private string $message,
private string $contextDescription,
private array $trace,
private ?string $traceAsString,
)
{
}
Expand Down Expand Up @@ -51,6 +53,11 @@ public function getMessage(): string
return $this->message;
}

public function getContextDescription(): string
{
return $this->contextDescription;
}

/**
* @return Trace
*/
Expand All @@ -59,12 +66,17 @@ public function getTrace(): array
return $this->trace;
}

public function getTraceAsString(): ?string
{
return $this->traceAsString;
}

/**
* @param mixed[] $json
*/
public static function decode(array $json): self
{
return new self($json['message'], $json['trace']);
return new self($json['message'], $json['contextDescription'], $json['trace'], $json['traceAsString']);
}

/**
Expand All @@ -75,7 +87,9 @@ public function jsonSerialize()
{
return [
'message' => $this->message,
'contextDescription' => $this->contextDescription,
'trace' => $this->trace,
'traceAsString' => $this->traceAsString,
];
}

Expand Down
125 changes: 96 additions & 29 deletions src/Command/AnalyseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Command;

use OndraM\CiDetector\CiDetector;
use PHPStan\Analyser\InternalError;
use PHPStan\Command\ErrorFormatter\BaselineNeonErrorFormatter;
use PHPStan\Command\ErrorFormatter\BaselinePhpErrorFormatter;
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
Expand Down Expand Up @@ -328,13 +329,108 @@ protected function execute(InputInterface $input, OutputInterface $output): int
throw $t;
}

$internalErrors = [];
foreach ($analysisResult->getInternalErrorObjects() as $internalError) {
$internalErrors[$internalError->getMessage()] = new InternalError(
sprintf('Internal error: %s', $internalError->getMessage()),
$internalError->getContextDescription(),
$internalError->getTrace(),
$internalError->getTraceAsString(),
);
}
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
if (!$fileSpecificError->hasNonIgnorableException()) {
continue;
}

$message = $fileSpecificError->getMessage();
if ($fileSpecificError->getIdentifier() === 'phpstan.internal') {
$message = sprintf('Internal error: %s', $message);
}

$metadata = $fileSpecificError->getMetadata();
$internalErrors[$fileSpecificError->getMessage()] = new InternalError(
$message,
sprintf('analysing file %s', $fileSpecificError->getTraitFilePath() ?? $fileSpecificError->getFilePath()),
$metadata[InternalError::STACK_TRACE_METADATA_KEY] ?? [],
$metadata[InternalError::STACK_TRACE_AS_STRING_METADATA_KEY] ?? null,
);
}

$internalErrors = array_values($internalErrors);
$bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml';
foreach ($internalErrors as $i => $internalError) {
$message = sprintf('%s while %s', $internalError->getMessage(), $internalError->getContextDescription());
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$firstTraceItem = $internalError->getTrace()[0] ?? null;
$trace = '';
if ($firstTraceItem !== null && $firstTraceItem['file'] !== null && $firstTraceItem['line'] !== null) {
$trace = sprintf('## %s(%d)%s', $firstTraceItem['file'], $firstTraceItem['line'], "\n");
}
$trace .= $internalError->getTraceAsString();
$message .= sprintf('%sPost the following stack trace to %s: %s%s', "\n\n", $bugReportUrl, "\n", $trace);
} else {
$message .= sprintf('%sRun PHPStan with -v option and post the stack trace to:%s%s', "\n", "\n", $bugReportUrl);
}
$internalErrors[$i] = new InternalError(
$message,
$internalError->getContextDescription(),
$internalError->getTrace(),
$internalError->getTraceAsString(),
);
}

$internalErrors = array_values($internalErrors);

if ($generateBaselineFile !== null) {
if (count($internalErrors) > 0) {
foreach ($internalErrors as $internalError) {
$inceptionResult->getStdOutput()->writeLineFormatted($internalError->getMessage());
$inceptionResult->getStdOutput()->writeLineFormatted('');
}

$inceptionResult->getStdOutput()->getStyle()->error(sprintf(
'%s occurred. Baseline could not be generated.',
count($internalErrors) === 1 ? 'An internal error' : 'Internal errors',
));

return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes());
}

return $this->generateBaseline($generateBaselineFile, $inceptionResult, $analysisResult, $output, $allowEmptyBaseline, $baselineExtension, $failWithoutResultCache);
}

/** @var ErrorFormatter $errorFormatter */
$errorFormatter = $container->getService($errorFormatterServiceName);

if (count($internalErrors) > 0) {
$analysisResult = new AnalysisResult(
[],
array_map(static fn (InternalError $internalError) => $internalError->getMessage(), $internalErrors),
[],
[],
[],
$analysisResult->isDefaultLevelUsed(),
$analysisResult->getProjectConfigFile(),
$analysisResult->isResultCacheSaved(),
$analysisResult->getPeakMemoryUsageBytes(),
$analysisResult->isResultCacheUsed(),
$analysisResult->getChangedProjectExtensionFilesOutsideOfAnalysedPaths(),
);

$exitCode = $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput());

$errorOutput->writeLineFormatted('⚠️ Result is incomplete because of internal errors. ⚠️');
$errorOutput->writeLineFormatted(' Fix these errors first and then re-run PHPStan');
$errorOutput->writeLineFormatted(' to get all reported errors.');
$errorOutput->writeLineFormatted('');

return $inceptionResult->handleReturn(
$exitCode,
$analysisResult->getPeakMemoryUsageBytes(),
);
}

$exitCode = $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput());
if ($failWithoutResultCache && !$analysisResult->isResultCacheUsed()) {
$exitCode = 2;
Expand Down Expand Up @@ -413,35 +509,6 @@ private function generateBaseline(string $generateBaselineFile, InceptionResult

return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes());
}
if ($analysisResult->hasInternalErrors()) {
$internalErrors = array_values(array_unique($analysisResult->getInternalErrors()));

foreach ($internalErrors as $internalError) {
$inceptionResult->getStdOutput()->writeLineFormatted($internalError);
$inceptionResult->getStdOutput()->writeLineFormatted('');
}

$inceptionResult->getStdOutput()->getStyle()->error(sprintf(
'%s occurred. Baseline could not be generated.',
count($internalErrors) === 1 ? 'An internal error' : 'Internal errors',
));

return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes());
}

foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
if (!$fileSpecificError->hasNonIgnorableException()) {
continue;
}

$inceptionResult->getStdOutput()->getStyle()->error('An internal error occurred. Baseline could not be generated.');

$inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getMessage());
$inceptionResult->getStdOutput()->writeLineFormatted($fileSpecificError->getFile());
$inceptionResult->getStdOutput()->writeLineFormatted('');

return $inceptionResult->handleReturn(1, $analysisResult->getPeakMemoryUsageBytes());
}

$streamOutput = $this->createStreamOutput();
$errorConsoleStyle = new ErrorsConsoleStyle(new StringInput(''), $streamOutput);
Expand Down
9 changes: 9 additions & 0 deletions src/Command/AnalysisResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,22 @@ public function getNotFileSpecificErrors(): array
}

/**
* @deprecated Use getInternalErrorObjects
* @return list<string>
*/
public function getInternalErrors(): array
{
return array_map(static fn (InternalError $internalError) => $internalError->getMessage(), $this->internalErrors);
}

/**
* @return list<InternalError>
*/
public function getInternalErrorObjects(): array
{
return $this->internalErrors;
}

/**
* @return list<string>
*/
Expand Down
7 changes: 6 additions & 1 deletion src/Command/FixerApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,12 @@ private function analyse(
$server->close();
$output->writeln('<error>Worker process exited: ' . $e->getMessage() . '</error>');
$phpstanFixerEncoder->write(['action' => 'analysisCrash', 'data' => [
'internalErrors' => [new InternalError($e->getMessage(), InternalError::prepareTrace($e))],
'internalErrors' => [new InternalError(
$e->getMessage(),
'running PHPStan Pro worker',
InternalError::prepareTrace($e),
$e->getTraceAsString(),
)],
]]);
throw $e;
});
Expand Down
7 changes: 6 additions & 1 deletion src/Command/FixerWorkerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,12 @@ function (array $errors, array $locallyIgnoredErrors, array $analysedFiles) use
if ($hasInternalErrors) {
$out->write(['action' => 'analysisCrash', 'data' => [
'internalErrors' => count($finalizerResult->getAnalyserResult()->getInternalErrors()) > 0 ? $finalizerResult->getAnalyserResult()->getInternalErrors() : [
new InternalError('Internal error occurred', []),
new InternalError(
'Internal error occurred',
'running analyser in PHPStan Pro worker',
[],
null,
),
],
]]);
}
Expand Down
27 changes: 13 additions & 14 deletions src/Command/WorkerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,12 @@ private function runWorker(
'result' => [
'errors' => [],
'internalErrors' => [
new InternalError($error->getMessage(), InternalError::prepareTrace($error)),
new InternalError(
$error->getMessage(),
'communicating with main process in parallel worker',
InternalError::prepareTrace($error),
$error->getTraceAsString(),
),
],
'filteredPhpErrors' => [],
'allPhpErrors' => [],
Expand All @@ -186,7 +191,7 @@ private function runWorker(
$fileAnalyser = $container->getByType(FileAnalyser::class);
$ruleRegistry = $container->getByType(RuleRegistry::class);
$collectorRegistry = $container->getByType(CollectorRegistry::class);
$in->on('data', static function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles, $output): void {
$in->on('data', static function (array $json) use ($fileAnalyser, $ruleRegistry, $collectorRegistry, $out, $analysedFiles): void {
$action = $json['action'];
if ($action !== 'analyse') {
return;
Expand Down Expand Up @@ -225,18 +230,12 @@ private function runWorker(
}
} catch (Throwable $t) {
$internalErrorsCount++;
$internalErrorMessage = sprintf('Internal error: %s while analysing file %s', $t->getMessage(), $file);

$bugReportUrl = 'https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml';
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$trace = sprintf('## %s(%d)%s', $t->getFile(), $t->getLine(), "\n");
$trace .= $t->getTraceAsString();
$internalErrorMessage .= sprintf('%sPost the following stack trace to %s: %s%s', "\n\n", $bugReportUrl, "\n", $trace);
} else {
$internalErrorMessage .= sprintf('%sRun PHPStan with -v option and post the stack trace to:%s%s', "\n", "\n", $bugReportUrl);
}

$internalErrors[] = new InternalError($internalErrorMessage, InternalError::prepareTrace($t));
$internalErrors[] = new InternalError(
$t->getMessage(),
sprintf('analysing file %s', $file),
InternalError::prepareTrace($t),
$t->getTraceAsString(),
);
}
}

Expand Down
18 changes: 14 additions & 4 deletions src/Parallel/ParallelAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ public function analyse(
$server = new TcpServer('127.0.0.1:0', $loop);
$this->processPool = new ProcessPool($server, static function () use ($deferred, &$jobs, &$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit, &$errors, &$filteredPhpErrors, &$allPhpErrors, &$locallyIgnoredErrors, &$linesToIgnore, &$unmatchedLineIgnores, &$collectedData, &$dependencies, &$exportedNodes, &$peakMemoryUsages): void {
if (count($jobs) > 0 && $internalErrorsCount === 0) {
$internalErrors[] = new InternalError('Some parallel worker jobs have not finished.', []);
$internalErrors[] = new InternalError(
'Some parallel worker jobs have not finished.',
'running parallel worker',
[],
null,
);
$internalErrorsCount++;
}

Expand Down Expand Up @@ -139,7 +144,12 @@ public function analyse(
$serverPort = parse_url($serverAddress, PHP_URL_PORT);

$handleError = function (Throwable $error) use (&$internalErrors, &$internalErrorsCount, &$reachedInternalErrorsCountLimit): void {
$internalErrors[] = new InternalError($error->getMessage(), InternalError::prepareTrace($error));
$internalErrors[] = new InternalError(
$error->getMessage(),
'communicating with parallel worker',
InternalError::prepareTrace($error),
$error->getTraceAsString(),
);
$internalErrorsCount++;
$reachedInternalErrorsCountLimit = true;
$this->processPool->quitAll();
Expand Down Expand Up @@ -289,12 +299,12 @@ public function analyse(
$memoryLimitMessage,
ini_get('memory_limit'),
'Increase your memory limit in php.ini or run PHPStan with --memory-limit CLI option.',
), []);
), 'running parallel worker', [], null);
$internalErrorsCount++;
return;
}

$internalErrors[] = new InternalError(sprintf('<error>Child process error</error> (exit code %d): %s', $exitCode, $output), []);
$internalErrors[] = new InternalError(sprintf('<error>Child process error</error> (exit code %d): %s', $exitCode, $output), 'running parallel worker', [], null);
$internalErrorsCount++;
});
$this->processPool->attachProcess($processIdentifier, $process);
Expand Down

0 comments on commit aa92113

Please sign in to comment.