diff --git a/src/Php/PhpVersion.php b/src/Php/PhpVersion.php index 9657ab9818..6a555dff73 100644 --- a/src/Php/PhpVersion.php +++ b/src/Php/PhpVersion.php @@ -394,6 +394,19 @@ public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool return $this->versionId < 80000; } + /** + * whether curl handles are represented as 'resource' or CurlShareHandle + */ + public function supportsCurlShareHandle(): bool + { + return $this->versionId >= 80000; + } + + public function supportsCurlSharePersistentHandle(): bool + { + return $this->versionId >= 80500; + } + public function highlightStringDoesNotReturnFalse(): bool { return $this->versionId >= 80400; diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index accede38af..ba5ed852cb 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -58,6 +58,7 @@ use function sprintf; use const ARRAY_FILTER_USE_BOTH; use const ARRAY_FILTER_USE_KEY; +use const CURLOPT_SHARE; use const CURLOPT_SSL_VERIFYHOST; /** @@ -1212,6 +1213,21 @@ private static function getCurlOptValueType(int $curlOpt): ?Type } } + if ($curlOpt === CURLOPT_SHARE) { + $phpversion = PhpVersionStaticAccessor::getInstance(); + + if ($phpversion->supportsCurlShareHandle()) { + $shareType = new ObjectType('CurlShareHandle'); + } else { + $shareType = new ResourceType(); + } + if ($phpversion->supportsCurlSharePersistentHandle()) { + $shareType = TypeCombinator::union($shareType, new ObjectType('CurlSharePersistentHandle')); + } + + return $shareType; + } + // unknown constant return null; } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 9ea34fcfeb..53ddb13625 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1401,6 +1401,25 @@ public function testCurlSetOpt(): void ]); } + public function testCurlSetOptInvalidShare(): void + { + if (PHP_VERSION_ID < 80000) { + $errors = [ + ['Parameter #3 $value of function curl_setopt expects resource, string given.', 8], + ]; + } elseif (PHP_VERSION_ID < 80500) { + $errors = [ + ['Parameter #3 $value of function curl_setopt expects CurlShareHandle, string given.', 8], + ]; + } else { + $errors = [ + ['Parameter #3 $value of function curl_setopt expects CurlShareHandle|CurlSharePersistentHandle, string given.', 8], + ]; + } + + $this->analyse([__DIR__ . '/data/curl_setopt_share.php'], $errors); + } + #[RequiresPhp('>= 8.1')] public function testCurlSetOptArray(): void { diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index 7cff79c7ac..9cbbc3a46a 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -95,4 +95,23 @@ public function unionType() { curl_setopt($curl, $var, $value); } + + public function curlShare() { + $curl = curl_init(); + + $share = curl_share_init(); + curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); + curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); + curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); + curl_setopt($curl, CURLOPT_SHARE, $share); + + if (function_exists('curl_share_init_persistent')) { + $share = curl_share_init_persistent([ + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_SSL_SESSION, + ]); + curl_setopt($curl, CURLOPT_SHARE, $share); + } + } } diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt_share.php b/tests/PHPStan/Rules/Functions/data/curl_setopt_share.php new file mode 100644 index 0000000000..e947053f77 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt_share.php @@ -0,0 +1,9 @@ +