From c214b520911db2d1b64aadfe9e8daaf5ba7ec1ca Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 14:36:58 +0200 Subject: [PATCH 1/2] Support union types in curl_setopt validation --- src/Reflection/ParametersAcceptorSelector.php | 71 ++++++++++++------- .../CallToFunctionParametersRuleTest.php | 4 ++ .../Rules/Functions/data/curl_setopt.php | 14 ++++ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 6b47480d73..47fddd507e 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -50,6 +50,7 @@ use function constant; use function count; use function defined; +use function is_int; use function is_string; use function sprintf; use const ARRAY_FILTER_USE_BOTH; @@ -126,33 +127,51 @@ public static function selectFromArgs( if (count($args) >= 3 && (bool) $args[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) { $optType = $scope->getType($args[1]->value); - if ($optType instanceof ConstantIntegerType) { - $optValueType = self::getCurlOptValueType($optType->getValue()); - - if ($optValueType !== null) { - $acceptor = $parametersAcceptors[0]; - $parameters = $acceptor->getParameters(); - - $parameters[2] = new NativeParameterReflection( - $parameters[2]->getName(), - $parameters[2]->isOptional(), - $optValueType, - $parameters[2]->passedByReference(), - $parameters[2]->isVariadic(), - $parameters[2]->getDefaultValue(), - ); - - $parametersAcceptors = [ - new FunctionVariant( - $acceptor->getTemplateTypeMap(), - $acceptor->getResolvedTemplateTypeMap(), - array_values($parameters), - $acceptor->isVariadic(), - $acceptor->getReturnType(), - $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), - ), - ]; + + $optValueType = null; + foreach ($optType->getConstantScalarValues() as $scalarValue) { + if (!is_int($scalarValue)) { + $optValueType = null; + break; + } + + $valueType = self::getCurlOptValueType($scalarValue); + if ($valueType === null) { + $optValueType = null; + break; + } + + if ($optValueType === null) { + $optValueType = $valueType; + continue; } + + $optValueType = TypeCombinator::union($optValueType, $valueType); + } + + if ($optValueType !== null) { + $acceptor = $parametersAcceptors[0]; + $parameters = $acceptor->getParameters(); + + $parameters[2] = new NativeParameterReflection( + $parameters[2]->getName(), + $parameters[2]->isOptional(), + $optValueType, + $parameters[2]->passedByReference(), + $parameters[2]->isVariadic(), + $parameters[2]->getDefaultValue(), + ); + + $parametersAcceptors = [ + new FunctionVariant( + $acceptor->getTemplateTypeMap(), + $acceptor->getResolvedTemplateTypeMap(), + array_values($parameters), + $acceptor->isVariadic(), + $acceptor->getReturnType(), + $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), + ), + ]; } } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c3dc4df606..f49cc7f15c 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1375,6 +1375,10 @@ public function testCurlSetOpt(): void 'Parameter #3 $value of function curl_setopt expects array, array given.', 77, ], + [ + 'Parameter #3 $value of function curl_setopt expects bool|int, int|string given.', + 96, + ], ]); } diff --git a/tests/PHPStan/Rules/Functions/data/curl_setopt.php b/tests/PHPStan/Rules/Functions/data/curl_setopt.php index bc5b8f6cea..7cff79c7ac 100644 --- a/tests/PHPStan/Rules/Functions/data/curl_setopt.php +++ b/tests/PHPStan/Rules/Functions/data/curl_setopt.php @@ -81,4 +81,18 @@ public function bug9263() { ]; curl_setopt($curl, CURLOPT_HTTPHEADER, $header_list); } + + public function unionType() { + $curl = curl_init(); + + if (rand(0,1)) { + $var = CURLOPT_AUTOREFERER; + $value = 'yes'; // invalid, should be bool + } else { + $var = CURLOPT_TIMEOUT; + $value = 1; + } + + curl_setopt($curl, $var, $value); + } } From 8f64189f270f321959a2aba3f2d716f9d1bb1d6e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Oct 2025 18:53:56 +0200 Subject: [PATCH 2/2] Update ParametersAcceptorSelector.php --- src/Reflection/ParametersAcceptorSelector.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Reflection/ParametersAcceptorSelector.php b/src/Reflection/ParametersAcceptorSelector.php index 47fddd507e..bc60c25c2d 100644 --- a/src/Reflection/ParametersAcceptorSelector.php +++ b/src/Reflection/ParametersAcceptorSelector.php @@ -128,35 +128,30 @@ public static function selectFromArgs( if (count($args) >= 3 && (bool) $args[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) { $optType = $scope->getType($args[1]->value); - $optValueType = null; + $valueTypes = []; foreach ($optType->getConstantScalarValues() as $scalarValue) { if (!is_int($scalarValue)) { - $optValueType = null; + $valueTypes = []; break; } $valueType = self::getCurlOptValueType($scalarValue); if ($valueType === null) { - $optValueType = null; + $valueTypes = []; break; } - if ($optValueType === null) { - $optValueType = $valueType; - continue; - } - - $optValueType = TypeCombinator::union($optValueType, $valueType); + $valueTypes[] = $valueType; } - if ($optValueType !== null) { + if (count($valueTypes) !== 0) { $acceptor = $parametersAcceptors[0]; $parameters = $acceptor->getParameters(); $parameters[2] = new NativeParameterReflection( $parameters[2]->getName(), $parameters[2]->isOptional(), - $optValueType, + TypeCombinator::union(...$valueTypes), $parameters[2]->passedByReference(), $parameters[2]->isVariadic(), $parameters[2]->getDefaultValue(),