Skip to content

Commit

Permalink
Update return types for hash functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jlherren committed Dec 7, 2021
1 parent 1c2e4bd commit fa48721
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 191 deletions.
5 changes: 0 additions & 5 deletions conf/config.neon
Expand Up @@ -1107,11 +1107,6 @@ services:
class: PHPStan\Type\Php\GettimeofdayDynamicFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension
-
class: PHPStan\Type\Php\HashHmacFunctionsReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\HashFunctionsReturnTypeExtension
tags:
Expand Down
20 changes: 10 additions & 10 deletions resources/functionMap.php
Expand Up @@ -3902,19 +3902,19 @@
'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|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_algos' => ['array'],
'hash_algos' => ['array<int,non-empty-string>'],
'hash_copy' => ['HashContext', 'context'=>'HashContext'],
'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'],
'hash_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
'hash_final' => ['string', 'context'=>'HashContext', 'raw_output='=>'bool'],
'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_algos' => ['array<int,string>'],
'hash_hmac_file' => ['string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
'hash_pbkdf2' => ['string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'hash_file' => ['non-empty-string|false', 'algo'=>'non-empty-string', 'filename'=>'non-empty-string', 'raw_output='=>'bool'],
'hash_final' => ['non-empty-string', 'context'=>'HashContext', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-empty-string|false', 'algo'=>'non-empty-string', 'key'=>'non-empty-string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-empty-string|false', 'algo'=>'non-empty-string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_algos' => ['array<int,non-empty-string>'],
'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'non-empty-string', 'filename'=>'non-empty-string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_init' => ['HashContext', 'algo'=>'non-empty-string', 'options='=>'int', 'key='=>'string'],
'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'non-empty-string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'],
'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'],
'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'non-empty-string', 'scontext='=>'?HashContext'],
'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'],
'hashTableObj::clear' => ['void'],
'hashTableObj::get' => ['string', 'key'=>'string'],
Expand Down
94 changes: 89 additions & 5 deletions src/Type/Php/HashFunctionsReturnTypeExtension.php
Expand Up @@ -6,19 +6,78 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;

final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

private const SUPPORTED_FUNCTIONS = [
'hash' => [
'cryptographic' => false,
'possiblyFalse' => false,
],
'hash_file' => [
'cryptographic' => false,
'possiblyFalse' => true,
],
'hash_hkdf' => [
'cryptographic' => true,
'possiblyFalse' => false,
],
'hash_hmac' => [
'cryptographic' => true,
'possiblyFalse' => false,
],
'hash_hmac_file' => [
'cryptographic' => true,
'possiblyFalse' => true,
],
'hash_pbkdf2' => [
'cryptographic' => true,
'possiblyFalse' => false,
],
];

private const NON_CRYPTOGRAPHIC_ALGORITHMS = [
'adler32',
'crc32',
'crc32b',
'crc32c',
'fnv132',
'fnv1a32',
'fnv164',
'fnv1a64',
'joaat',
'murmur3a',
'murmur3c',
'murmur3f',
'xxh32',
'xxh64',
'xxh3',
'xxh128',
];

/** @var array<int, non-empty-string> */
private array $hashAlgorithms;

public function __construct()
{
$this->hashAlgorithms = hash_algos();
}

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return $functionReflection->getName() === 'hash';
$name = strtolower($functionReflection->getName());
return isset(self::SUPPORTED_FUNCTIONS[$name]);
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
Expand All @@ -34,13 +93,38 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return TypeUtils::toBenevolentUnion($defaultReturnType);
}

$values = TypeUtils::getConstantStrings($argType);
if (count($values) !== 1) {
$constantStringTypes = TypeUtils::getConstantStrings($argType);

if ($constantStringTypes === []) {
return TypeUtils::toBenevolentUnion($defaultReturnType);
}
$string = $values[0];

return in_array($string->getValue(), hash_algos(), true) ? new StringType() : new ConstantBooleanType(false);
$functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())];
$false = new ConstantBooleanType(false);
$nonEmptyString = new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);

$returnTypes = array_map(
function (ConstantStringType $type) use ($functionData, $nonEmptyString, $false) {
$algorithm = strtolower($type->getValue());
if (!in_array($algorithm, $this->hashAlgorithms, true)) {
return $false;
}
if ($functionData['cryptographic'] && in_array($algorithm, self::NON_CRYPTOGRAPHIC_ALGORITHMS, true)) {
return $false;
}
return $nonEmptyString;
},
$constantStringTypes,
);

if ($functionData['possiblyFalse']) {
$returnTypes[] = $false;
}

return TypeCombinator::union(...$returnTypes);
}

}
97 changes: 0 additions & 97 deletions src/Type/Php/HashHmacFunctionsReturnTypeExtension.php

This file was deleted.

56 changes: 0 additions & 56 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Expand Up @@ -5606,62 +5606,6 @@ public function dataFunctions(): array
'array<int, string>|int|false',
'$strWordCountStrTypeIndeterminant',
],
[
'string',
'$hashHmacMd5',
],
[
'string',
'$hashHmacSha256',
],
[
'false',
'$hashHmacNonCryptographic',
],
[
'false',
'$hashHmacRandom',
],
[
'string',
'$hashHmacVariable',
],
[
'string|false',
'$hashHmacFileMd5',
],
[
'string|false',
'$hashHmacFileSha256',
],
[
'false',
'$hashHmacFileNonCryptographic',
],
[
'false',
'$hashHmacFileRandom',
],
[
'(string|false)',
'$hashHmacFileVariable',
],
[
'string',
'$hash',
],
[
'string',
'$hashRaw',
],
[
'false',
'$hashRandom',
],
[
'string',
'$hashMixed',
],
];
}

Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -569,6 +569,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/filesystem-functions.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/classPhpDocs-phpstanPropertyPrefix.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/hash-functions.php');
}

/**
Expand Down
18 changes: 0 additions & 18 deletions tests/PHPStan/Analyser/data/functions.php
Expand Up @@ -123,22 +123,4 @@
$integer = doFoo();
$strWordCountStrTypeIndeterminant = str_word_count('string', $integer);

$hashHmacMd5 = hash_hmac('md5', 'data', 'key');
$hashHmacSha256 = hash_hmac('sha256', 'data', 'key');
$hashHmacNonCryptographic = hash_hmac('crc32', 'data', 'key');
$hashHmacRandom = hash_hmac('random', 'data', 'key');
$hashHmacVariable = hash_hmac($string, 'data', 'key');

$hashHmacFileMd5 = hash_hmac_file('md5', 'data', 'key');
$hashHmacFileSha256 = hash_hmac_file('sha256', 'data', 'key');
$hashHmacFileNonCryptographic = hash_hmac_file('crc32', 'data', 'key');
$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;

0 comments on commit fa48721

Please sign in to comment.