Skip to content

Commit

Permalink
Add rule to validate parameters to random_int()
Browse files Browse the repository at this point in the history
  • Loading branch information
cs278 committed Jan 12, 2020
1 parent 29dbab0 commit fd1ad58
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level0.neon
Expand Up @@ -27,6 +27,7 @@ rules:
- PHPStan\Rules\Functions\InnerFunctionRule
- PHPStan\Rules\Functions\NonExistentDefinedFunctionRule
- PHPStan\Rules\Functions\PrintfParametersRule
- PHPStan\Rules\Functions\RandomIntParametersRule
- PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
- PHPStan\Rules\Properties\AccessPropertiesInAssignRule
- PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule
Expand Down
100 changes: 100 additions & 0 deletions src/Rules/Functions/RandomIntParametersRule.php
@@ -0,0 +1,100 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\VerbosityLevel;

/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\FuncCall>
*/
class RandomIntParametersRule implements \PHPStan\Rules\Rule
{

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!($node->name instanceof \PhpParser\Node\Name)) {
return [];
}

if (strtolower((string) $node->name) !== 'random_int') {
return [];
}

$minType = $scope->getType($node->args[0]->value)->toInteger();
$maxType = $scope->getType($node->args[1]->value)->toInteger();

if ($minType instanceof ConstantIntegerType
&& $maxType instanceof ConstantIntegerType
&& $minType->getValue() > $maxType->getValue()
) {
return [
RuleErrorBuilder::message(sprintf(
'Cannot call random_int() with $min parameter (%d) greater than $max parameter (%d).',
$minType->getValue(),
$maxType->getValue()
))->build(),
];
}

if ($minType instanceof IntegerRangeType
&& $maxType instanceof ConstantIntegerType
&& $minType->getMax() > $maxType->getValue()
) {
$message = $minType->getMin() > $maxType->getValue()
? 'Cannot call random_int() with $min parameter (%s) greater than $max parameter (%d).'
: 'Cannot call random_int() when $min parameter (%s) can be greater than $max parameter (%d).';

return [
RuleErrorBuilder::message(sprintf(
$message,
$minType->describe(VerbosityLevel::value()),
$maxType->getValue()
))->build(),
];
}

if ($minType instanceof ConstantIntegerType
&& $maxType instanceof IntegerRangeType
&& $minType->getValue() > $maxType->getMin()
) {
$message = $minType->getValue() > $maxType->getMax()
? 'Cannot call random_int() with $max parameter (%s) less than $min parameter (%d).'
: 'Cannot call random_int() when $max parameter (%s) can be less than $min parameter (%d).';

return [
RuleErrorBuilder::message(sprintf(
$message,
$maxType->describe(VerbosityLevel::value()),
$minType->getValue()
))->build(),
];
}

if ($minType instanceof IntegerRangeType
&& $maxType instanceof IntegerRangeType
&& $minType->getMax() > $maxType->getMin()
) {
return [
RuleErrorBuilder::message(sprintf(
'Cannot call random_int() with intersecting $min (%s) and $max (%s) parameters.',
$minType->describe(VerbosityLevel::value()),
$maxType->describe(VerbosityLevel::value())
))->build(),
];
}

return [];
}

}
54 changes: 54 additions & 0 deletions tests/PHPStan/Rules/Functions/RandomIntParametersRuleTest.php
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

/**
* @extends \PHPStan\Testing\RuleTestCase<RandomIntParametersRule>
*/
class RandomIntParametersRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new RandomIntParametersRule();
}

public function testFile(): void
{
$this->analyse([__DIR__ . '/data/random-int.php'], [
[
'Cannot call random_int() with $min parameter (1) greater than $max parameter (0).',
8,
],
[
'Cannot call random_int() with $min parameter (0) greater than $max parameter (-1).',
9,
],
[
'Cannot call random_int() with $max parameter (int<-10, -1>) less than $min parameter (0).',
11,
],
[
'Cannot call random_int() when $max parameter (int<-10, 10>) can be less than $min parameter (0).',
12,
],
[
'Cannot call random_int() with $min parameter (int<1, 10>) greater than $max parameter (0).',
15,
],
[
'Cannot call random_int() when $min parameter (int<-10, 10>) can be greater than $max parameter (0).',
16,
],
[
'Cannot call random_int() with intersecting $min (int<-5, 1>) and $max (int<0, 5>) parameters.',
19,
],
[
'Cannot call random_int() with intersecting $min (int<-5, 0>) and $max (int<-1, 5>) parameters.',
20,
],
]);
}

}
32 changes: 32 additions & 0 deletions tests/PHPStan/Rules/Functions/data/random-int.php
@@ -0,0 +1,32 @@
<?php

random_int(0, 0);
random_int(0, 1);
random_int(-1, 0);
random_int(-1, 1);

random_int(1, 0);
random_int(0, -1);

random_int(0, random_int(-10, -1));
random_int(0, random_int(-10, 10));
random_int(0, random_int(0, 10)); // ok

random_int(random_int(1, 10), 0);
random_int(random_int(-10, 10), 0);
random_int(random_int(-10, 0), 0); // ok

random_int(random_int(-5, 1), random_int(0, 5));
random_int(random_int(-5, 0), random_int(-1, 5));

/** @var int */
$x = foo();
/** @var int */
$y = bar();

random_int($x, $y);
random_int(0, $x);
random_int($x, random_int(0, PHP_INT_MAX));
random_int(random_int(PHP_INT_MIN, 0), $x);

random_int(PHP_INT_MAX, PHP_INT_MIN); // @todo this should error

0 comments on commit fd1ad58

Please sign in to comment.