Skip to content

Commit ecfa6c2

Browse files
committed
Add more precise return types for the openssl cipher functions
1 parent 7644bd0 commit ecfa6c2

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,11 @@ services:
15801580
tags:
15811581
- phpstan.dynamicFunctionThrowTypeExtension
15821582

1583+
-
1584+
class: PHPStan\Type\Php\OpensslCipherFunctionsReturnTypeExtension
1585+
tags:
1586+
- phpstan.broker.dynamicFunctionReturnTypeExtension
1587+
15831588
-
15841589
class: PHPStan\Type\Php\OpenSslEncryptParameterOutTypeExtension
15851590
tags:
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Php\PhpVersion;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\Constant\ConstantBooleanType;
11+
use PHPStan\Type\Constant\ConstantStringType;
12+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
15+
use function array_map;
16+
use function array_unique;
17+
use function count;
18+
use function in_array;
19+
use function is_null;
20+
use function openssl_get_cipher_methods;
21+
use function strtoupper;
22+
23+
final class OpensslCipherFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension
24+
{
25+
26+
/** @var string[]|null */
27+
private ?array $supportedAlgorithms = null;
28+
29+
public function __construct(private PhpVersion $phpVersion)
30+
{
31+
}
32+
33+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
34+
{
35+
return in_array($functionReflection->getName(), ['openssl_cipher_iv_length', 'openssl_cipher_key_length'], true);
36+
}
37+
38+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
39+
{
40+
$returnType = ParametersAcceptorSelector::selectFromArgs(
41+
$scope,
42+
$functionCall->getArgs(),
43+
$functionReflection->getVariants(),
44+
)->getReturnType();
45+
46+
if (!$this->phpVersion->throwsValueErrorForInternalFunctions()) {
47+
return $returnType;
48+
}
49+
50+
if (count($functionCall->getArgs()) < 1) {
51+
return $returnType;
52+
}
53+
54+
$strings = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings();
55+
$results = array_unique(array_map(fn (ConstantStringType $algorithm): bool => $this->isSupportedAlgorithm($algorithm->getValue()), $strings));
56+
57+
if (count($results) === 1) {
58+
return $results[0]
59+
? TypeCombinator::remove($returnType, new ConstantBooleanType(false))
60+
: new ConstantBooleanType(false);
61+
}
62+
63+
return $returnType;
64+
}
65+
66+
private function isSupportedAlgorithm(string $algorithm): bool
67+
{
68+
return in_array(strtoupper($algorithm), $this->getSupportedAlgorithms(), true);
69+
}
70+
71+
/** @return string[] */
72+
private function getSupportedAlgorithms(): array
73+
{
74+
if (!is_null($this->supportedAlgorithms)) {
75+
return $this->supportedAlgorithms;
76+
}
77+
78+
$supportedAlgorithms = [];
79+
if (function_exists('openssl_get_cipher_methods')) {
80+
$supportedAlgorithms = openssl_get_cipher_methods(true);
81+
}
82+
$this->supportedAlgorithms = array_map('strtoupper', $supportedAlgorithms);
83+
84+
return $this->supportedAlgorithms;
85+
}
86+
87+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php // lint < 8.0
2+
3+
namespace OpensslCipherPhp7;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class OpensslCipher
8+
{
9+
10+
/**
11+
* @param 'aes-256-cbc'|'aes128'|'aes-128-cbc' $validAlgorithms
12+
* @param 'aes-256-cbc'|'invalid' $validAndInvalidAlgorithms
13+
*/
14+
public function doFoo(string $s, $validAlgorithms, $validAndInvalidAlgorithms)
15+
{
16+
assertType('int|false', openssl_cipher_iv_length('aes-256-cbc'));
17+
assertType('int|false', openssl_cipher_iv_length('AES-256-CBC'));
18+
assertType('int|false', openssl_cipher_iv_length('unsupported'));
19+
assertType('int|false', openssl_cipher_iv_length($s));
20+
assertType('int|false', openssl_cipher_iv_length($validAlgorithms));
21+
assertType('int|false', openssl_cipher_iv_length($validAndInvalidAlgorithms));
22+
23+
assertType('int|false', openssl_cipher_key_length('aes-256-cbc'));
24+
assertType('int|false', openssl_cipher_key_length('AES-256-CBC'));
25+
assertType('int|false', openssl_cipher_key_length('unsupported'));
26+
assertType('int|false', openssl_cipher_key_length($s));
27+
assertType('int|false', openssl_cipher_key_length($validAlgorithms));
28+
assertType('int|false', openssl_cipher_key_length($validAndInvalidAlgorithms));
29+
}
30+
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php // lint >= 8.0
2+
3+
namespace OpensslCipherPhp8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class OpensslCipher
8+
{
9+
10+
/**
11+
* @param 'aes-256-cbc'|'aes128'|'aes-128-cbc' $validAlgorithms
12+
* @param 'aes-256-cbc'|'invalid' $validAndInvalidAlgorithms
13+
*/
14+
public function doFoo(string $s, $validAlgorithms, $validAndInvalidAlgorithms)
15+
{
16+
assertType('int', openssl_cipher_iv_length('aes-256-cbc'));
17+
assertType('int', openssl_cipher_iv_length('AES-256-CBC'));
18+
assertType('false', openssl_cipher_iv_length('unsupported'));
19+
assertType('int|false', openssl_cipher_iv_length($s));
20+
assertType('int', openssl_cipher_iv_length($validAlgorithms));
21+
assertType('int|false', openssl_cipher_iv_length($validAndInvalidAlgorithms));
22+
23+
assertType('int', openssl_cipher_key_length('aes-256-cbc'));
24+
assertType('int', openssl_cipher_key_length('AES-256-CBC'));
25+
assertType('false', openssl_cipher_key_length('unsupported'));
26+
assertType('int|false', openssl_cipher_key_length($s));
27+
assertType('int', openssl_cipher_key_length($validAlgorithms));
28+
assertType('int|false', openssl_cipher_key_length($validAndInvalidAlgorithms));
29+
}
30+
31+
}

0 commit comments

Comments
 (0)