diff --git a/src/Analyser/Analyser.php b/src/Analyser/Analyser.php index 3a464c00c0..ed161671c6 100644 --- a/src/Analyser/Analyser.php +++ b/src/Analyser/Analyser.php @@ -11,7 +11,6 @@ use function array_merge; use function count; use function memory_get_peak_usage; -use function sprintf; class Analyser { @@ -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), diff --git a/src/Analyser/InternalError.php b/src/Analyser/InternalError.php index f760b1aaf7..d14347a809 100644 --- a/src/Analyser/InternalError.php +++ b/src/Analyser/InternalError.php @@ -23,7 +23,9 @@ class InternalError implements JsonSerializable */ public function __construct( private string $message, + private string $contextDescription, private array $trace, + private ?string $traceAsString, ) { } @@ -51,6 +53,11 @@ public function getMessage(): string return $this->message; } + public function getContextDescription(): string + { + return $this->contextDescription; + } + /** * @return Trace */ @@ -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']); } /** @@ -75,7 +87,9 @@ public function jsonSerialize() { return [ 'message' => $this->message, + 'contextDescription' => $this->contextDescription, 'trace' => $this->trace, + 'traceAsString' => $this->traceAsString, ]; } diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php index 513a9b8697..d39a886870 100644 --- a/src/Command/AnalyseCommand.php +++ b/src/Command/AnalyseCommand.php @@ -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; @@ -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; @@ -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); diff --git a/src/Command/AnalysisResult.php b/src/Command/AnalysisResult.php index 96bd377c0e..f7c4424284 100644 --- a/src/Command/AnalysisResult.php +++ b/src/Command/AnalysisResult.php @@ -81,6 +81,7 @@ public function getNotFileSpecificErrors(): array } /** + * @deprecated Use getInternalErrorObjects * @return list */ public function getInternalErrors(): array @@ -88,6 +89,14 @@ public function getInternalErrors(): array return array_map(static fn (InternalError $internalError) => $internalError->getMessage(), $this->internalErrors); } + /** + * @return list + */ + public function getInternalErrorObjects(): array + { + return $this->internalErrors; + } + /** * @return list */ diff --git a/src/Command/FixerApplication.php b/src/Command/FixerApplication.php index 9e6d0949d6..9c0d2de414 100644 --- a/src/Command/FixerApplication.php +++ b/src/Command/FixerApplication.php @@ -480,7 +480,12 @@ private function analyse( $server->close(); $output->writeln('Worker process exited: ' . $e->getMessage() . ''); $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; }); diff --git a/src/Command/FixerWorkerCommand.php b/src/Command/FixerWorkerCommand.php index c53a0d6c03..f28f8a30f7 100644 --- a/src/Command/FixerWorkerCommand.php +++ b/src/Command/FixerWorkerCommand.php @@ -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, + ), ], ]]); } diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index b9fd65336d..60a0f626ad 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -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' => [], @@ -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; @@ -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(), + ); } } diff --git a/src/Parallel/ParallelAnalyser.php b/src/Parallel/ParallelAnalyser.php index 9d39404770..cebcc7618f 100644 --- a/src/Parallel/ParallelAnalyser.php +++ b/src/Parallel/ParallelAnalyser.php @@ -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++; } @@ -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(); @@ -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('Child process error (exit code %d): %s', $exitCode, $output), []); + $internalErrors[] = new InternalError(sprintf('Child process error (exit code %d): %s', $exitCode, $output), 'running parallel worker', [], null); $internalErrorsCount++; }); $this->processPool->attachProcess($processIdentifier, $process);