Skip to content

Commit

Permalink
Add range checks to identify for loops that always enter
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Jan 4, 2020
1 parent 9fa2db1 commit 8cbc26c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 0 deletions.
41 changes: 41 additions & 0 deletions src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,20 @@ public static function analyze(
$pre_assigned_var_ids = $context->assigned_var_ids;
$context->assigned_var_ids = [];

$init_var_types = [];

foreach ($stmt->init as $init) {
if (ExpressionAnalyzer::analyze($statements_analyzer, $init, $context) === false) {
return false;
}

if ($init instanceof PhpParser\Node\Expr\Assign
&& $init->var instanceof PhpParser\Node\Expr\Variable
&& is_string($init->var->name)
&& ($init_var_type = $statements_analyzer->node_data->getType($init->expr))
) {
$init_var_types[$init->var->name] = $init_var_type;
}
}

$assigned_var_ids = $context->assigned_var_ids;
Expand Down Expand Up @@ -94,6 +104,37 @@ public static function analyze(
break;
}
}

if ($cond instanceof PhpParser\Node\Expr\BinaryOp
&& $cond->right instanceof PhpParser\Node\Scalar\LNumber
&& $cond->left instanceof PhpParser\Node\Expr\Variable
&& is_string($cond->left->name)
&& isset($init_var_types[$cond->left->name])
&& $init_var_types[$cond->left->name]->isSingleIntLiteral()
) {
$init_value = $init_var_types[$cond->left->name]->getSingleIntLiteral()->value;
$cond_value = $cond->right->value;

if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Smaller && $init_value < $cond_value) {
$always_enters_loop = true;
break;
}

if ($cond instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual && $init_value <= $cond_value) {
$always_enters_loop = true;
break;
}

if ($cond instanceof PhpParser\Node\Expr\BinaryOp\Greater && $init_value > $cond_value) {
$always_enters_loop = true;
break;
}

if ($cond instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual && $init_value >= $cond_value) {
$always_enters_loop = true;
break;
}
}
}

if ($while_true) {
Expand Down
18 changes: 18 additions & 0 deletions tests/Loop/ForTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ function test(Node $head) {
for ($i = 0; $i < 5; $i++);
echo $i;',
],
'nestedEchoAfterFor' => [
'<?php
for ($i = 1; $i < 2; $i++) {
for ($j = 1; $j < 2; $j++) {}
}
echo $i * $j;'
],
];
}

Expand Down Expand Up @@ -146,6 +154,16 @@ public function providerInvalidCodeParse()
echo $a;',
'error_message' => 'UndefinedGlobalVariable',
],
'nestedEchoAfterFor' => [
'<?php
for ($i = 1; $i < 2; $i++) {
if (rand(0, 1)) break;
for ($j = 1; $j < 2; $j++) {}
}
echo $i * $j;',
'error_message' => 'PossiblyUndefinedGlobalVariable',
],
];
}
}

0 comments on commit 8cbc26c

Please sign in to comment.