Skip to content

Commit 5414391

Browse files
committed
Add $isAlwaysTerminating to every node-type if-branch
1 parent 06a98e0 commit 5414391

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,13 +2413,13 @@ public function processExprNode(Node\Stmt $stmt, Expr $expr, MutatingScope $scop
24132413
return $this->processExprNode($stmt, $newExpr, $scope, $nodeCallback, $context);
24142414
}
24152415

2416-
$isAlwaysTerminating = false;
24172416
$this->callNodeCallbackWithExpression($nodeCallback, $expr, $scope, $context);
24182417

24192418
if ($expr instanceof Variable) {
24202419
$hasYield = false;
24212420
$throwPoints = [];
24222421
$impurePoints = [];
2422+
$isAlwaysTerminating = false;
24232423
if ($expr->name instanceof Expr) {
24242424
return $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep());
24252425
} elseif (in_array($expr->name, Scope::SUPERGLOBAL_VARIABLES, true)) {
@@ -2537,6 +2537,7 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context): Exp
25372537
$functionReflection = null;
25382538
$throwPoints = [];
25392539
$impurePoints = [];
2540+
$isAlwaysTerminating = false;
25402541
if ($expr->name instanceof Expr) {
25412542
$nameType = $scope->getType($expr->name);
25422543
if (!$nameType->isCallable()->no()) {
@@ -2940,6 +2941,7 @@ static function (): void {
29402941
$hasYield = false;
29412942
$throwPoints = [];
29422943
$impurePoints = [];
2944+
$isAlwaysTerminating = false;
29432945
if ($expr->class instanceof Expr) {
29442946
$objectClasses = $scope->getType($expr->class)->getObjectClassNames();
29452947
if (count($objectClasses) !== 1) {
@@ -3103,6 +3105,7 @@ static function (): void {
31033105
$hasYield = $result->hasYield();
31043106
$throwPoints = $result->getThrowPoints();
31053107
$impurePoints = $result->getImpurePoints();
3108+
$isAlwaysTerminating = false;
31063109
$scope = $result->getScope();
31073110
if ($expr->name instanceof Expr) {
31083111
$result = $this->processExprNode($stmt, $expr->name, $scope, $nodeCallback, $context->enterDeep());
@@ -3151,6 +3154,7 @@ static function (): void {
31513154
true,
31523155
),
31533156
];
3157+
$isAlwaysTerminating = false;
31543158
if ($expr->class instanceof Expr) {
31553159
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
31563160
$hasYield = $result->hasYield();
@@ -3189,6 +3193,7 @@ static function (): void {
31893193
$hasYield = $result->hasYield();
31903194
$throwPoints = $result->getThrowPoints();
31913195
$impurePoints = $result->getImpurePoints();
3196+
$isAlwaysTerminating = false;
31923197
$scope = $result->getScope();
31933198
} elseif ($expr instanceof Exit_) {
31943199
$hasYield = false;
@@ -3210,6 +3215,7 @@ static function (): void {
32103215
$hasYield = false;
32113216
$throwPoints = [];
32123217
$impurePoints = [];
3218+
$isAlwaysTerminating = false;
32133219
foreach ($expr->parts as $part) {
32143220
if (!$part instanceof Expr) {
32153221
continue;
@@ -3225,6 +3231,7 @@ static function (): void {
32253231
$hasYield = false;
32263232
$throwPoints = [];
32273233
$impurePoints = [];
3234+
$isAlwaysTerminating = false;
32283235
if ($expr->dim !== null) {
32293236
$result = $this->processExprNode($stmt, $expr->dim, $scope, $nodeCallback, $context->enterDeep());
32303237
$hasYield = $result->hasYield();
@@ -3243,6 +3250,7 @@ static function (): void {
32433250
$hasYield = false;
32443251
$throwPoints = [];
32453252
$impurePoints = [];
3253+
$isAlwaysTerminating = false;
32463254
foreach ($expr->items as $arrayItem) {
32473255
$itemNodes[] = new LiteralArrayItem($scope, $arrayItem);
32483256
$nodeCallback($arrayItem, $scope);
@@ -3324,6 +3332,7 @@ static function (): void {
33243332
$hasYield = $condResult->hasYield() || $rightResult->hasYield();
33253333
$throwPoints = array_merge($condResult->getThrowPoints(), $rightResult->getThrowPoints());
33263334
$impurePoints = array_merge($condResult->getImpurePoints(), $rightResult->getImpurePoints());
3335+
$isAlwaysTerminating = false;
33273336
} elseif ($expr instanceof BinaryOp) {
33283337
$result = $this->processExprNode($stmt, $expr->left, $scope, $nodeCallback, $context->enterDeep());
33293338
$scope = $result->getScope();
@@ -3356,11 +3365,13 @@ static function (): void {
33563365
true,
33573366
);
33583367
$hasYield = $result->hasYield();
3368+
$isAlwaysTerminating = false;
33593369
$scope = $result->getScope()->afterExtractCall();
33603370
} elseif ($expr instanceof Expr\Print_) {
33613371
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33623372
$throwPoints = $result->getThrowPoints();
33633373
$impurePoints = $result->getImpurePoints();
3374+
$isAlwaysTerminating = $result->isAlwaysTerminating();
33643375
$impurePoints[] = new ImpurePoint($scope, $expr, 'print', 'print', true);
33653376
$hasYield = $result->hasYield();
33663377

@@ -3369,6 +3380,7 @@ static function (): void {
33693380
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33703381
$throwPoints = $result->getThrowPoints();
33713382
$impurePoints = $result->getImpurePoints();
3383+
$isAlwaysTerminating = true;
33723384
$hasYield = $result->hasYield();
33733385

33743386
$exprType = $scope->getType($expr->expr);
@@ -3396,6 +3408,7 @@ static function (): void {
33963408
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
33973409
$throwPoints = $result->getThrowPoints();
33983410
$impurePoints = $result->getImpurePoints();
3411+
$isAlwaysTerminating = $result->isAlwaysTerminating();
33993412
$hasYield = $result->hasYield();
34003413

34013414
$scope = $result->getScope();
@@ -3406,6 +3419,7 @@ static function (): void {
34063419
$impurePoints = $result->getImpurePoints();
34073420
$impurePoints[] = new ImpurePoint($scope, $expr, 'eval', 'eval', true);
34083421
$hasYield = $result->hasYield();
3422+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34093423

34103424
$scope = $result->getScope();
34113425
} elseif ($expr instanceof Expr\YieldFrom) {
@@ -3421,6 +3435,7 @@ static function (): void {
34213435
true,
34223436
);
34233437
$hasYield = true;
3438+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34243439

34253440
$scope = $result->getScope();
34263441
} elseif ($expr instanceof BooleanNot) {
@@ -3429,7 +3444,10 @@ static function (): void {
34293444
$hasYield = $result->hasYield();
34303445
$throwPoints = $result->getThrowPoints();
34313446
$impurePoints = $result->getImpurePoints();
3447+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34323448
} elseif ($expr instanceof Expr\ClassConstFetch) {
3449+
$isAlwaysTerminating = false;
3450+
34333451
if ($expr->class instanceof Expr) {
34343452
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
34353453
$scope = $result->getScope();
@@ -3460,13 +3478,15 @@ static function (): void {
34603478
$hasYield = $result->hasYield();
34613479
$throwPoints = $result->getThrowPoints();
34623480
$impurePoints = $result->getImpurePoints();
3481+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34633482
$scope = $this->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions());
34643483
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr);
34653484
} elseif ($expr instanceof Expr\Isset_) {
34663485
$hasYield = false;
34673486
$throwPoints = [];
34683487
$impurePoints = [];
34693488
$nonNullabilityResults = [];
3489+
$isAlwaysTerminating = false;
34703490
foreach ($expr->vars as $var) {
34713491
$nonNullabilityResult = $this->ensureNonNullability($scope, $var);
34723492
$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var);
@@ -3475,6 +3495,7 @@ static function (): void {
34753495
$hasYield = $hasYield || $result->hasYield();
34763496
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
34773497
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
3498+
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating();
34783499
$nonNullabilityResults[] = $nonNullabilityResult;
34793500
}
34803501
foreach (array_reverse($expr->vars) as $var) {
@@ -3489,6 +3510,7 @@ static function (): void {
34893510
$hasYield = $result->hasYield();
34903511
$throwPoints = $result->getThrowPoints();
34913512
$impurePoints = $result->getImpurePoints();
3513+
$isAlwaysTerminating = $result->isAlwaysTerminating();
34923514
if ($expr->class instanceof Expr) {
34933515
$result = $this->processExprNode($stmt, $expr->class, $scope, $nodeCallback, $context->enterDeep());
34943516
$scope = $result->getScope();
@@ -3505,6 +3527,7 @@ static function (): void {
35053527
$hasYield = false;
35063528
$throwPoints = [];
35073529
$impurePoints = [];
3530+
$isAlwaysTerminating = false;
35083531
$className = null;
35093532
if ($expr->class instanceof Expr || $expr->class instanceof Name) {
35103533
if ($expr->class instanceof Expr) {
@@ -3629,6 +3652,7 @@ static function (): void {
36293652
$hasYield = $result->hasYield();
36303653
$throwPoints = $result->getThrowPoints();
36313654
$impurePoints = $result->getImpurePoints();
3655+
$isAlwaysTerminating = $result->isAlwaysTerminating();
36323656

36333657
$newExpr = $expr;
36343658
if ($expr instanceof Expr\PostInc) {
@@ -3657,20 +3681,23 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36573681
$ternaryCondResult = $this->processExprNode($stmt, $expr->cond, $scope, $nodeCallback, $context->enterDeep());
36583682
$throwPoints = $ternaryCondResult->getThrowPoints();
36593683
$impurePoints = $ternaryCondResult->getImpurePoints();
3684+
$isAlwaysTerminating = $ternaryCondResult->isAlwaysTerminating();
36603685
$ifTrueScope = $ternaryCondResult->getTruthyScope();
36613686
$ifFalseScope = $ternaryCondResult->getFalseyScope();
36623687
$ifTrueType = null;
36633688
if ($expr->if !== null) {
36643689
$ifResult = $this->processExprNode($stmt, $expr->if, $ifTrueScope, $nodeCallback, $context);
36653690
$throwPoints = array_merge($throwPoints, $ifResult->getThrowPoints());
36663691
$impurePoints = array_merge($impurePoints, $ifResult->getImpurePoints());
3692+
$isAlwaysTerminating = $isAlwaysTerminating || $ifResult->isAlwaysTerminating();
36673693
$ifTrueScope = $ifResult->getScope();
36683694
$ifTrueType = $ifTrueScope->getType($expr->if);
36693695
}
36703696

36713697
$elseResult = $this->processExprNode($stmt, $expr->else, $ifFalseScope, $nodeCallback, $context);
36723698
$throwPoints = array_merge($throwPoints, $elseResult->getThrowPoints());
36733699
$impurePoints = array_merge($impurePoints, $elseResult->getImpurePoints());
3700+
$isAlwaysTerminating = $isAlwaysTerminating || $elseResult->isAlwaysTerminating();
36743701
$ifFalseScope = $elseResult->getScope();
36753702

36763703
$condType = $scope->getType($expr->cond);
@@ -3695,7 +3722,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
36953722
return new ExpressionResult(
36963723
$finalScope,
36973724
$ternaryCondResult->hasYield(),
3698-
false,
3725+
$isAlwaysTerminating,
36993726
$throwPoints,
37003727
$impurePoints,
37013728
static fn (): MutatingScope => $finalScope->filterByTruthyValue($expr),
@@ -3715,17 +3742,20 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37153742
true,
37163743
),
37173744
];
3745+
$isAlwaysTerminating = false;
37183746
if ($expr->key !== null) {
37193747
$keyResult = $this->processExprNode($stmt, $expr->key, $scope, $nodeCallback, $context->enterDeep());
37203748
$scope = $keyResult->getScope();
37213749
$throwPoints = $keyResult->getThrowPoints();
37223750
$impurePoints = array_merge($impurePoints, $keyResult->getImpurePoints());
3751+
$isAlwaysTerminating = $isAlwaysTerminating || $keyResult->isAlwaysTerminating();
37233752
}
37243753
if ($expr->value !== null) {
37253754
$valueResult = $this->processExprNode($stmt, $expr->value, $scope, $nodeCallback, $context->enterDeep());
37263755
$scope = $valueResult->getScope();
37273756
$throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints());
37283757
$impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints());
3758+
$isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating();
37293759
}
37303760
$hasYield = true;
37313761
} elseif ($expr instanceof Expr\Match_) {
@@ -3736,6 +3766,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
37363766
$hasYield = $condResult->hasYield();
37373767
$throwPoints = $condResult->getThrowPoints();
37383768
$impurePoints = $condResult->getImpurePoints();
3769+
$isAlwaysTerminating = $condResult->isAlwaysTerminating();
37393770
$matchScope = $scope->enterMatch($expr);
37403771
$armNodes = [];
37413772
$hasDefaultCond = false;
@@ -3947,79 +3978,93 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
39473978
$hasYield = $result->hasYield();
39483979
$throwPoints = $result->getThrowPoints();
39493980
$impurePoints = $result->getImpurePoints();
3981+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39503982
$scope = $result->getScope();
39513983
} elseif ($expr instanceof Expr\Throw_) {
39523984
$hasYield = false;
39533985
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, ExpressionContext::createDeep());
39543986
$throwPoints = $result->getThrowPoints();
39553987
$impurePoints = $result->getImpurePoints();
3988+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39563989
$throwPoints[] = ThrowPoint::createExplicit($scope, $scope->getType($expr->expr), $expr, false);
39573990
} elseif ($expr instanceof FunctionCallableNode) {
39583991
$throwPoints = [];
39593992
$impurePoints = [];
39603993
$hasYield = false;
3994+
$isAlwaysTerminating = false;
39613995
if ($expr->getName() instanceof Expr) {
39623996
$result = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep());
39633997
$scope = $result->getScope();
39643998
$hasYield = $result->hasYield();
39653999
$throwPoints = $result->getThrowPoints();
39664000
$impurePoints = $result->getImpurePoints();
4001+
$isAlwaysTerminating = $result->isAlwaysTerminating();
39674002
}
39684003
} elseif ($expr instanceof MethodCallableNode) {
39694004
$result = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep());
39704005
$scope = $result->getScope();
39714006
$hasYield = $result->hasYield();
39724007
$throwPoints = $result->getThrowPoints();
39734008
$impurePoints = $result->getImpurePoints();
4009+
$isAlwaysTerminating = false;
39744010
if ($expr->getName() instanceof Expr) {
39754011
$nameResult = $this->processExprNode($stmt, $expr->getVar(), $scope, $nodeCallback, ExpressionContext::createDeep());
39764012
$scope = $nameResult->getScope();
39774013
$hasYield = $hasYield || $nameResult->hasYield();
39784014
$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints());
39794015
$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints());
4016+
$isAlwaysTerminating = $nameResult->isAlwaysTerminating();
39804017
}
39814018
} elseif ($expr instanceof StaticMethodCallableNode) {
39824019
$throwPoints = [];
39834020
$impurePoints = [];
39844021
$hasYield = false;
4022+
$isAlwaysTerminating = false;
39854023
if ($expr->getClass() instanceof Expr) {
39864024
$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep());
39874025
$scope = $classResult->getScope();
39884026
$hasYield = $classResult->hasYield();
39894027
$throwPoints = $classResult->getThrowPoints();
39904028
$impurePoints = $classResult->getImpurePoints();
4029+
$isAlwaysTerminating = $isAlwaysTerminating || $classResult->isAlwaysTerminating();
39914030
}
39924031
if ($expr->getName() instanceof Expr) {
39934032
$nameResult = $this->processExprNode($stmt, $expr->getName(), $scope, $nodeCallback, ExpressionContext::createDeep());
39944033
$scope = $nameResult->getScope();
39954034
$hasYield = $hasYield || $nameResult->hasYield();
39964035
$throwPoints = array_merge($throwPoints, $nameResult->getThrowPoints());
39974036
$impurePoints = array_merge($impurePoints, $nameResult->getImpurePoints());
4037+
$isAlwaysTerminating = $isAlwaysTerminating || $nameResult->isAlwaysTerminating();
39984038
}
39994039
} elseif ($expr instanceof InstantiationCallableNode) {
40004040
$throwPoints = [];
40014041
$impurePoints = [];
40024042
$hasYield = false;
4043+
$isAlwaysTerminating = false;
40034044
if ($expr->getClass() instanceof Expr) {
40044045
$classResult = $this->processExprNode($stmt, $expr->getClass(), $scope, $nodeCallback, ExpressionContext::createDeep());
40054046
$scope = $classResult->getScope();
40064047
$hasYield = $classResult->hasYield();
40074048
$throwPoints = $classResult->getThrowPoints();
40084049
$impurePoints = $classResult->getImpurePoints();
4050+
$isAlwaysTerminating = $isAlwaysTerminating || $classResult->isAlwaysTerminating();
40094051
}
40104052
} elseif ($expr instanceof Node\Scalar) {
40114053
$hasYield = false;
40124054
$throwPoints = [];
40134055
$impurePoints = [];
4056+
$isAlwaysTerminating = false;
40144057
} elseif ($expr instanceof ConstFetch) {
40154058
$hasYield = false;
40164059
$throwPoints = [];
40174060
$impurePoints = [];
4061+
$isAlwaysTerminating = false;
40184062
$nodeCallback($expr->name, $scope);
40194063
} else {
40204064
$hasYield = false;
40214065
$throwPoints = [];
40224066
$impurePoints = [];
4067+
$isAlwaysTerminating = false;
40234068
}
40244069

40254070
return new ExpressionResult(

tests/PHPStan/Analyser/ExpressionResultTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,50 @@ public static function dataIsAlwaysTerminating(): array
2626
'sprintf("hello %s", "abc");',
2727
false,
2828
],
29+
[
30+
'isset($x);',
31+
false,
32+
],
33+
[
34+
'$x ? "def" : "abc";',
35+
false,
36+
],
2937
[
3038
'sprintf("hello %s", exit());',
3139
true,
3240
],
41+
[
42+
'(string) exit();',
43+
true,
44+
],
45+
[
46+
'!exit();',
47+
true,
48+
],
49+
[
50+
'eval(exit());',
51+
true,
52+
],
53+
[
54+
'empty(exit());',
55+
true,
56+
],
57+
[
58+
'isset(exit());',
59+
true,
60+
],
61+
[
62+
'$x ? "abc" : exit();',
63+
true,
64+
],
65+
[
66+
'$x ? exit() : "abc";',
67+
true,
68+
],
69+
[
70+
'fn() => yield (exit());',
71+
true,
72+
],
3373
];
3474
}
3575

0 commit comments

Comments
 (0)