diff --git a/src/Type/Php/OpenSslCipherMethodsProvider.php b/src/Type/Php/OpenSslCipherMethodsProvider.php new file mode 100644 index 0000000000..45d197a301 --- /dev/null +++ b/src/Type/Php/OpenSslCipherMethodsProvider.php @@ -0,0 +1,57 @@ +|null */ + private ?array $supportedCipherMethods = null; + + /** + * Returns supported cipher methods in lowercase. + * + * Filters out methods that openssl_get_cipher_methods() reports + * but are not actually usable due to https://github.com/php/php-src/issues/19994 + * + * @return list + */ + public function getSupportedCipherMethods(): array + { + if ($this->supportedCipherMethods !== null) { + return $this->supportedCipherMethods; + } + + $methods = []; + if (function_exists('openssl_get_cipher_methods')) { + // openssl_get_cipher_methods() reports algorithms that are not actually + // supported on PHP 8.0-8.4 due to https://github.com/php/php-src/issues/19994 + // Filter by actually testing each algorithm with openssl_cipher_iv_length(). + $methods = array_values(array_filter( + openssl_get_cipher_methods(true), + static fn (string $algorithm): bool => @openssl_cipher_iv_length($algorithm) !== false, + )); + } + + $this->supportedCipherMethods = array_map('strtolower', $methods); + + return $this->supportedCipherMethods; + } + + public function isSupportedCipherMethod(string $method): bool + { + return in_array(strtolower($method), $this->getSupportedCipherMethods(), true); + } + +} diff --git a/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php index c457b73257..bc38ad0519 100644 --- a/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php +++ b/src/Type/Php/OpenSslEncryptParameterOutTypeExtension.php @@ -16,7 +16,6 @@ use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use function in_array; -use function openssl_get_cipher_methods; use function strtolower; use function substr; @@ -24,6 +23,10 @@ final class OpenSslEncryptParameterOutTypeExtension implements FunctionParameterOutTypeExtension { + public function __construct(private OpenSslCipherMethodsProvider $cipherMethodsProvider) + { + } + public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool { return $functionReflection->getName() === 'openssl_encrypt' && $parameter->getName() === 'tag'; @@ -44,7 +47,7 @@ public function getParameterOutTypeFromFunctionCall(FunctionReflection $function $cipher = strtolower($cipherType->getValue()); $mode = substr($cipher, -3); - if (!in_array($cipher, openssl_get_cipher_methods(), true)) { + if (!$this->cipherMethodsProvider->isSupportedCipherMethod($cipher)) { $tagTypes[] = new NullType(); continue; } diff --git a/src/Type/Php/OpensslCipherFunctionsReturnTypeExtension.php b/src/Type/Php/OpensslCipherFunctionsReturnTypeExtension.php index 813ba532d2..c59e80ac87 100644 --- a/src/Type/Php/OpensslCipherFunctionsReturnTypeExtension.php +++ b/src/Type/Php/OpensslCipherFunctionsReturnTypeExtension.php @@ -16,20 +16,16 @@ use function array_map; use function array_unique; use function count; -use function function_exists; use function in_array; -use function is_null; -use function openssl_get_cipher_methods; -use function strtoupper; #[AutowiredService] final class OpensslCipherFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - /** @var string[]|null */ - private ?array $supportedAlgorithms = null; - - public function __construct(private PhpVersion $phpVersion) + public function __construct( + private PhpVersion $phpVersion, + private OpenSslCipherMethodsProvider $cipherMethodsProvider, + ) { } @@ -49,7 +45,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, } $strings = $scope->getType($functionCall->getArgs()[0]->value)->getConstantStrings(); - $results = array_unique(array_map(fn (ConstantStringType $algorithm): bool => $this->isSupportedAlgorithm($algorithm->getValue()), $strings)); + $results = array_unique(array_map(fn (ConstantStringType $algorithm): bool => $this->cipherMethodsProvider->isSupportedCipherMethod($algorithm->getValue()), $strings)); if (count($results) !== 1) { return null; @@ -66,25 +62,4 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, : new ConstantBooleanType(false); } - private function isSupportedAlgorithm(string $algorithm): bool - { - return in_array(strtoupper($algorithm), $this->getSupportedAlgorithms(), true); - } - - /** @return string[] */ - private function getSupportedAlgorithms(): array - { - if (!is_null($this->supportedAlgorithms)) { - return $this->supportedAlgorithms; - } - - $supportedAlgorithms = []; - if (function_exists('openssl_get_cipher_methods')) { - $supportedAlgorithms = openssl_get_cipher_methods(true); - } - $this->supportedAlgorithms = array_map('strtoupper', $supportedAlgorithms); - - return $this->supportedAlgorithms; - } - } diff --git a/tests/PHPStan/Analyser/nsrt/bug-13692.php b/tests/PHPStan/Analyser/nsrt/bug-13692.php new file mode 100644 index 0000000000..4ac4299082 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13692.php @@ -0,0 +1,19 @@ += 8.0 + +namespace Bug13692; + +use function PHPStan\Testing\assertType; + +class Foo +{ + + public function doFoo(): void + { + // Known supported cipher should resolve to int + assertType('int', openssl_cipher_iv_length('aes-128-cbc')); + + // Unknown cipher should resolve to false + assertType('false', openssl_cipher_iv_length('unknown')); + } + +}