Skip to content

Commit 96c70c5

Browse files
committed
Helpful debugging in case of crashes
1 parent 5afb019 commit 96c70c5

File tree

7 files changed

+149
-15
lines changed

7 files changed

+149
-15
lines changed

build/phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ parameters:
6868
- 'PHPStan\Reflection\MissingStaticAccessorInstanceException'
6969
- 'LogicException'
7070
- 'Error'
71+
- 'PHPStan\Analyser\Generator\TrampolineException'
7172
check:
7273
missingCheckedExceptionInThrows: true
7374
tooWideThrowType: true

src/Analyser/Generator/ExprAnalysisRequest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
use PhpParser\Node\Stmt;
88
use PHPStan\Analyser\ExpressionContext;
99
use PHPStan\Analyser\Scope;
10+
use function debug_backtrace;
11+
use const DEBUG_BACKTRACE_IGNORE_ARGS;
1012

1113
final class ExprAnalysisRequest
1214
{
1315

16+
public ?string $originFile = null;
17+
18+
public ?int $originLine = null;
19+
1420
/**
1521
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
1622
*/
@@ -22,6 +28,9 @@ public function __construct(
2228
public readonly mixed $alternativeNodeCallback = null,
2329
)
2430
{
31+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
32+
$this->originFile = $trace[0]['file'] ?? null;
33+
$this->originLine = $trace[0]['line'] ?? null;
2534
}
2635

2736
}

src/Analyser/Generator/GeneratorNodeScopeResolver.php

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
use PHPStan\NeverException;
1515
use PHPStan\Node\Printer\ExprPrinter;
1616
use PHPStan\ShouldNotHappenException;
17+
use Throwable;
1718
use function array_map;
1819
use function array_merge;
1920
use function array_pop;
2021
use function count;
2122
use function get_class;
2223
use function get_debug_type;
2324
use function implode;
25+
use function is_array;
2426
use function sprintf;
2527

2628
/**
@@ -111,17 +113,68 @@ private function processStmtNodes(
111113
StatementContext $context,
112114
): StmtAnalysisResult
113115
{
116+
$gen = new IdentifiedGeneratorInStack($this->analyseStmts($stmts, $scope, $context, null), $stmts, null, null);
117+
$gen->generator->current();
118+
114119
$stack = [];
115120

116-
$gen = $this->analyseStmts($stmts, $scope, $context, null);
117-
$gen->current();
121+
try {
122+
return $this->runTrampoline(
123+
$fibersStorage,
124+
$exprAnalysisResultStorage,
125+
$gen,
126+
$nodeCallback,
127+
$stack,
128+
);
129+
} catch (Throwable $e) {
130+
$stackTrace = [];
131+
foreach (array_merge($stack, [$gen]) as $identifiedGenerator) {
132+
if ($identifiedGenerator->file === null && $identifiedGenerator->line === null) {
133+
continue;
134+
}
135+
if (is_array($identifiedGenerator->node)) {
136+
$stackTrace[] = sprintf(
137+
"Stmts\n -> %s on line %d",
138+
$identifiedGenerator->file,
139+
$identifiedGenerator->line,
140+
);
141+
continue;
142+
}
143+
144+
$stackTrace[] = sprintf(
145+
"%s:%d\n -> %s on line %d",
146+
$identifiedGenerator->file,
147+
$identifiedGenerator->line,
148+
get_class($identifiedGenerator->node),
149+
$identifiedGenerator->node->getStartLine(),
150+
);
151+
}
152+
153+
throw new TrampolineException(sprintf(
154+
"Error ocurred in GNSR trampoline: %s\n\nAST processor stack trace:\n%s",
155+
$e->getMessage(),
156+
implode("\n", $stackTrace),
157+
), previous: $e);
158+
}
159+
}
118160

119-
// Trampoline loop
161+
/**
162+
* @param callable(Node, Scope): void $nodeCallback
163+
* @param list<IdentifiedGeneratorInStack> $stack
164+
*/
165+
private function runTrampoline(
166+
PendingFibersStorage $fibersStorage,
167+
ExprAnalysisResultStorage $exprAnalysisResultStorage,
168+
IdentifiedGeneratorInStack &$gen,
169+
callable $nodeCallback,
170+
array &$stack,
171+
): StmtAnalysisResult
172+
{
120173
while (true) {
121174
$this->processPendingFibers($fibersStorage, $exprAnalysisResultStorage);
122175

123-
if ($gen->valid()) {
124-
$yielded = $gen->current();
176+
if ($gen->generator->valid()) {
177+
$yielded = $gen->generator->current();
125178

126179
if ($yielded instanceof NodeCallbackRequest) {
127180
$this->invokeNodeCallback(
@@ -132,7 +185,7 @@ private function processStmtNodes(
132185
$nodeCallback,
133186
);
134187

135-
$gen->next();
188+
$gen->generator->next();
136189
continue;
137190
} elseif ($yielded instanceof AlternativeNodeCallbackRequest) {
138191
$alternativeNodeCallback = $yielded->nodeCallback;
@@ -146,29 +199,44 @@ static function (Node $node, Scope $scope) use ($alternativeNodeCallback, $nodeC
146199
},
147200
);
148201

149-
$gen->next();
202+
$gen->generator->next();
150203
continue;
151204
} elseif ($yielded instanceof ExprAnalysisRequest) {
152205
$stack[] = $gen;
153-
$gen = $this->analyseExpr($exprAnalysisResultStorage, $yielded->stmt, $yielded->expr, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback);
154-
$gen->current();
206+
$gen = new IdentifiedGeneratorInStack(
207+
$this->analyseExpr($exprAnalysisResultStorage, $yielded->stmt, $yielded->expr, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback),
208+
$yielded->expr,
209+
$yielded->originFile,
210+
$yielded->originLine,
211+
);
212+
$gen->generator->current();
155213
continue;
156214
} elseif ($yielded instanceof StmtAnalysisRequest) {
157215
$stack[] = $gen;
158-
$gen = $this->analyseStmt($yielded->stmt, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback);
159-
$gen->current();
216+
$gen = new IdentifiedGeneratorInStack(
217+
$this->analyseStmt($yielded->stmt, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback),
218+
$yielded->stmt,
219+
$yielded->originFile,
220+
$yielded->originLine,
221+
);
222+
$gen->generator->current();
160223
continue;
161224
} elseif ($yielded instanceof StmtsAnalysisRequest) {
162225
$stack[] = $gen;
163-
$gen = $this->analyseStmts($yielded->stmts, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback);
164-
$gen->current();
226+
$gen = new IdentifiedGeneratorInStack(
227+
$this->analyseStmts($yielded->stmts, $yielded->scope, $yielded->context, $yielded->alternativeNodeCallback),
228+
$yielded->stmts,
229+
$yielded->originFile,
230+
$yielded->originLine,
231+
);
232+
$gen->generator->current();
165233
continue;
166234
} else { // phpcs:ignore
167235
throw new NeverException($yielded);
168236
}
169237
}
170238

171-
$result = $gen->getReturn();
239+
$result = $gen->generator->getReturn();
172240
if (count($stack) === 0) {
173241
foreach ($fibersStorage->pendingFibers as $pending) {
174242
$request = $pending['request'];
@@ -200,7 +268,7 @@ static function () {
200268
}
201269

202270
$gen = array_pop($stack);
203-
$gen->send($result);
271+
$gen->generator->send($result);
204272
}
205273
}
206274

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator;
4+
5+
use Generator;
6+
use PhpParser\Node;
7+
8+
final class IdentifiedGeneratorInStack
9+
{
10+
11+
/**
12+
* @param (
13+
* Generator<int, StmtAnalysisRequest, StmtAnalysisResult, StmtAnalysisResult>| // analyseStmts
14+
* Generator<int, ExprAnalysisRequest|StmtAnalysisRequest|StmtsAnalysisRequest|NodeCallbackRequest|AlternativeNodeCallbackRequest, ExprAnalysisResult|StmtAnalysisResult, StmtAnalysisResult>| // analyseStmt
15+
* Generator<int, ExprAnalysisRequest|NodeCallbackRequest|AlternativeNodeCallbackRequest, ExprAnalysisResult, ExprAnalysisResult> // analyseExpr
16+
* ) $generator
17+
* @param Node|Node[] $node
18+
*/
19+
public function __construct(
20+
public Generator $generator,
21+
public Node|array $node,
22+
public ?string $file,
23+
public ?int $line,
24+
)
25+
{
26+
}
27+
28+
}

src/Analyser/Generator/StmtAnalysisRequest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
use PhpParser\Node\Stmt;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Analyser\StatementContext;
9+
use function debug_backtrace;
10+
use const DEBUG_BACKTRACE_IGNORE_ARGS;
911

1012
final class StmtAnalysisRequest
1113
{
1214

15+
public ?string $originFile = null;
16+
17+
public ?int $originLine = null;
18+
1319
/**
1420
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
1521
*/
@@ -20,6 +26,9 @@ public function __construct(
2026
public readonly mixed $alternativeNodeCallback = null,
2127
)
2228
{
29+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
30+
$this->originFile = $trace[0]['file'] ?? null;
31+
$this->originLine = $trace[0]['line'] ?? null;
2332
}
2433

2534
}

src/Analyser/Generator/StmtsAnalysisRequest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
use PhpParser\Node\Stmt;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Analyser\StatementContext;
9+
use function debug_backtrace;
10+
use const DEBUG_BACKTRACE_IGNORE_ARGS;
911

1012
final class StmtsAnalysisRequest
1113
{
1214

15+
public ?string $originFile = null;
16+
17+
public ?int $originLine = null;
18+
1319
/**
1420
* @param Stmt[] $stmts
1521
* @param (callable(Node, Scope, callable(Node, Scope): void): void)|null $alternativeNodeCallback
@@ -21,6 +27,9 @@ public function __construct(
2127
public readonly mixed $alternativeNodeCallback = null,
2228
)
2329
{
30+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
31+
$this->originFile = $trace[0]['file'] ?? null;
32+
$this->originLine = $trace[0]['line'] ?? null;
2433
}
2534

2635
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser\Generator;
4+
5+
use Exception;
6+
7+
final class TrampolineException extends Exception
8+
{
9+
10+
}

0 commit comments

Comments
 (0)