From b61d340c6aac3364e0f8438638cf6bd4009f2e0c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 4 Dec 2025 07:52:14 +0100 Subject: [PATCH 1/3] Narrow CURLOPT_SHARE accepting type --- src/Reflection/ParametersAcceptorSelector.php | 9 +++++++++ .../Functions/CallToFunctionParametersRuleTest.php | 4 ++++ tests/PHPStan/Rules/Functions/data/curl_setopt.php | 13 +++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index accede38af..0695445c09 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,14 @@ private static function getCurlOptValueType(int $curlOpt): ?Type } } + if ($curlOpt === CURLOPT_SHARE) { + return new UnionType([ + new ResourceType(), // PHP 7.x + new ObjectType('CurlShareHandle'), // since PHP 8.0 + new ObjectType('CurlSharePersistentHandle'), // since PHP 8.5 + ]); + } + // unknown constant return null; } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 9ea34fcfeb..41ecf96b10 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1398,6 +1398,10 @@ public function testCurlSetOpt(): void 'Parameter #3 $value of function curl_setopt expects bool|int, int|string given.', 96, ], + [ + 'Parameter #3 $value of function curl_setopt expects CurlShareHandle|CurlSharePersistentHandle|resource, string given.', + 101, + ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index 7cff79c7ac..32f8c2a73d 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -95,4 +95,17 @@ public function unionType() { curl_setopt($curl, $var, $value); } + + public function curlShare() { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_SHARE, 'this is wrong'); + + $share = curl_share_init(); + curl_setopt($curl, CURLOPT_SHARE, $share); + + if (function_exists('curl_share_init_persistent')) { + $share = curl_share_init_persistent(); + curl_setopt($curl, CURLOPT_SHARE, $share); + } + } } From 0ffbec5ac336cf05483c7295093b475496a90d87 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 4 Dec 2025 08:01:50 +0100 Subject: [PATCH 2/3] improve test --- tests/PHPStan/Rules/Functions/data/curl_setopt.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index 32f8c2a73d..f80520aef3 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -101,10 +101,17 @@ public function curlShare() { curl_setopt($curl, CURLOPT_SHARE, 'this is wrong'); $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(); + $share = curl_share_init_persistent([ + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_SSL_SESSION, + ]); curl_setopt($curl, CURLOPT_SHARE, $share); } } From e5988687b01fa46868f74d7475eb1228feeb96b3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 5 Dec 2025 09:52:24 +0100 Subject: [PATCH 3/3] feature detect --- src/Php/PhpVersion.php | 13 +++++++++++ src/Reflection/ParametersAcceptorSelector.php | 17 ++++++++++---- .../CallToFunctionParametersRuleTest.php | 23 +++++++++++++++---- .../Rules/Functions/data/curl_setopt.php | 1 - .../Functions/data/curl_setopt_share.php | 9 ++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Rules/Functions/data/curl_setopt_share.php 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 0695445c09..ba5ed852cb 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -1214,11 +1214,18 @@ private static function getCurlOptValueType(int $curlOpt): ?Type } if ($curlOpt === CURLOPT_SHARE) { - return new UnionType([ - new ResourceType(), // PHP 7.x - new ObjectType('CurlShareHandle'), // since PHP 8.0 - new ObjectType('CurlSharePersistentHandle'), // since PHP 8.5 - ]); + $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 diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 41ecf96b10..53ddb13625 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1398,13 +1398,28 @@ public function testCurlSetOpt(): void 'Parameter #3 $value of function curl_setopt expects bool|int, int|string given.', 96, ], - [ - 'Parameter #3 $value of function curl_setopt expects CurlShareHandle|CurlSharePersistentHandle|resource, string given.', - 101, - ], ]); } + 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 f80520aef3..9cbbc3a46a 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -98,7 +98,6 @@ public function unionType() { public function curlShare() { $curl = curl_init(); - curl_setopt($curl, CURLOPT_SHARE, 'this is wrong'); $share = curl_share_init(); curl_share_setopt($share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); 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 @@ +