diff --git a/conf/config.neon b/conf/config.neon index 977fb09f96..010e1e9e46 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -895,6 +895,11 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension + - + class: PHPStan\Type\Php\HashFunctionsReturnTypeExtension + tags: + - phpstan.broker.dynamicFunctionReturnTypeExtension + - class: PHPStan\Type\Php\SimpleXMLElementClassPropertyReflectionExtension tags: diff --git a/resources/functionMap.php b/resources/functionMap.php index 5fe95c7f47..f9d4b24d28 100644 --- a/resources/functionMap.php +++ b/resources/functionMap.php @@ -3906,7 +3906,7 @@ 'HaruPage::stroke' => ['bool', 'close_path='=>'bool'], 'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'], 'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'], -'hash' => ['string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], +'hash' => ['string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'], 'hash_algos' => ['array'], 'hash_copy' => ['HashContext', 'context'=>'HashContext'], 'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'], diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php new file mode 100644 index 0000000000..402bc24196 --- /dev/null +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -0,0 +1,46 @@ +getName() === 'hash'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + + if (!isset($functionCall->args[0])) { + return $defaultReturnType; + } + + $argType = $scope->getType($functionCall->args[0]->value); + if ($argType instanceof MixedType) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + + $values = TypeUtils::getConstantStrings($argType); + if (count($values) !== 1) { + return TypeUtils::toBenevolentUnion($defaultReturnType); + } + $string = $values[0]; + + return in_array($string->getValue(), hash_algos(), true) ? new StringType() : new ConstantBooleanType(false); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 771ce6ee55..c3c22e8f5a 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5954,6 +5954,22 @@ public function dataFunctions(): array '(string|false)', '$hashHmacFileVariable', ], + [ + 'string', + '$hash', + ], + [ + 'string', + '$hashRaw', + ], + [ + 'false', + '$hashRandom', + ], + [ + 'string', + '$hashMixed', + ], ]; } diff --git a/tests/PHPStan/Analyser/data/functions.php b/tests/PHPStan/Analyser/data/functions.php index 9af80a8edf..173a4cd5f5 100644 --- a/tests/PHPStan/Analyser/data/functions.php +++ b/tests/PHPStan/Analyser/data/functions.php @@ -135,4 +135,10 @@ $hashHmacFileRandom = hash_hmac_file('random', 'data', 'key'); $hashHmacFileVariable = hash_hmac_file($string, 'data', 'key'); +$hash = hash('sha256', 'data', false); +$hashRaw = hash('sha256', 'data', true); +$hashRandom = hash('random', 'data', false); +/** @var mixed $mixed */ +$mixed = doFoo(); +$hashMixed = hash('md5', $mixed, false); die;