mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 13:51:54 +01:00
filter_input & filter_var return type more specific
This commit is contained in:
parent
b38530ed0d
commit
dee555daaf
@ -418,6 +418,7 @@
|
||||
<xs:element name="RedundantCastGivenDocblockType" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantCondition" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantConditionGivenDocblockType" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantFlag" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantFunctionCall" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantFunctionCallGivenDocblockType" type="IssueHandlerType" minOccurs="0" />
|
||||
<xs:element name="RedundantIdentityWithTrue" type="IssueHandlerType" minOccurs="0" />
|
||||
|
@ -2847,11 +2847,11 @@ return [
|
||||
'FilesystemIterator::setInfoClass' => ['void', 'class='=>'class-string'],
|
||||
'FilesystemIterator::valid' => ['bool'],
|
||||
'filetype' => ['string|false', 'filename'=>'string'],
|
||||
'filter_has_var' => ['bool', 'input_type'=>'int', 'var_name'=>'string'],
|
||||
'filter_has_var' => ['bool', 'input_type'=>'0|1|2|4|5', 'var_name'=>'string'],
|
||||
'filter_id' => ['int|false', 'name'=>'string'],
|
||||
'filter_input' => ['mixed|false', 'type'=>'int', 'var_name'=>'string', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_input_array' => ['array|false|null', 'type'=>'int', 'options='=>'int|array', 'add_empty='=>'bool'],
|
||||
'filter_list' => ['array'],
|
||||
'filter_input' => ['mixed|false|null', 'type'=>'0|1|2|4|5', 'var_name'=>'string', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_input_array' => ['array|false|null', 'type'=>'0|1|2|4|5', 'options='=>'int|array', 'add_empty='=>'bool'],
|
||||
'filter_list' => ['non-empty-list<non-falsy-string>'],
|
||||
'filter_var' => ['mixed|false', 'value'=>'mixed', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_var_array' => ['array|false|null', 'array'=>'array', 'options='=>'array|int', 'add_empty='=>'bool'],
|
||||
'FilterIterator::__construct' => ['void', 'iterator'=>'Iterator'],
|
||||
|
@ -10434,11 +10434,11 @@ return [
|
||||
'filepro_rowcount' => ['int'],
|
||||
'filesize' => ['int|false', 'filename'=>'string'],
|
||||
'filetype' => ['string|false', 'filename'=>'string'],
|
||||
'filter_has_var' => ['bool', 'input_type'=>'int', 'var_name'=>'string'],
|
||||
'filter_has_var' => ['bool', 'input_type'=>'0|1|2|4|5', 'var_name'=>'string'],
|
||||
'filter_id' => ['int|false', 'name'=>'string'],
|
||||
'filter_input' => ['mixed|false', 'type'=>'int', 'var_name'=>'string', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_input_array' => ['array|false|null', 'type'=>'int', 'options='=>'int|array', 'add_empty='=>'bool'],
|
||||
'filter_list' => ['array'],
|
||||
'filter_input' => ['mixed|false|null', 'type'=>'0|1|2|4|5', 'var_name'=>'string', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_input_array' => ['array|false|null', 'type'=>'0|1|2|4|5', 'options='=>'int|array', 'add_empty='=>'bool'],
|
||||
'filter_list' => ['non-empty-list<non-falsy-string>'],
|
||||
'filter_var' => ['mixed|false', 'value'=>'mixed', 'filter='=>'int', 'options='=>'array|int'],
|
||||
'filter_var_array' => ['array|false|null', 'array'=>'array', 'options='=>'array|int', 'add_empty='=>'bool'],
|
||||
'finfo::__construct' => ['void', 'flags='=>'int', 'magic_database='=>'string'],
|
||||
|
@ -220,6 +220,7 @@
|
||||
- [RedundantCastGivenDocblockType](issues/RedundantCastGivenDocblockType.md)
|
||||
- [RedundantCondition](issues/RedundantCondition.md)
|
||||
- [RedundantConditionGivenDocblockType](issues/RedundantConditionGivenDocblockType.md)
|
||||
- [RedundantFlag](issues/RedundantFlag.md)
|
||||
- [RedundantFunctionCall](issues/RedundantFunctionCall.md)
|
||||
- [RedundantFunctionCallGivenDocblockType](issues/RedundantFunctionCallGivenDocblockType.md)
|
||||
- [RedundantIdentityWithTrue](issues/RedundantIdentityWithTrue.md)
|
||||
|
8
docs/running_psalm/issues/RedundantFlag.md
Normal file
8
docs/running_psalm/issues/RedundantFlag.md
Normal file
@ -0,0 +1,8 @@
|
||||
# RedundantFlag
|
||||
|
||||
Emitted when a flag is redundant. e.g. FILTER_NULL_ON_FAILURE won't do anything when the default option is specified
|
||||
|
||||
```php
|
||||
<?php
|
||||
$x = filter_input(INPUT_GET, 'hello', FILTER_VALIDATE_DOMAIN, array('options' => array('default' => 'world.com'), 'flags' => FILTER_NULL_ON_FAILURE));
|
||||
```
|
@ -25,6 +25,7 @@ use Psalm\Internal\Provider\ReturnTypeProvider\ArraySpliceReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\BasenameReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\DateReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\DirnameReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FilterInputReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FilterVarReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\FirstArgStringReturnTypeProvider;
|
||||
use Psalm\Internal\Provider\ReturnTypeProvider\GetClassMethodsReturnTypeProvider;
|
||||
@ -85,6 +86,7 @@ final class FunctionReturnTypeProvider
|
||||
$this->registerClass(ArrayReverseReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayFillReturnTypeProvider::class);
|
||||
$this->registerClass(ArrayFillKeysReturnTypeProvider::class);
|
||||
$this->registerClass(FilterInputReturnTypeProvider::class);
|
||||
$this->registerClass(FilterVarReturnTypeProvider::class);
|
||||
$this->registerClass(IteratorToArrayReturnTypeProvider::class);
|
||||
$this->registerClass(ParseUrlReturnTypeProvider::class);
|
||||
|
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TArray;
|
||||
use Psalm\Type\Atomic\TKeyedArray;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_search;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
|
||||
use const FILTER_CALLBACK;
|
||||
use const FILTER_DEFAULT;
|
||||
use const FILTER_FLAG_NONE;
|
||||
use const FILTER_REQUIRE_ARRAY;
|
||||
use const FILTER_VALIDATE_REGEXP;
|
||||
use const INPUT_COOKIE;
|
||||
use const INPUT_ENV;
|
||||
use const INPUT_GET;
|
||||
use const INPUT_POST;
|
||||
use const INPUT_SERVER;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FilterInputReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return array<lowercase-string>
|
||||
*/
|
||||
public static function getFunctionIds(): array
|
||||
{
|
||||
return ['filter_input'];
|
||||
}
|
||||
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union
|
||||
{
|
||||
$statements_analyzer = $event->getStatementsSource();
|
||||
if (! $statements_analyzer instanceof StatementsAnalyzer) {
|
||||
throw new UnexpectedValueException('Expected StatementsAnalyzer not StatementsSource');
|
||||
}
|
||||
|
||||
$call_args = $event->getCallArgs();
|
||||
$function_id = $event->getFunctionId();
|
||||
$code_location = $event->getCodeLocation();
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (! isset($call_args[0]) || ! isset($call_args[1])) {
|
||||
return FilterUtils::missingFirstArg($codebase);
|
||||
}
|
||||
|
||||
$first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value);
|
||||
if ($first_arg_type && ! $first_arg_type->isInt()) {
|
||||
if ($codebase->analysis_php_version_id >= 8_00_00) {
|
||||
// throws
|
||||
return Type::getNever();
|
||||
}
|
||||
|
||||
// default option won't be used in this case
|
||||
return Type::getNull();
|
||||
}
|
||||
|
||||
$filter_int_used = FILTER_DEFAULT;
|
||||
if (isset($call_args[2])) {
|
||||
$filter_int_used = FilterUtils::getFilterArgValueOrError(
|
||||
$call_args[2],
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
);
|
||||
|
||||
if (!is_int($filter_int_used)) {
|
||||
return $filter_int_used;
|
||||
}
|
||||
}
|
||||
|
||||
$options = null;
|
||||
$flags_int_used = FILTER_FLAG_NONE;
|
||||
if (isset($call_args[3])) {
|
||||
$helper = FilterUtils::getOptionsArgValueOrError(
|
||||
$call_args[3],
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
$code_location,
|
||||
$function_id,
|
||||
$filter_int_used,
|
||||
);
|
||||
|
||||
if (!is_array($helper)) {
|
||||
return $helper;
|
||||
}
|
||||
|
||||
$flags_int_used = $helper['flags_int_used'];
|
||||
$options = $helper['options'];
|
||||
}
|
||||
|
||||
// if we reach this point with callback, the callback is missing
|
||||
if ($filter_int_used === FILTER_CALLBACK) {
|
||||
return FilterUtils::missingFilterCallbackCallable(
|
||||
$function_id,
|
||||
$code_location,
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
);
|
||||
}
|
||||
|
||||
[$default, $min_range, $max_range, $has_range, $regexp] = FilterUtils::getOptions(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$options,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$function_id,
|
||||
);
|
||||
|
||||
// only return now, as we still want to report errors above
|
||||
if (!$first_arg_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $first_arg_type->isSingleIntLiteral()) {
|
||||
// eventually complex cases can be handled too, however practically this is irrelevant
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$default) {
|
||||
[$fails_type, $not_set_type, $fails_or_not_set_type] = FilterUtils::getFailsNotSetType($flags_int_used);
|
||||
} else {
|
||||
$fails_type = $default;
|
||||
$not_set_type = $default;
|
||||
$fails_or_not_set_type = $default;
|
||||
}
|
||||
|
||||
if ($filter_int_used === FILTER_VALIDATE_REGEXP && $regexp === null) {
|
||||
if ($codebase->analysis_php_version_id >= 8_00_00) {
|
||||
// throws
|
||||
return Type::getNever();
|
||||
}
|
||||
|
||||
// any "array" flags are ignored by this filter!
|
||||
return $fails_or_not_set_type;
|
||||
}
|
||||
|
||||
$possible_types = array(
|
||||
'$_GET' => INPUT_GET,
|
||||
'$_POST' => INPUT_POST,
|
||||
'$_COOKIE' => INPUT_COOKIE,
|
||||
'$_SERVER' => INPUT_SERVER,
|
||||
'$_ENV' => INPUT_ENV,
|
||||
);
|
||||
|
||||
$first_arg_type_type = $first_arg_type->getSingleIntLiteral();
|
||||
$global_name = array_search($first_arg_type_type->value, $possible_types);
|
||||
if (!$global_name) {
|
||||
// invalid
|
||||
if ($codebase->analysis_php_version_id >= 8_00_00) {
|
||||
// throws
|
||||
return Type::getNever();
|
||||
}
|
||||
|
||||
// the "not set type" is never in an array, even if FILTER_FORCE_ARRAY is set!
|
||||
return $not_set_type;
|
||||
}
|
||||
|
||||
$second_arg_type = $statements_analyzer->node_data->getType($call_args[1]->value);
|
||||
if (!$second_arg_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $second_arg_type->hasString()) {
|
||||
// for filter_input there can only be string array keys
|
||||
return $not_set_type;
|
||||
}
|
||||
|
||||
if (! $second_arg_type->isString()) {
|
||||
// already reports an error by default
|
||||
return null;
|
||||
}
|
||||
|
||||
// in all these cases it can fail or be not set, depending on whether the variable is set or not
|
||||
$redundant_error_return_type = FilterUtils::checkRedundantFlags(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$fails_or_not_set_type,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
);
|
||||
if ($redundant_error_return_type !== null) {
|
||||
return $redundant_error_return_type;
|
||||
}
|
||||
|
||||
if (FilterUtils::hasFlag($flags_int_used, FILTER_REQUIRE_ARRAY)
|
||||
&& in_array($first_arg_type_type->value, array(INPUT_COOKIE, INPUT_SERVER, INPUT_ENV), true)) {
|
||||
// these globals can never be an array
|
||||
return $fails_or_not_set_type;
|
||||
}
|
||||
|
||||
// @todo eventually this needs to be changed when we fully support filter_has_var
|
||||
$global_type = VariableFetchAnalyzer::getGlobalType($global_name, $codebase->analysis_php_version_id);
|
||||
|
||||
$input_type = null;
|
||||
if ($global_type->isArray() && $global_type->getArray() instanceof TKeyedArray) {
|
||||
$array_instance = $global_type->getArray();
|
||||
if ($second_arg_type->isSingleStringLiteral()) {
|
||||
$key = $second_arg_type->getSingleStringLiteral()->value;
|
||||
|
||||
if (isset($array_instance->properties[ $key ])) {
|
||||
$input_type = $array_instance->properties[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
if ($input_type === null) {
|
||||
$input_type = $array_instance->getGenericValueType();
|
||||
$input_type = $input_type->setPossiblyUndefined(true);
|
||||
}
|
||||
} elseif ($global_type->isArray()
|
||||
&& ($array_atomic = $global_type->getArray())
|
||||
&& $array_atomic instanceof TArray) {
|
||||
[$_, $input_type] = $array_atomic->type_params;
|
||||
$input_type = $input_type->setPossiblyUndefined(true);
|
||||
} else {
|
||||
// this is impossible
|
||||
throw new UnexpectedValueException('This should not happen');
|
||||
}
|
||||
|
||||
return FilterUtils::getReturnType(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$input_type,
|
||||
$fails_type,
|
||||
$not_set_type,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$function_id,
|
||||
$has_range,
|
||||
$min_range,
|
||||
$max_range,
|
||||
$regexp,
|
||||
);
|
||||
}
|
||||
}
|
1738
src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php
Normal file
1738
src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,30 +3,19 @@
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\DataFlow\DataFlowNode;
|
||||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TFalse;
|
||||
use Psalm\Type\Atomic\TKeyedArray;
|
||||
use Psalm\Type\Atomic\TLiteralInt;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
|
||||
use const FILTER_NULL_ON_FAILURE;
|
||||
use const FILTER_SANITIZE_URL;
|
||||
use const FILTER_VALIDATE_BOOLEAN;
|
||||
use const FILTER_VALIDATE_DOMAIN;
|
||||
use const FILTER_VALIDATE_EMAIL;
|
||||
use const FILTER_VALIDATE_FLOAT;
|
||||
use const FILTER_VALIDATE_INT;
|
||||
use const FILTER_VALIDATE_IP;
|
||||
use const FILTER_VALIDATE_MAC;
|
||||
use const FILTER_CALLBACK;
|
||||
use const FILTER_DEFAULT;
|
||||
use const FILTER_FLAG_NONE;
|
||||
use const FILTER_VALIDATE_REGEXP;
|
||||
use const FILTER_VALIDATE_URL;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -41,135 +30,124 @@ final class FilterVarReturnTypeProvider implements FunctionReturnTypeProviderInt
|
||||
return ['filter_var'];
|
||||
}
|
||||
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): Union
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Union
|
||||
{
|
||||
$statements_source = $event->getStatementsSource();
|
||||
$call_args = $event->getCallArgs();
|
||||
$function_id = $event->getFunctionId();
|
||||
$code_location = $event->getCodeLocation();
|
||||
if (!$statements_source instanceof StatementsAnalyzer) {
|
||||
$statements_analyzer = $event->getStatementsSource();
|
||||
if (!$statements_analyzer instanceof StatementsAnalyzer) {
|
||||
throw new UnexpectedValueException();
|
||||
}
|
||||
|
||||
$filter_type = null;
|
||||
$call_args = $event->getCallArgs();
|
||||
$function_id = $event->getFunctionId();
|
||||
$code_location = $event->getCodeLocation();
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (isset($call_args[1])
|
||||
&& ($second_arg_type = $statements_source->node_data->getType($call_args[1]->value))
|
||||
&& $second_arg_type->isSingleIntLiteral()
|
||||
) {
|
||||
$filter_type_type = $second_arg_type->getSingleIntLiteral();
|
||||
if (! isset($call_args[0])) {
|
||||
return FilterUtils::missingFirstArg($codebase);
|
||||
}
|
||||
|
||||
switch ($filter_type_type->value) {
|
||||
case FILTER_VALIDATE_INT:
|
||||
$filter_type = Type::getInt();
|
||||
break;
|
||||
$filter_int_used = FILTER_DEFAULT;
|
||||
if (isset($call_args[1])) {
|
||||
$filter_int_used = FilterUtils::getFilterArgValueOrError(
|
||||
$call_args[1],
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
);
|
||||
|
||||
case FILTER_VALIDATE_FLOAT:
|
||||
$filter_type = Type::getFloat();
|
||||
break;
|
||||
|
||||
case FILTER_VALIDATE_BOOLEAN:
|
||||
$filter_type = Type::getBool();
|
||||
|
||||
break;
|
||||
|
||||
case FILTER_VALIDATE_IP:
|
||||
case FILTER_VALIDATE_MAC:
|
||||
case FILTER_VALIDATE_REGEXP:
|
||||
case FILTER_VALIDATE_URL:
|
||||
case FILTER_VALIDATE_EMAIL:
|
||||
case FILTER_VALIDATE_DOMAIN:
|
||||
case FILTER_SANITIZE_URL:
|
||||
$filter_type = Type::getString();
|
||||
break;
|
||||
}
|
||||
|
||||
$has_object_like = false;
|
||||
$filter_null = false;
|
||||
|
||||
if (isset($call_args[2])
|
||||
&& ($third_arg_type = $statements_source->node_data->getType($call_args[2]->value))
|
||||
&& $filter_type
|
||||
) {
|
||||
foreach ($third_arg_type->getAtomicTypes() as $atomic_type) {
|
||||
if ($atomic_type instanceof TKeyedArray) {
|
||||
$has_object_like = true;
|
||||
|
||||
if (isset($atomic_type->properties['options'])
|
||||
&& $atomic_type->properties['options']->hasArray()
|
||||
&& ($options_array = $atomic_type->properties['options']->getArray())
|
||||
&& $options_array instanceof TKeyedArray
|
||||
&& isset($options_array->properties['default'])
|
||||
) {
|
||||
$filter_type = Type::combineUnionTypes(
|
||||
$filter_type,
|
||||
$options_array->properties['default'],
|
||||
);
|
||||
} else {
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TFalse)->freeze();
|
||||
}
|
||||
|
||||
if (isset($atomic_type->properties['flags'])
|
||||
&& $atomic_type->properties['flags']->isSingleIntLiteral()
|
||||
) {
|
||||
$filter_flag_type =
|
||||
$atomic_type->properties['flags']->getSingleIntLiteral();
|
||||
|
||||
if ($filter_type->hasBool()
|
||||
&& $filter_flag_type->value === FILTER_NULL_ON_FAILURE
|
||||
) {
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
}
|
||||
} elseif ($atomic_type instanceof TLiteralInt) {
|
||||
if ($atomic_type->value === FILTER_NULL_ON_FAILURE) {
|
||||
$filter_null = true;
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TNull)->freeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_object_like && !$filter_null && $filter_type) {
|
||||
$filter_type = $filter_type->getBuilder()->addType(new TFalse)->freeze();
|
||||
if (!is_int($filter_int_used)) {
|
||||
return $filter_int_used;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$filter_type) {
|
||||
$filter_type = Type::getMixed();
|
||||
}
|
||||
|
||||
if ($statements_source->data_flow_graph
|
||||
&& !in_array('TaintedInput', $statements_source->getSuppressedIssues())
|
||||
) {
|
||||
$function_return_sink = DataFlowNode::getForMethodReturn(
|
||||
$function_id,
|
||||
$function_id,
|
||||
null,
|
||||
$options = null;
|
||||
$flags_int_used = FILTER_FLAG_NONE;
|
||||
if (isset($call_args[2])) {
|
||||
$helper = FilterUtils::getOptionsArgValueOrError(
|
||||
$call_args[2],
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
$code_location,
|
||||
);
|
||||
|
||||
$statements_source->data_flow_graph->addNode($function_return_sink);
|
||||
|
||||
$function_param_sink = DataFlowNode::getForMethodArgument(
|
||||
$function_id,
|
||||
$function_id,
|
||||
0,
|
||||
null,
|
||||
$code_location,
|
||||
$filter_int_used,
|
||||
);
|
||||
|
||||
$statements_source->data_flow_graph->addNode($function_param_sink);
|
||||
if (!is_array($helper)) {
|
||||
return $helper;
|
||||
}
|
||||
|
||||
$statements_source->data_flow_graph->addPath(
|
||||
$function_param_sink,
|
||||
$function_return_sink,
|
||||
'arg',
|
||||
);
|
||||
|
||||
return $filter_type->setParentNodes([$function_return_sink->id => $function_return_sink]);
|
||||
$flags_int_used = $helper['flags_int_used'];
|
||||
$options = $helper['options'];
|
||||
}
|
||||
|
||||
return $filter_type;
|
||||
// if we reach this point with callback, the callback is missing
|
||||
if ($filter_int_used === FILTER_CALLBACK) {
|
||||
return FilterUtils::missingFilterCallbackCallable(
|
||||
$function_id,
|
||||
$code_location,
|
||||
$statements_analyzer,
|
||||
$codebase,
|
||||
);
|
||||
}
|
||||
|
||||
[$default, $min_range, $max_range, $has_range, $regexp] = FilterUtils::getOptions(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$options,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$function_id,
|
||||
);
|
||||
|
||||
if (!$default) {
|
||||
[$fails_type] = FilterUtils::getFailsNotSetType($flags_int_used);
|
||||
} else {
|
||||
$fails_type = $default;
|
||||
}
|
||||
|
||||
if ($filter_int_used === FILTER_VALIDATE_REGEXP && $regexp === null) {
|
||||
if ($codebase->analysis_php_version_id >= 8_00_00) {
|
||||
// throws
|
||||
return Type::getNever();
|
||||
}
|
||||
|
||||
// any "array" flags are ignored by this filter!
|
||||
return $fails_type;
|
||||
}
|
||||
|
||||
$input_type = $statements_analyzer->node_data->getType($call_args[0]->value);
|
||||
|
||||
// only return now, as we still want to report errors above
|
||||
if (!$input_type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$redundant_error_return_type = FilterUtils::checkRedundantFlags(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$fails_type,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
);
|
||||
if ($redundant_error_return_type !== null) {
|
||||
return $redundant_error_return_type;
|
||||
}
|
||||
|
||||
return FilterUtils::getReturnType(
|
||||
$filter_int_used,
|
||||
$flags_int_used,
|
||||
$input_type,
|
||||
$fails_type,
|
||||
null,
|
||||
$statements_analyzer,
|
||||
$code_location,
|
||||
$codebase,
|
||||
$function_id,
|
||||
$has_range,
|
||||
$min_range,
|
||||
$max_range,
|
||||
$regexp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
9
src/Psalm/Issue/RedundantFlag.php
Normal file
9
src/Psalm/Issue/RedundantFlag.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Issue;
|
||||
|
||||
final class RedundantFlag extends CodeIssue
|
||||
{
|
||||
public const ERROR_LEVEL = 1;
|
||||
public const SHORTCODE = 322;
|
||||
}
|
@ -1028,6 +1028,41 @@ class FunctionCallTest extends TestCase
|
||||
'$d' => 'string',
|
||||
],
|
||||
],
|
||||
'filterInput' => [
|
||||
'code' => '<?php
|
||||
function filterInt(string $s) : int {
|
||||
$filtered = filter_var($s, FILTER_VALIDATE_INT);
|
||||
if ($filtered === false) {
|
||||
return 0;
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
function filterNullableInt(string $s) : ?int {
|
||||
return filter_var($s, FILTER_VALIDATE_INT, ["options" => ["default" => null]]);
|
||||
}
|
||||
function filterIntWithDefault(string $s) : int {
|
||||
return filter_var($s, FILTER_VALIDATE_INT, ["options" => ["default" => 5]]);
|
||||
}
|
||||
function filterBool(string $s) : bool {
|
||||
return filter_var($s, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
function filterNullableBool(string $s) : ?bool {
|
||||
return filter_var($s, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
}
|
||||
function filterNullableBoolWithFlagsArray(string $s) : ?bool {
|
||||
return filter_var($s, FILTER_VALIDATE_BOOLEAN, ["flags" => FILTER_NULL_ON_FAILURE]);
|
||||
}
|
||||
function filterFloat(string $s) : float {
|
||||
$filtered = filter_var($s, FILTER_VALIDATE_FLOAT);
|
||||
if ($filtered === false) {
|
||||
return 0.0;
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
function filterFloatWithDefault(string $s) : float {
|
||||
return filter_var($s, FILTER_VALIDATE_FLOAT, ["options" => ["default" => 5.0]]);
|
||||
}',
|
||||
],
|
||||
'filterVar' => [
|
||||
'code' => '<?php
|
||||
function filterInt(string $s) : int {
|
||||
|
@ -1418,10 +1418,9 @@ class ConditionalTest extends TestCase
|
||||
'nullCoalescePossibleMixed' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @psalm-suppress MixedReturnStatement
|
||||
* @psalm-suppress MixedInferredReturnType
|
||||
* @return array<never, never>|false|string
|
||||
*/
|
||||
function foo() : array {
|
||||
function foo() {
|
||||
return filter_input(INPUT_POST, "some_var") ?? [];
|
||||
}',
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user