Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/1.12.x' into 2.1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 16, 2025
2 parents 30b9cd8 + 0cd6324 commit 6d74dcf
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 63 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
build: cs tests phpstan

tests:
php vendor/bin/paratest --runner WrapperRunner --no-coverage
XDEBUG_MODE=off php vendor/bin/paratest --runner WrapperRunner --no-coverage

tests-integration:
php vendor/bin/paratest --runner WrapperRunner --no-coverage --group exec
Expand All @@ -18,7 +18,7 @@ tests-golden-reflection:
php vendor/bin/paratest --runner WrapperRunner --no-coverage tests/PHPStan/Reflection/ReflectionProviderGoldenTest.php

lint:
php vendor/bin/parallel-lint --colors \
XDEBUG_MODE=off php vendor/bin/parallel-lint --colors \
--exclude tests/PHPStan/Analyser/data \
--exclude tests/PHPStan/Analyser/nsrt \
--exclude tests/PHPStan/Rules/Methods/data \
Expand Down Expand Up @@ -99,10 +99,10 @@ lint:
src tests

cs:
composer install --working-dir build-cs && php build-cs/vendor/bin/phpcs
composer install --working-dir build-cs && XDEBUG_MODE=off php build-cs/vendor/bin/phpcs

cs-fix:
php build-cs/vendor/bin/phpcbf
XDEBUG_MODE=off php build-cs/vendor/bin/phpcbf

phpstan:
php bin/phpstan clear-result-cache -q && php -d memory_limit=448M bin/phpstan
Expand Down
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 @@ -57,8 +57,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'=>'non-falsy-string', 'key'=>'string', 'length='=>'0|positive-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'=>'non-falsy-string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'positive-int', 'length='=>'0|positive-int', 'raw_output='=>'bool'],
'hash_hmac' => ['non-falsy-string', 'algo'=>'string', 'data'=>'string', 'key'=>'string', 'raw_output='=>'bool'],
'hash_pbkdf2' => ['non-falsy-string', 'algo'=>'non-falsy-string', 'password'=>'string', 'salt'=>'string', 'iterations'=>'positive-int', 'length='=>'0|positive-int', 'raw_output='=>'bool'],
'imageaffine' => ['false|object', 'src'=>'resource', 'affine'=>'array', 'clip='=>'array'],
'imagecreate' => ['__benevolent<GdImage|false>', 'width'=>'int<1, max>', 'height'=>'int<1, max>'],
'imagecreatefrombmp' => ['false|object', 'filename'=>'string'],
Expand Down Expand Up @@ -197,10 +197,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));
}

}

0 comments on commit 6d74dcf

Please sign in to comment.