Skip to content

Commit 2cb89ae

Browse files
More precise hash return type
1 parent b9894fa commit 2cb89ae

File tree

6 files changed

+84
-59
lines changed

6 files changed

+84
-59
lines changed

resources/functionMap.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3909,18 +3909,18 @@
39093909
'HaruPage::stroke' => ['bool', 'close_path='=>'bool'],
39103910
'HaruPage::textOut' => ['bool', 'x'=>'float', 'y'=>'float', 'text'=>'string'],
39113911
'HaruPage::textRect' => ['bool', 'left'=>'float', 'top'=>'float', 'right'=>'float', 'bottom'=>'float', 'text'=>'string', 'align='=>'int'],
3912-
'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
3912+
'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
39133913
'hash_algos' => ['non-empty-list<non-falsy-string>'],
39143914
'hash_copy' => ['HashContext', 'context'=>'HashContext'],
39153915
'hash_equals' => ['bool', 'known_string'=>'string', 'user_string'=>'string'],
3916-
'hash_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
3917-
'hash_final' => ['non-empty-string', 'context'=>'HashContext', 'raw_output='=>'bool'],
3918-
'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
3919-
'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
3916+
'hash_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'raw_output='=>'bool'],
3917+
'hash_final' => ['non-falsy-string', 'context'=>'HashContext', 'raw_output='=>'bool'],
3918+
'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
3919+
'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
39203920
'hash_hmac_algos' => ['non-empty-list<non-falsy-string>'],
3921-
'hash_hmac_file' => ['non-empty-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
3921+
'hash_hmac_file' => ['non-falsy-string|false', 'algo'=>'string', 'filename'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
39223922
'hash_init' => ['HashContext', 'algo'=>'string', 'options='=>'int', 'key='=>'string'],
3923-
'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
3923+
'hash_pbkdf2' => ['(non-falsy-string&lowercase-string)|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
39243924
'hash_update' => ['bool', 'context'=>'HashContext', 'data'=>'string'],
39253925
'hash_update_file' => ['bool', 'context'=>'HashContext', 'filename'=>'string', 'scontext='=>'?HashContext'],
39263926
'hash_update_stream' => ['int', 'context'=>'HashContext', 'handle'=>'resource', 'length='=>'int'],

resources/functionMap_php80delta.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
'gmmktime' => ['int|false', 'hour'=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'],
5454
'hash' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
5555
'hash_hkdf' => ['non-falsy-string', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
56-
'hash_hmac' => ['non-empty-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
57-
'hash_pbkdf2' => ['non-empty-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
56+
'hash_hmac' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
57+
'hash_pbkdf2' => ['non-falsy-string', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
5858
'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
5959
'imagecreate' => ['__benevolent<GdImage|false>', 'width'=>'int', 'height'=>'int'],
6060
'imagecreatefrombmp' => ['false|object', 'filename'=>'string'],
@@ -192,10 +192,10 @@
192192
'gmmktime' => ['int|false', 'hour='=>'int', 'minute='=>'int', 'second='=>'int', 'month='=>'int', 'day='=>'int', 'year='=>'int'],
193193
'gmp_random' => ['GMP', 'limiter='=>'int'],
194194
'gzgetss' => ['string|false', 'zp'=>'resource', 'length'=>'int', 'allowable_tags='=>'string'],
195-
'hash' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
196-
'hash_hkdf' => ['non-empty-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
197-
'hash_hmac' => ['non-empty-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
198-
'hash_pbkdf2' => ['non-empty-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
195+
'hash' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'raw_output='=>'bool'],
196+
'hash_hkdf' => ['non-falsy-string|false', 'algo'=>'string', 'key'=>'string', 'length='=>'int', 'info='=>'string', 'salt='=>'string'],
197+
'hash_hmac' => ['non-falsy-string|false', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
198+
'hash_pbkdf2' => ['non-falsy-string|false', 'algo'=>'string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'int', 'length='=>'int', 'raw_output='=>'bool'],
199199
'hebrevc' => ['string', 'str'=>'string', 'max_chars_per_line='=>'int'],
200200
'image2wbmp' => ['bool', 'im'=>'resource', 'filename='=>'?string', 'threshold='=>'int'],
201201
'imageaffine' => ['resource|false', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],

src/Type/Php/HashFunctionsReturnTypeExtension.php

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Php\PhpVersion;
88
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\ParametersAcceptorSelector;
10-
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
9+
use PHPStan\Type\Accessory\AccessoryLowercaseStringType;
10+
use PHPStan\Type\Accessory\AccessoryNonFalsyStringType;
1111
use PHPStan\Type\Constant\ConstantBooleanType;
1212
use PHPStan\Type\Constant\ConstantStringType;
1313
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
1414
use PHPStan\Type\IntersectionType;
15-
use PHPStan\Type\MixedType;
1615
use PHPStan\Type\NeverType;
1716
use PHPStan\Type\StringType;
1817
use PHPStan\Type\Type;
1918
use PHPStan\Type\TypeCombinator;
2019
use PHPStan\Type\TypeUtils;
2120
use function array_map;
21+
use function count;
2222
use function hash_algos;
2323
use function in_array;
24+
use function is_bool;
2425
use function strtolower;
2526

2627
final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -30,26 +31,32 @@ final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTyp
3031
'hash' => [
3132
'cryptographic' => false,
3233
'possiblyFalse' => false,
34+
'binary' => 2,
3335
],
3436
'hash_file' => [
3537
'cryptographic' => false,
3638
'possiblyFalse' => true,
39+
'binary' => 2,
3740
],
3841
'hash_hkdf' => [
3942
'cryptographic' => true,
4043
'possiblyFalse' => false,
44+
'binary' => true,
4145
],
4246
'hash_hmac' => [
4347
'cryptographic' => true,
4448
'possiblyFalse' => false,
49+
'binary' => 3,
4550
],
4651
'hash_hmac_file' => [
4752
'cryptographic' => true,
4853
'possiblyFalse' => true,
54+
'binary' => 3,
4955
],
5056
'hash_pbkdf2' => [
5157
'cryptographic' => true,
5258
'possiblyFalse' => false,
59+
'binary' => 5,
5360
],
5461
];
5562

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

89-
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
96+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
9097
{
91-
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
92-
$scope,
93-
$functionCall->getArgs(),
94-
$functionReflection->getVariants(),
95-
)->getReturnType();
96-
9798
if (!isset($functionCall->getArgs()[0])) {
98-
return $defaultReturnType;
99+
return null;
99100
}
100101

101-
$algorithmType = $scope->getType($functionCall->getArgs()[0]->value);
102-
if ($algorithmType instanceof MixedType) {
103-
return TypeUtils::toBenevolentUnion($defaultReturnType);
102+
$functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())];
103+
if (is_bool($functionData['binary'])) {
104+
$binaryType = new ConstantBooleanType($functionData['binary']);
105+
} elseif (isset($functionCall->getArgs()[$functionData['binary']])) {
106+
$binaryType = $scope->getType($functionCall->getArgs()[$functionData['binary']]->value);
107+
} else {
108+
$binaryType = new ConstantBooleanType(false);
109+
}
110+
111+
$stringTypes = [
112+
new StringType(),
113+
new AccessoryNonFalsyStringType(),
114+
];
115+
if ($binaryType->isFalse()->yes()) {
116+
$stringTypes[] = new AccessoryLowercaseStringType();
104117
}
118+
$stringReturnType = new IntersectionType($stringTypes);
105119

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

108-
if ($constantAlgorithmTypes === []) {
109-
return TypeUtils::toBenevolentUnion($defaultReturnType);
127+
return $stringReturnType;
110128
}
111129

112130
$neverType = new NeverType();
113131
$falseType = new ConstantBooleanType(false);
114-
$nonEmptyString = new IntersectionType([
115-
new StringType(),
116-
new AccessoryNonEmptyStringType(),
117-
]);
118-
119132
$invalidAlgorithmType = $this->phpVersion->throwsValueErrorForInternalFunctions() ? $neverType : $falseType;
120-
$functionData = self::SUPPORTED_FUNCTIONS[strtolower($functionReflection->getName())];
121133

122134
$returnTypes = array_map(
123-
function (ConstantStringType $type) use ($functionData, $nonEmptyString, $invalidAlgorithmType) {
135+
function (ConstantStringType $type) use ($functionData, $stringReturnType, $invalidAlgorithmType) {
124136
$algorithm = strtolower($type->getValue());
125137
if (!in_array($algorithm, $this->hashAlgorithms, true)) {
126138
return $invalidAlgorithmType;
127139
}
128140
if ($functionData['cryptographic'] && in_array($algorithm, self::NON_CRYPTOGRAPHIC_ALGORITHMS, true)) {
129141
return $invalidAlgorithmType;
130142
}
131-
return $nonEmptyString;
143+
return $stringReturnType;
132144
},
133145
$constantAlgorithmTypes,
134146
);

tests/PHPStan/Analyser/nsrt/hash-functions-74.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public function hash_hmac(string $string): void
1111
{
1212
assertType('false', hash_hmac('crc32', 'data', 'key'));
1313
assertType('false', hash_hmac('invalid', 'data', 'key'));
14-
assertType('(non-empty-string|false)', hash_hmac($string, 'data', 'key'));
14+
assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac($string, 'data', 'key'));
15+
assertType('(non-falsy-string|false)', hash_hmac($string, 'data', 'key', true));
1516
}
1617

1718
public function hash_hmac_file(): void
@@ -23,7 +24,8 @@ public function hash_hmac_file(): void
2324
public function hash(string $string): void
2425
{
2526
assertType('false', hash('invalid', 'data', false));
26-
assertType('(non-empty-string|false)', hash($string, 'data'));
27+
assertType('((lowercase-string&non-falsy-string)|false)', hash($string, 'data'));
28+
assertType('(non-falsy-string|false)', hash($string, 'data', true));
2729
}
2830

2931
public function hash_file(): void
@@ -35,14 +37,15 @@ public function hash_hkdf(string $string): void
3537
{
3638
assertType('false', hash_hkdf('crc32', 'key'));
3739
assertType('false', hash_hkdf('invalid', 'key'));
38-
assertType('(non-empty-string|false)', hash_hkdf($string, 'key'));
40+
assertType('(non-falsy-string|false)', hash_hkdf($string, 'key'));
3941
}
4042

4143
public function hash_pbkdf2(string $string): void
4244
{
4345
assertType('false', hash_pbkdf2('crc32', 'password', 'salt', 1000));
4446
assertType('false', hash_pbkdf2('invalid', 'password', 'salt', 1000));
45-
assertType('(non-empty-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000));
47+
assertType('((lowercase-string&non-falsy-string)|false)', hash_pbkdf2($string, 'password', 'salt', 1000));
48+
assertType('(non-falsy-string|false)', hash_pbkdf2($string, 'password', 'salt', 1000, 0, true));
4649
}
4750

4851
public function caseSensitive()

tests/PHPStan/Analyser/nsrt/hash-functions-80.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ public function hash_hmac(string $string): void
1111
{
1212
assertType('*NEVER*', hash_hmac('crc32', 'data', 'key'));
1313
assertType('*NEVER*', hash_hmac('invalid', 'data', 'key'));
14-
assertType('non-empty-string', hash_hmac($string, 'data', 'key'));
14+
assertType('lowercase-string&non-falsy-string', hash_hmac($string, 'data', 'key'));
15+
assertType('non-falsy-string', hash_hmac($string, 'data', 'key', true));
1516
}
1617

1718
public function hash_hmac_file(): void
@@ -23,7 +24,7 @@ public function hash_hmac_file(): void
2324
public function hash(string $string): void
2425
{
2526
assertType('*NEVER*', hash('invalid', 'data', false));
26-
assertType('non-falsy-string', hash($string, 'data'));
27+
assertType('lowercase-string&non-falsy-string', hash($string, 'data'));
2728
}
2829

2930
public function hash_file(): void
@@ -42,7 +43,7 @@ public function hash_pbkdf2(string $string): void
4243
{
4344
assertType('*NEVER*', hash_pbkdf2('crc32', 'password', 'salt', 1000));
4445
assertType('*NEVER*', hash_pbkdf2('invalid', 'password', 'salt', 1000));
45-
assertType('non-empty-string', hash_pbkdf2($string, 'password', 'salt', 1000));
46+
assertType('lowercase-string&non-falsy-string', hash_pbkdf2($string, 'password', 'salt', 1000));
4647
}
4748

4849
public function caseSensitive()

tests/PHPStan/Analyser/nsrt/hash-functions.php

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,44 +13,52 @@ class HashFunctionTests
1313

1414
public function hash_hmac(): void
1515
{
16-
assertType('non-empty-string', hash_hmac('md5', 'data', 'key'));
17-
assertType('non-empty-string', hash_hmac('sha256', 'data', 'key'));
16+
assertType('lowercase-string&non-falsy-string', hash_hmac('md5', 'data', 'key'));
17+
assertType('non-falsy-string', hash_hmac('md5', 'data', 'key', true));
18+
assertType('lowercase-string&non-falsy-string', hash_hmac('sha256', 'data', 'key'));
19+
assertType('non-falsy-string', hash_hmac('sha256', 'data', 'key', true));
1820
}
1921

2022
public function hash_hmac_file(string $string): void
2123
{
22-
assertType('non-empty-string|false', hash_hmac_file('md5', 'filename', 'key'));
23-
assertType('non-empty-string|false', hash_hmac_file('sha256', 'filename', 'key'));
24-
assertType('(non-empty-string|false)', hash_hmac_file($string, 'filename', 'key'));
24+
assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('md5', 'filename', 'key'));
25+
assertType('non-falsy-string|false', hash_hmac_file('md5', 'filename', 'key', true));
26+
assertType('(lowercase-string&non-falsy-string)|false', hash_hmac_file('sha256', 'filename', 'key'));
27+
assertType('non-falsy-string|false', hash_hmac_file('sha256', 'filename', 'key', true));
28+
assertType('((lowercase-string&non-falsy-string)|false)', hash_hmac_file($string, 'filename', 'key'));
29+
assertType('(non-falsy-string|false)', hash_hmac_file($string, 'filename', 'key', true));
2530
}
2631

2732
public function hash($mixed): void
2833
{
29-
assertType('non-empty-string', hash('sha256', 'data', false));
30-
assertType('non-empty-string', hash('sha256', 'data', true));
31-
assertType('non-empty-string', hash('md5', $mixed, false));
34+
assertType('lowercase-string&non-falsy-string', hash('sha256', 'data', false));
35+
assertType('non-falsy-string', hash('sha256', 'data', true));
36+
assertType('lowercase-string&non-falsy-string', hash('md5', $mixed, false));
3237
}
3338

3439
public function hash_file(): void
3540
{
36-
assertType('non-empty-string|false', hash_file('sha256', 'filename', false));
37-
assertType('non-empty-string|false', hash_file('sha256', 'filename', true));
38-
assertType('non-empty-string|false', hash_file('crc32', 'filename'));
41+
assertType('(lowercase-string&non-falsy-string)|false', hash_file('sha256', 'filename', false));
42+
assertType('non-falsy-string|false', hash_file('sha256', 'filename', true));
43+
assertType('(lowercase-string&non-falsy-string)|false', hash_file('crc32', 'filename'));
44+
assertType('non-falsy-string|false', hash_file('crc32', 'filename', true));
3945
}
4046

4147
public function hash_hkdf(): void
4248
{
43-
assertType('non-empty-string', hash_hkdf('sha256', 'key'));
49+
assertType('non-falsy-string', hash_hkdf('sha256', 'key'));
4450
}
4551

4652
public function hash_pbkdf2(): void
4753
{
48-
assertType('non-empty-string', hash_pbkdf2('sha256', 'password', 'salt', 1000));
54+
assertType('lowercase-string&non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000));
55+
assertType('non-falsy-string', hash_pbkdf2('sha256', 'password', 'salt', 1000, 0, true));
4956
}
5057

5158
public function caseSensitive()
5259
{
53-
assertType('non-empty-string', hash('SHA256', 'data'));
60+
assertType('lowercase-string&non-falsy-string', hash('SHA256', 'data'));
61+
assertType('non-falsy-string', hash('SHA256', 'data', true));
5462
}
5563

5664
public function constantStrings(int $type)
@@ -69,7 +77,8 @@ public function constantStrings(int $type)
6977
return;
7078
}
7179

72-
assertType('non-empty-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000));
80+
assertType('lowercase-string&non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000));
81+
assertType('non-falsy-string', hash_pbkdf2($algorithm, 'password', 'salt', 1000, 0, true));
7382
}
7483

7584
}

0 commit comments

Comments
 (0)