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
77 changes: 65 additions & 12 deletions src/Type/Php/FilterFunctionReturnTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectShapeType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
Expand Down Expand Up @@ -180,9 +181,9 @@
$type = $this->applyRangeOptions($type, $options, $defaultType);

if (
$inputType->isNonEmptyString()->yes()
!$this->canStringBeSanitized($filterValue, $flagsType)
&& $inputType->isNonEmptyString()->yes()

Check warning on line 185 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( !$this->canStringBeSanitized($filterValue, $flagsType) - && $inputType->isNonEmptyString()->yes() + && !$inputType->isNonEmptyString()->no() && $type->isString()->yes() ) { $accessory = new AccessoryNonEmptyStringType();

Check warning on line 185 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( !$this->canStringBeSanitized($filterValue, $flagsType) - && $inputType->isNonEmptyString()->yes() + && !$inputType->isNonEmptyString()->no() && $type->isString()->yes() ) { $accessory = new AccessoryNonEmptyStringType();
&& $type->isString()->yes()
&& $this->canStringBeSanitized($filterValue, $flagsType)->no()
) {
$accessory = new AccessoryNonEmptyStringType();
if ($inputType->isNonFalsyString()->yes()) {
Expand All @@ -191,7 +192,11 @@
$type = TypeCombinator::intersect($type, $accessory);
}

if ($exactType === null || $hasOptions->maybe() || (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes())) {
if (
$exactType === null
|| $hasOptions->maybe()
|| ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes()))

Check warning on line 198 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $exactType === null || $hasOptions->maybe() - || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes())) + || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && !$inputType->isSuperTypeOf($type)->no())) ) { if (!$defaultType->isSuperTypeOf($type)->yes()) { $type = TypeCombinator::union($type, $defaultType);

Check warning on line 198 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ if ( $exactType === null || $hasOptions->maybe() - || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes())) + || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $type->isSuperTypeOf($inputType)->yes())) ) { if (!$defaultType->isSuperTypeOf($type)->yes()) { $type = TypeCombinator::union($type, $defaultType);

Check warning on line 198 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $exactType === null || $hasOptions->maybe() - || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes())) + || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && !$inputType->isSuperTypeOf($type)->no())) ) { if (!$defaultType->isSuperTypeOf($type)->yes()) { $type = TypeCombinator::union($type, $defaultType);

Check warning on line 198 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ if ( $exactType === null || $hasOptions->maybe() - || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $inputType->isSuperTypeOf($type)->yes())) + || ($this->isValidationFilter($filterValue) && (!$inputType->equals($type) && $type->isSuperTypeOf($inputType)->yes())) ) { if (!$defaultType->isSuperTypeOf($type)->yes()) { $type = TypeCombinator::union($type, $defaultType);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the 2 mutations here, I don't know how to kill them.

I want to find a data-set where only the $inputType->isSuperTypeOf($type)->yes() part toogles, which implies that such data-set must produce the following statements:

  • $exactType is not null
  • $hasOptions->maybe() is false (not a big deal)
  • $this->isValidationFilter($filterValue) is true`
  • $inputType->equals($type) is false.

Also, as the body of this if statement implies a possible change of the $type to return by adding it the $defaultType, it means that $exactType must not be $defaultType too (or the whole condition would have no effect, and mutants are still alive).

Now, regarding how $exactType is set to something different from defaultType and $this->isValidationFilter(…) must be true, the data-set MUST be via filter_var(???, FILTER_VALIDATE_(BOOLEAN|INT|FLOAT), ???)

(Please note that my PR was only about caring of the flag FILTER_FLAG_EMPTY_STRING_NULL which has no effect when used along one of the 3 validators identified, so I'm pretty sure the mutants to kill are not alive due to my changes, but anyway, let's say I'm a mutant killer 😆 )

About FILTER_VALIDATE_BOOLEAN, as we don't want $exactType to be $defaultType, the only data-set possible to search is when $in IS a boolean. Such situation produce that $exactType === $inputType === $type, therefore $inputType->equals($type) is always true. So this filterValue does not allow me to kill the mutant (no data-set possible).

About FILTER_VALIDATE_FLOAT, searched data-set would be $in is true or an integer (float would lead to the same outcome than any boolean with FILTER_VALIDATE_BOOLEAN and null would lead to $defaultType). In such data-set, $exactType (and therefore, $type) would be a Float or a ConstantFloat. But as $inputType as true is never a super-type of ConstantFloat, nor $inputType as int is never a super-type of Float, there is no way to toogle $inputType->isSuperTypeOf($type)->yes() from any possible data-set. This leads to a dead-end.

Finally, about FILTER_VALIDATE_INT, long story short, $exactType is a different type from $inputType or $defaultType only when:

  • $in === true => implies $type to be a ConstantInteger and true is never a super-type of a constant integer, therefore no data-set is possible
  • $in is a float which value matches its "integer" value => implies $type to be an integer and a float is never a super-type of an integer, therefore no sata-set is possible
  • $in is a numerical constant string => implies $type to be an integer and a string is never a super-type of an integer, therefore no data-set is possible.

I really do have the feeling that finding a data-set that could kill the mutants is not possible here 😢 .

) {
if (!$defaultType->isSuperTypeOf($type)->yes()) {
$type = TypeCombinator::union($type, $defaultType);
}
Expand Down Expand Up @@ -389,18 +394,56 @@
}

if ($filterValue === $this->getConstant('FILTER_DEFAULT')) {
if ($this->canStringBeSanitized($filterValue, $flagsType)->no() && $in->isString()->yes()) {
return $in;
$inputType = $in;
$useDefaultType = false;

if ($inputType->isArray()->maybe()) {
$inputType = TypeCombinator::remove($inputType, new ArrayType(new MixedType(), new MixedType()));
$useDefaultType = true;
}
if ($inputType->isObject()->maybe()) {
$inputType = TypeCombinator::remove($inputType, new ObjectShapeType([], []));
$useDefaultType = true;
}

if ($in->isBoolean()->yes() || $in->isFloat()->yes() || $in->isInteger()->yes() || $in->isNull()->yes()) {
return $in->toString();
if (!$this->canStringBeSanitized($filterValue, $flagsType)) {
$stringType = $inputType->toString();
} else {
$stringType = TypeCombinator::union(
TypeCombinator::remove($inputType, new StringType()),
new StringType(),
);
}

$returnType = $this->handleEmptyStringNullFlag($stringType, $flagsType);
return $useDefaultType ? TypeCombinator::union($defaultType, $returnType) : $returnType;
}

return null;
}

private function handleEmptyStringNullFlag(Type $in, ?Type $flagsType): Type
{
$hasFlag = $this->hasFlag('FILTER_FLAG_EMPTY_STRING_NULL', $flagsType);
if ($hasFlag->no()) {
return $in;
}

if ($hasFlag->maybe()) {
return $in->isNonEmptyString()->maybe() ? TypeCombinator::addNull($in) : $in;
}

if ($in->isNonEmptyString()->yes()) {
return $in;
}

if ($in->isNonEmptyString()->maybe()) {
return TypeCombinator::remove(TypeCombinator::addNull($in), new ConstantStringType(''));
}

return new NullType();
}

/** @param array<string, ?Type> $typeOptions */
private function applyRangeOptions(Type $type, array $typeOptions, Type $defaultType): Type
{
Expand Down Expand Up @@ -529,22 +572,32 @@
);
}

private function canStringBeSanitized(int $filterValue, ?Type $flagsType): TrinaryLogic
private function canStringBeSanitized(int $filterValue, ?Type $flagsType): bool
{
// If it is a validation filter, the string will not be changed
if (($filterValue & self::VALIDATION_FILTER_BITMASK) !== 0) {
return TrinaryLogic::createNo();
if ($this->isValidationFilter($filterValue)) {
return false;
}

// FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
// FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
if ($filterValue === $this->getConstant('FILTER_DEFAULT')) {
return $this->hasFlag('FILTER_FLAG_STRIP_LOW', $flagsType)
$hasStripFlags = $this->hasFlag('FILTER_FLAG_STRIP_LOW', $flagsType)
->or($this->hasFlag('FILTER_FLAG_STRIP_HIGH', $flagsType))
->or($this->hasFlag('FILTER_FLAG_STRIP_BACKTICK', $flagsType));
if ($hasStripFlags->maybe()) {
// Too complicated, considering strip flags are not defined (default behavior).
return false;
}
return $hasStripFlags->yes();

Check warning on line 592 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ // Too complicated, considering strip flags are not defined (default behavior). return false; } - return $hasStripFlags->yes(); + return !$hasStripFlags->no(); } return true;

Check warning on line 592 in src/Type/Php/FilterFunctionReturnTypeHelper.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ // Too complicated, considering strip flags are not defined (default behavior). return false; } - return $hasStripFlags->yes(); + return !$hasStripFlags->no(); } return true;
}

return TrinaryLogic::createYes();
return true;
}

private function isValidationFilter(int $filterValue): bool
{
return ($filterValue & self::VALIDATION_FILTER_BITMASK) !== 0;
}

}
Loading
Loading