Skip to content

Commit

Permalink
Fix filter_var() narrowing with unknown options
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Dec 27, 2022
1 parent a5b5090 commit 86523f8
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 4 deletions.
9 changes: 5 additions & 4 deletions src/Type/Php/FilterVarDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,16 @@ public function getTypeFromFunctionCall(
$filterValue = $filterType->getValue();
}

$flagsType = isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : null;
$flagsType = isset($functionCall->getArgs()[2]) ? $scope->getType($functionCall->getArgs()[2]->value) : new ConstantIntegerType(0);
$inputType = $scope->getType($functionCall->getArgs()[0]->value);
$defaultType = $this->hasFlag($this->getConstant('FILTER_NULL_ON_FAILURE'), $flagsType)
? new NullType()
: new ConstantBooleanType(false);
$exactType = $this->determineExactType($inputType, $filterValue, $defaultType, $flagsType);
$type = $exactType ?? $this->getFilterTypeMap()[$filterValue] ?? $mixedType;

$options = $flagsType !== null && $this->hasOptions($flagsType)->yes() ? $this->getOptions($flagsType, $filterValue) : [];
$hasOptions = $this->hasOptions($flagsType);
$options = $hasOptions->yes() ? $this->getOptions($flagsType, $filterValue) : [];
$otherTypes = $this->getOtherTypes($flagsType, $options, $defaultType);

if ($inputType->isNonEmptyString()->yes()
Expand All @@ -185,7 +186,7 @@ public function getTypeFromFunctionCall(
}
}

if ($exactType !== null) {
if ($exactType !== null && !$hasOptions->maybe()) {
unset($otherTypes['default']);
}

Expand Down Expand Up @@ -273,7 +274,7 @@ private function getOtherTypes(?Type $flagsType, array $typeOptions, Type $defau

private function hasOptions(Type $flagsType): TrinaryLogic
{
return $flagsType->isConstantArray()
return $flagsType->isArray()
->and($flagsType->hasOffsetValueType(new ConstantStringType('options')));
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ public function dataFileAsserts(): iterable
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/filter-var.php');

if (PHP_VERSION_ID >= 80100) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/enums.php');
Expand Down
31 changes: 31 additions & 0 deletions tests/PHPStan/Analyser/data/filter-var.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types=1);

namespace FilterVar;

use function PHPStan\Testing\assertType;

class FilterVar
{

public function doFoo($mixed): void
{
assertType('int|false', filter_var($mixed, FILTER_VALIDATE_INT));
assertType('int|null', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_NULL_ON_FAILURE]));
assertType('array<int|false>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY]));
assertType('array<int|null>', filter_var($mixed, FILTER_VALIDATE_INT, ['flags' => FILTER_FORCE_ARRAY|FILTER_NULL_ON_FAILURE]));
assertType('0|int<17, 19>', filter_var($mixed, FILTER_VALIDATE_INT, ['options' => ['default' => 0, 'min_range' => 17, 'max_range' => 19]]));
}

public function intToInt(int $int, array $options): void
{
assertType('int', filter_var($int, FILTER_VALIDATE_INT));
assertType('int|false', filter_var($int, FILTER_VALIDATE_INT, $options));
}

public function constants(): void
{
assertType('array<false>', filter_var(false, FILTER_VALIDATE_BOOLEAN, FILTER_FORCE_ARRAY | FILTER_NULL_ON_FAILURE));
assertType('17', filter_var(17, FILTER_VALIDATE_INT));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,16 @@ public function testBug8158(): void
$this->analyse([__DIR__ . '/data/bug-8158.php'], []);
}

public function testBug8516(): void
{
if (PHP_VERSION_ID < 70400) {
$this->markTestSkipped('Test requires PHP 7.4.');
}

$this->checkAlwaysTrueStrictComparison = true;
$this->analyse([__DIR__ . '/data/bug-8516.php'], []);
}

public function testPhpUnitIntegration(): void
{
$this->checkAlwaysTrueStrictComparison = true;
Expand Down
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-8516.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1); // lint >= 7.4

namespace Bug8516;

function validate($value, array $options = null): bool
{
if (is_int($value)) {
$options ??= ['options' => ['min_range' => 0]];
if (filter_var($value, FILTER_VALIDATE_INT, $options) === false) {
return false;
}
// ...
}
if (is_string($value)) {
// ...
}
return true;
}

0 comments on commit 86523f8

Please sign in to comment.