diff --git a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php index bf91435e62..34f1856c6f 100644 --- a/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php +++ b/src/Type/Php/Base64DecodeDynamicFunctionReturnTypeExtension.php @@ -6,13 +6,15 @@ use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\FunctionReflection; +use PHPStan\TrinaryLogic; use PHPStan\Type\BenevolentUnionType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\MixedType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\UnionType; +use function base64_decode; #[AutowiredService] final class Base64DecodeDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -29,32 +31,41 @@ public function getTypeFromFunctionCall( Scope $scope, ): Type { - if (!isset($functionCall->getArgs()[1])) { + if (!isset($functionCall->getArgs()[0])) { return new StringType(); } - $argType = $scope->getType($functionCall->getArgs()[1]->value); - - if ($argType instanceof MixedType) { - return new BenevolentUnionType([new StringType(), new ConstantBooleanType(false)]); + $stringArgNode = $functionCall->getArgs()[0]->value; + $constantStrings = $scope->getType($stringArgNode)->getConstantStrings(); + if ($constantStrings !== []) { + $isValidBase64 = TrinaryLogic::lazyExtremeIdentity( + $constantStrings, + static function (ConstantStringType $constantString): TrinaryLogic { + $isValid = base64_decode($constantString->getValue(), true) !== false; + return TrinaryLogic::createFromBoolean($isValid); + }, + ); + } else { + $isValidBase64 = TrinaryLogic::createMaybe(); } - $isTrueType = $argType->isTrue(); - $isFalseType = $argType->isFalse(); - $compareTypes = $isTrueType->compareTo($isFalseType); - if ($compareTypes === $isTrueType) { - return new UnionType([new StringType(), new ConstantBooleanType(false)]); + if (isset($functionCall->getArgs()[1])) { + $strictArgNode = $functionCall->getArgs()[1]->value; + $isStrict = $scope->getType($strictArgNode)->toBoolean()->toTrinaryLogic(); + } else { + $isStrict = TrinaryLogic::createNo(); } - if ($compareTypes === $isFalseType) { + + if ($isStrict->no() || $isValidBase64->yes()) { return new StringType(); } - - // second argument could be interpreted as true - if (!$isTrueType->no()) { - return new UnionType([new StringType(), new ConstantBooleanType(false)]); + if ($isStrict->yes() && $isValidBase64->no()) { + return new ConstantBooleanType(false); } - - return new StringType(); + if ($isStrict->maybe() && $isValidBase64->maybe()) { + return new BenevolentUnionType([new StringType(), new ConstantBooleanType(false)]); + } + return new UnionType([new StringType(), new ConstantBooleanType(false)]); } } diff --git a/tests/PHPStan/Analyser/data/functions.php b/tests/PHPStan/Analyser/data/functions.php index 01b6d33f3d..07b83f827f 100644 --- a/tests/PHPStan/Analyser/data/functions.php +++ b/tests/PHPStan/Analyser/data/functions.php @@ -78,11 +78,12 @@ $fileObject = new \SplFileObject(__FILE__); $fileObjectStat = $fileObject->fstat(); -$base64DecodeWithoutStrict = base64_decode(''); -$base64DecodeWithStrictDisabled = base64_decode('', false); -$base64DecodeWithStrictEnabled = base64_decode('', true); -$base64DecodeDefault = base64_decode('', null); -$base64DecodeBenevolent = base64_decode('', $undefined); +$string = (string)time(); +$base64DecodeWithoutStrict = base64_decode($string); +$base64DecodeWithStrictDisabled = base64_decode($string, false); +$base64DecodeWithStrictEnabled = base64_decode($string, true); +$base64DecodeDefault = base64_decode($string, null); +$base64DecodeBenevolent = base64_decode($string, $undefined); //str_word_count diff --git a/tests/PHPStan/Analyser/nsrt/base64_decode.php b/tests/PHPStan/Analyser/nsrt/base64_decode.php index 34de145d9a..d19d03524b 100644 --- a/tests/PHPStan/Analyser/nsrt/base64_decode.php +++ b/tests/PHPStan/Analyser/nsrt/base64_decode.php @@ -11,11 +11,27 @@ public function nonStrictMode(string $string): void { assertType('string', base64_decode($string)); assertType('string', base64_decode($string, false)); + assertType('string', base64_decode($string, 0)); + assertType('string', base64_decode('UEhQU3Rhbg==', false)); + assertType('string', base64_decode('!!!', false)); } public function strictMode(string $string): void { assertType('string|false', base64_decode($string, true)); + assertType('string|false', base64_decode($string, 1)); + assertType('string', base64_decode(mt_rand() ? 'UEhQU3Rhbg==' : 'cm9ja3Mh', true)); + assertType('string|false', base64_decode(mt_rand() ? 'UEhQU3Rhbg==' : '!!!', true)); + assertType('false', base64_decode(mt_rand() ? '!' : '!!!', true)); + } + + public function mixedMode(string $string): void + { + assertType('(string|false)', base64_decode($string, unknown())); + assertType('(string|false)', base64_decode($string, mt_rand(0, 1) === 1)); + assertType('(string|false)', base64_decode($string, mt_rand(0, 1))); + assertType('string', base64_decode('UEhQU3Rhbg==', mt_rand(0, 1) === 1)); + assertType('string|false', base64_decode('!!!', mt_rand(0, 1) === 1)); } }