From 882a87e5bb7dea1d310ae53f77e7ca97dc498178 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Wed, 14 Oct 2020 14:31:11 +0200 Subject: [PATCH 1/4] Update functionMap.php Correct return type of the `hash` function. [The PHP docs](https://www.php.net/manual/en/function.hash.php) say that this function always returns a string, however this is not true. It can also return a boolean `false` if you pass in the name of a non-existent hash algorithm. For example: var_dump(@hash('foo', 'bar', true)); bool(false) --- resources/functionMap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'], From 1bbd625103ef83c7081e6d131cd9522837d0fa56 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Wed, 14 Oct 2020 15:38:44 +0200 Subject: [PATCH 2/4] Test for hash return types --- .../Php/HashFunctionsReturnTypeExtension.php | 50 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 12 +++++ tests/PHPStan/Analyser/data/functions.php | 3 ++ 3 files changed, 65 insertions(+) create mode 100644 src/Type/Php/HashFunctionsReturnTypeExtension.php diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php new file mode 100644 index 0000000000..268f3e2cd3 --- /dev/null +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -0,0 +1,50 @@ +getName() === 'hash'; + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + if ($functionReflection->getName() === 'hash') { + $defaultReturnType = new StringType(); + } else { + $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) ? $defaultReturnType : new ConstantBooleanType(false); + } + +} diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ce50fcfc77..2380fab02c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5954,6 +5954,18 @@ public function dataFunctions(): array '(string|false)', '$hashHmacFileVariable', ], + [ + 'string', + '$hash', + ], + [ + 'string', + '$hashRaw', + ], + [ + 'false', + '$hashRandom', + ], ]; } diff --git a/tests/PHPStan/Analyser/data/functions.php b/tests/PHPStan/Analyser/data/functions.php index 9af80a8edf..179d0b1840 100644 --- a/tests/PHPStan/Analyser/data/functions.php +++ b/tests/PHPStan/Analyser/data/functions.php @@ -135,4 +135,7 @@ $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); die; From 675e347f751196b9e7bb4c8c102d2af3ed6d8407 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Wed, 14 Oct 2020 18:51:30 +0200 Subject: [PATCH 3/4] Requested PR changes --- src/Type/Php/HashFunctionsReturnTypeExtension.php | 8 ++------ tests/PHPStan/Analyser/NodeScopeResolverTest.php | 4 ++++ tests/PHPStan/Analyser/data/functions.php | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Type/Php/HashFunctionsReturnTypeExtension.php b/src/Type/Php/HashFunctionsReturnTypeExtension.php index 268f3e2cd3..402bc24196 100644 --- a/src/Type/Php/HashFunctionsReturnTypeExtension.php +++ b/src/Type/Php/HashFunctionsReturnTypeExtension.php @@ -23,11 +23,7 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type { - if ($functionReflection->getName() === 'hash') { - $defaultReturnType = new StringType(); - } else { - $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); - } + $defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); if (!isset($functionCall->args[0])) { return $defaultReturnType; @@ -44,7 +40,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $string = $values[0]; - return in_array($string->getValue(), hash_algos(), true) ? $defaultReturnType : new ConstantBooleanType(false); + 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 2380fab02c..f4f1809671 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5966,6 +5966,10 @@ public function dataFunctions(): array 'false', '$hashRandom', ], + [ + 'string', + '$hashMixed', + ], ]; } diff --git a/tests/PHPStan/Analyser/data/functions.php b/tests/PHPStan/Analyser/data/functions.php index 179d0b1840..173a4cd5f5 100644 --- a/tests/PHPStan/Analyser/data/functions.php +++ b/tests/PHPStan/Analyser/data/functions.php @@ -138,4 +138,7 @@ $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; From 1e6a47a51cac660e556f6696580b6c70c9a6ee72 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Wed, 14 Oct 2020 19:21:47 +0200 Subject: [PATCH 4/4] Add class to config.neon --- conf/config.neon | 5 +++++ 1 file changed, 5 insertions(+) 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: