Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions resources/functionMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -3909,18 +3909,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' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_algos' => ['non-empty-list<non-falsy-string>'],
'hash_copy' => ['HashContext', 'context'=>'HashContext'],
'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'],
'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_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
'hash_final' => ['non-falsy-string', 'context'=>'HashContext', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_algos' => ['non-empty-list<non-falsy-string>'],
'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_hmac_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['(non-falsy-string&lowercase-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
12 changes: 6 additions & 6 deletions resources/functionMap_php80delta.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'],
'hash' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-falsy-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'],
'hash_hmac' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['non-falsy-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
'imagecreate' => ['__benevolent<GdImage|false>', 'width'=>'int', 'height'=>'int'],
'imagecreatefrombmp' => ['false|object', 'filename'=>'string'],
Expand Down Expand Up @@ -192,10 +192,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' => ['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'],
'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['non-falsy-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
'hebrevc' => ['string', 'str'=>'string', 'max_chars_per_line='=>'int'],
'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'],
'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
Expand Down
60 changes: 36 additions & 24 deletions src/Type/Php/HashFunctionsReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
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 array_map;
use function count;
use function hash_algos;
use function in_array;
use function is_bool;
use function strtolower;

final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension
Expand All @@ -30,26 +31,32 @@ final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTyp
'hash' => [
'cryptographic' => false,
'possiblyFalse' => false,
'binary' => 2,
],
'hash_file' => [
'cryptographic' => false,
'possiblyFalse' => true,
'binary' => 2,
],
'hash_hkdf' => [
'cryptographic' => true,
'possiblyFalse' => false,
'binary' => true,
],
'hash_hmac' => [
'cryptographic' => true,
'possiblyFalse' => false,
'binary' => 3,
],
'hash_hmac_file' => [
'cryptographic' => true,
'possiblyFalse' => true,
'binary' => 3,
],
'hash_pbkdf2' => [
'cryptographic' => true,
'possiblyFalse' => false,
'binary' => 5,
],
];

Expand Down Expand Up @@ -86,49 +93,54 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
return isset(self::SUPPORTED_FUNCTIONS[$name]);
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
{
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
$scope,
$functionCall->getArgs(),
$functionReflection->getVariants(),
)->getReturnType();

if (!isset($functionCall->getArgs()[0])) {
return $defaultReturnType;
return null;
}

$algorithmType = $scope->getType($functionCall->getArgs()[0]->value);
if ($algorithmType instanceof MixedType) {
return TypeUtils::toBenevolentUnion($defaultReturnType);
$functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())];
if (is_bool($functionData['binary'])) {
$binaryType = new ConstantBooleanType($functionData['binary']);
} elseif (isset($functionCall->getArgs()[$functionData['binary']])) {
$binaryType = $scope->getType($functionCall->getArgs()[$functionData['binary']]->value);
} else {
$binaryType = new ConstantBooleanType(false);
}

$stringTypes = [
new StringType(),
new AccessoryNonFalsyStringType(),
];
if ($binaryType->isFalse()->yes()) {
$stringTypes[] = new AccessoryLowercaseStringType();
}
$stringReturnType = new IntersectionType($stringTypes);

$algorithmType = $scope->getType($functionCall->getArgs()[0]->value);
$constantAlgorithmTypes = $algorithmType->getConstantStrings();
if (count($constantAlgorithmTypes) === 0) {
if ($functionData['possiblyFalse'] || !$this->phpVersion->throwsValueErrorForInternalFunctions()) {
return TypeUtils::toBenevolentUnion(TypeCombinator::union($stringReturnType, new ConstantBooleanType(false)));
}

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

$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) {
function (ConstantStringType $type) use ($functionData, $stringReturnType, $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;
return $stringReturnType;
},
$constantAlgorithmTypes,
);
Expand Down
11 changes: 7 additions & 4 deletions tests/PHPStan/Analyser/nsrt/hash-functions-74.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public function hash_hmac(string $string): void
{
assertType('false', hash_hmac('crc32', 'data', 'key'));
assertType('false', hash_hmac('invalid', 'data', 'key'));
assertType('(non-empty-string|false)', hash_hmac($string, 'data', 'key'));
assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac($string, 'data', 'key'));
assertType('(non-falsy-string|false)', hash_hmac($string, 'data', 'key', true));
}

public function hash_hmac_file(): void
Expand All @@ -23,7 +24,8 @@ public function hash_hmac_file(): void
public function hash(string $string): void
{
assertType('false', hash('invalid', 'data', false));
assertType('(non-empty-string|false)', hash($string, 'data'));
assertType('((lowercase-string&non-falsy-string)|false)', hash($string, 'data'));
assertType('(non-falsy-string|false)', hash($string, 'data', true));
}

public function hash_file(): void
Expand All @@ -35,14 +37,15 @@ public function hash_hkdf(string $string): void
{
assertType('false', hash_hkdf('crc32', 'key'));
assertType('false', hash_hkdf('invalid', 'key'));
assertType('(non-empty-string|false)', hash_hkdf($string, 'key'));
assertType('(non-falsy-string|false)', hash_hkdf($string, 'key'));
}

public function hash_pbkdf2(string $string): void
{
assertType('false', hash_pbkdf2('crc32', 'password', 'salt', 1000));
assertType('false', hash_pbkdf2('invalid', 'password', 'salt', 1000));
assertType('(non-empty-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000));
assertType('((lowercase-string&non-falsy-string)|false)', hash_pbkdf2($string, 'password', 'salt', 1000));
assertType('(non-falsy-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000, 0, true));
}

public function caseSensitive()
Expand Down
7 changes: 4 additions & 3 deletions tests/PHPStan/Analyser/nsrt/hash-functions-80.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public function hash_hmac(string $string): void
{
assertType('*NEVER*', hash_hmac('crc32', 'data', 'key'));
assertType('*NEVER*', hash_hmac('invalid', 'data', 'key'));
assertType('non-empty-string', hash_hmac($string, 'data', 'key'));
assertType('lowercase-string&non-falsy-string', hash_hmac($string, 'data', 'key'));
assertType('non-falsy-string', hash_hmac($string, 'data', 'key', true));
}

public function hash_hmac_file(): void
Expand All @@ -23,7 +24,7 @@ public function hash_hmac_file(): void
public function hash(string $string): void
{
assertType('*NEVER*', hash('invalid', 'data', false));
assertType('non-falsy-string', hash($string, 'data'));
assertType('lowercase-string&non-falsy-string', hash($string, 'data'));
}

public function hash_file(): void
Expand All @@ -42,7 +43,7 @@ public function hash_pbkdf2(string $string): void
{
assertType('*NEVER*', hash_pbkdf2('crc32', 'password', 'salt', 1000));
assertType('*NEVER*', hash_pbkdf2('invalid', 'password', 'salt', 1000));
assertType('non-empty-string', hash_pbkdf2($string, 'password', 'salt', 1000));
assertType('lowercase-string&non-falsy-string', hash_pbkdf2($string, 'password', 'salt', 1000));
}

public function caseSensitive()
Expand Down
39 changes: 24 additions & 15 deletions tests/PHPStan/Analyser/nsrt/hash-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,52 @@ class HashFunctionTests

public function hash_hmac(): void
{
assertType('non-empty-string', hash_hmac('md5', 'data', 'key'));
assertType('non-empty-string', hash_hmac('sha256', 'data', 'key'));
assertType('lowercase-string&non-falsy-string', hash_hmac('md5', 'data', 'key'));
assertType('non-falsy-string', hash_hmac('md5', 'data', 'key', true));
assertType('lowercase-string&non-falsy-string', hash_hmac('sha256', 'data', 'key'));
assertType('non-falsy-string', hash_hmac('sha256', 'data', 'key', true));
}

public function hash_hmac_file(string $string): void
{
assertType('non-empty-string|false', hash_hmac_file('md5', 'filename', 'key'));
assertType('non-empty-string|false', hash_hmac_file('sha256', 'filename', 'key'));
assertType('(non-empty-string|false)', hash_hmac_file($string, 'filename', 'key'));
assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('md5', 'filename', 'key'));
assertType('non-falsy-string|false', hash_hmac_file('md5', 'filename', 'key', true));
assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('sha256', 'filename', 'key'));
assertType('non-falsy-string|false', hash_hmac_file('sha256', 'filename', 'key', true));
assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac_file($string, 'filename', 'key'));
assertType('(non-falsy-string|false)', hash_hmac_file($string, 'filename', 'key', true));
}

public function hash($mixed): void
{
assertType('non-empty-string', hash('sha256', 'data', false));
assertType('non-empty-string', hash('sha256', 'data', true));
assertType('non-empty-string', hash('md5', $mixed, false));
assertType('lowercase-string&non-falsy-string', hash('sha256', 'data', false));
assertType('non-falsy-string', hash('sha256', 'data', true));
assertType('lowercase-string&non-falsy-string', hash('md5', $mixed, false));
}

public function hash_file(): void
{
assertType('non-empty-string|false', hash_file('sha256', 'filename', false));
assertType('non-empty-string|false', hash_file('sha256', 'filename', true));
assertType('non-empty-string|false', hash_file('crc32', 'filename'));
assertType('(lowercase-string&non-falsy-string)|false', hash_file('sha256', 'filename', false));
assertType('non-falsy-string|false', hash_file('sha256', 'filename', true));
assertType('(lowercase-string&non-falsy-string)|false', hash_file('crc32', 'filename'));
assertType('non-falsy-string|false', hash_file('crc32', 'filename', true));
}

public function hash_hkdf(): void
{
assertType('non-empty-string', hash_hkdf('sha256', 'key'));
assertType('non-falsy-string', hash_hkdf('sha256', 'key'));
}

public function hash_pbkdf2(): void
{
assertType('non-empty-string', hash_pbkdf2('sha256', 'password', 'salt', 1000));
assertType('lowercase-string&non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000));
assertType('non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000, 0, true));
}

public function caseSensitive()
{
assertType('non-empty-string', hash('SHA256', 'data'));
assertType('lowercase-string&non-falsy-string', hash('SHA256', 'data'));
assertType('non-falsy-string', hash('SHA256', 'data', true));
}

public function constantStrings(int $type)
Expand All @@ -69,7 +77,8 @@ public function constantStrings(int $type)
return;
}

assertType('non-empty-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000));
assertType('lowercase-string&non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000));
assertType('non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000, 0, true));
}

}
Loading