Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/Type/Php/FilterFunctionReturnTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,12 @@ public function hasFlag(string $flagName, ?Type $flagsType): TrinaryLogic
);
}

private function hasThrowOnFailureFlag(?Type $flagsType, PhpVersions $phpVersions): TrinaryLogic
public function hasThrowOnFailureFlag(?Type $flagsType, PhpVersions $phpVersions): TrinaryLogic
{
return $phpVersions->hasFilterThrowOnFailureConstant()->no()
? TrinaryLogic::createNo()
: $this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType);
// Flag values are resolved from the runtime. Before PHP 8.5, FILTER_FLAG_GLOBAL_RANGE used the bit
// that PHP 8.5 assigned to FILTER_THROW_ON_FAILURE, so a set bit only definitely means
// FILTER_THROW_ON_FAILURE when the analysed code definitely runs on PHP 8.5+.
return $phpVersions->hasFilterThrowOnFailureConstant()->and($this->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType));
}

private function getFlagsValue(Type $exprType): Type
Expand Down
5 changes: 3 additions & 2 deletions src/Type/Php/FilterFunctionsThrowTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ public function getThrowTypeFromFunctionCall(
Scope $scope,
): ?Type
{
$phpVersions = $scope->getPhpVersion();
if (
$scope->getPhpVersion()->hasFilterThrowOnFailureConstant()->no()
$phpVersions->hasFilterThrowOnFailureConstant()->no()
|| !$this->reflectionProvider->hasConstant(new Name\FullyQualified('FILTER_THROW_ON_FAILURE'), null)
) {
return null;
}

foreach ($this->filterFunctionFlagsHelper->getFlagsTypes($functionReflection, $funcCall, $scope) as $flagsType) {
if ($this->filterFunctionReturnTypeHelper->hasFlag('FILTER_THROW_ON_FAILURE', $flagsType)->no()) {
if ($this->filterFunctionReturnTypeHelper->hasThrowOnFailureFlag($flagsType, $phpVersions)->no()) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ function mbSubstituteCharacter(): void
assertType('false', mb_substitute_character('foo'));
assertType('false', mb_substitute_character(0x110000));
}

function filterFlagGlobalRange(string $address): void
{
assertType('non-falsy-string|false', filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE));
assertType('non-falsy-string|false', filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_GLOBAL_RANGE));
assertType('non-falsy-string|null', filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE | FILTER_NULL_ON_FAILURE));
assertType('array{ip: non-falsy-string|false|null}', filter_input_array(INPUT_GET, ['ip' => ['filter' => FILTER_VALIDATE_IP, 'flags' => FILTER_FLAG_GLOBAL_RANGE]]));
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ protected function getRule(): Rule

public function testThrowTypesInPhpVersionRange(): void
{
$this->analyse([__DIR__ . '/data/throw-type-php-versions.php'], [
$errors = [
[
'Dead catch - ValueError is never thrown in the try block.',
19,
],
]);
];

if (PHP_VERSION_ID >= 80500) {
// Before PHP 8.5, FILTER_FLAG_GLOBAL_RANGE has the same runtime value as FILTER_THROW_ON_FAILURE on PHP 8.5,
// so it might throw Filter\FilterFailedException in a PHP version range that only maybe includes PHP 8.5.
$errors[] = [
'Dead catch - Filter\\FilterFailedException is never thrown in the try block.',
58,
];
}

$this->analyse([__DIR__ . '/data/throw-type-php-versions.php'], $errors);
}

public static function getAdditionalConfigFiles(): array
Expand Down
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/Exceptions/data/throw-type-php-versions.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,22 @@ public function doStrSplit(string $s, int $length): void
}
}

public function doFilterVar(string $address): void
{
try {
$a = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE);
} catch (\Filter\FilterFailedException $e) {

}
}

public function doFilterVarThrowOnFailure(string $address): void
{
try {
$a = filter_var($address, FILTER_VALIDATE_IP, FILTER_THROW_ON_FAILURE);
} catch (\Filter\FilterFailedException $e) {

}
}

}
Loading