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 Jan 21, 2022
1 parent 1aef99d commit a1c77cb
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 197 deletions.
5 changes: 0 additions & 5 deletions conf/config.neon
Expand Up @@ -1119,11 +1119,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
18 changes: 9 additions & 9 deletions resources/functionMap.php
Expand Up @@ -3902,18 +3902,18 @@
'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|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_algos' => ['array'],
'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'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_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
'hash_final' => ['non-empty-string', 'context'=>'HashContext', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_algos' => ['array<int,non-empty-string>'],
'hash_hmac_file' => ['non-empty-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_pbkdf2' => ['non-empty-string|false', 'algo'=>'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_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'],
Expand Down
10 changes: 8 additions & 2 deletions resources/functionMap_php80delta.php
Expand Up @@ -45,7 +45,10 @@
'get_resource_id' => ['int', 'res'=>'resource'],
'gmdate' => ['string', 'format'=>'string', 'timestamp='=>'int'],
'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'],
'hash_hkdf' => ['string', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-empty-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
'imagecreate' => ['false|object', 'x_size'=>'int', 'y_size'=>'int'],
'imagecreatefrombmp' => ['false|object', 'filename'=>'string'],
Expand Down Expand Up @@ -169,7 +172,10 @@
'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'],
'gmp_random' => ['GMP', 'limiter='=>'int'],
'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'],
'hash_hkdf' => ['string|false', 'algo'=>'string', 'ikm'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'],
'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
'imagecreate' => ['resource|false', 'x_size'=>'int', 'y_size'=>'int'],
Expand Down
108 changes: 100 additions & 8 deletions src/Type/Php/HashFunctionsReturnTypeExtension.php
Expand Up @@ -4,24 +4,86 @@

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
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\NeverType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function count;
use function array_map;
use function hash_algos;
use function in_array;
use function strtolower;

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(private PhpVersion $phpVersion)
{
$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 @@ -32,18 +94,48 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return $defaultReturnType;
}

$argType = $scope->getType($functionCall->getArgs()[0]->value);
if ($argType instanceof MixedType) {
$algorithmType = $scope->getType($functionCall->getArgs()[0]->value);
if ($algorithmType instanceof MixedType) {
return TypeUtils::toBenevolentUnion($defaultReturnType);
}

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

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

return in_array($string->getValue(), hash_algos(), true) ? new StringType() : new ConstantBooleanType(false);
$neverType = new NeverType();
$falseType = new ConstantBooleanType(false);
$nonEmptyString = new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);

$invalidAlgorithmType = $this->phpVersion->throwsValueErrorForInternalFunctions() ? $neverType : $falseType;
$functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())];

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

$returnType = TypeCombinator::union(...$returnTypes);

if ($functionData['possiblyFalse'] && !$neverType->isSuperTypeOf($returnType)->yes()) {
$returnType = TypeCombinator::union($returnType, $falseType);
}

return $returnType;
}

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

This file was deleted.

56 changes: 0 additions & 56 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Expand Up @@ -5618,62 +5618,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
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -619,6 +619,13 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6404.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6399.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4357.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/hash-functions.php');
if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/hash-functions-80.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/hash-functions-74.php');
}
}

/**
Expand Down

0 comments on commit a1c77cb

Please sign in to comment.