From ba5bdb936dd933bcd890b8aab8d64a12befe0172 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Nov 2023 21:31:44 +0100 Subject: [PATCH 1/6] Non-numeric strings in pow() lead to error --- src/Type/ExponentiateHelper.php | 13 ++++++++--- tests/PHPStan/Analyser/nsrt/pow.php | 34 +++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 4e62ed9a5d..06f2e9c10c 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Type; +use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use function is_float; @@ -24,14 +25,20 @@ public static function exponentiate(Type $base, Type $exponent): Type return new NeverType(); } - $allowedExponentTypes = new UnionType([ + $allowedOperandTypes = new UnionType([ new IntegerType(), new FloatType(), - new StringType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), new BooleanType(), new NullType(), ]); - if (!$allowedExponentTypes->isSuperTypeOf($exponent)->yes()) { + if (!$allowedOperandTypes->isSuperTypeOf($exponent)->yes()) { + return new ErrorType(); + } + if (!$allowedOperandTypes->isSuperTypeOf($base)->yes()) { return new ErrorType(); } diff --git a/tests/PHPStan/Analyser/nsrt/pow.php b/tests/PHPStan/Analyser/nsrt/pow.php index 82f03b1bae..e077cbc223 100644 --- a/tests/PHPStan/Analyser/nsrt/pow.php +++ b/tests/PHPStan/Analyser/nsrt/pow.php @@ -112,8 +112,8 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('int', pow($intA, 1)); assertType('int', $intA ** '1'); - assertType('(float|int)', pow($intA, $s)); - assertType('(float|int)', $intA ** $s); + assertType('*ERROR*', pow($intA, $s)); + assertType('*ERROR*', $intA ** $s); assertType('(float|int)', pow($intA, $bool)); // could be int assertType('(float|int)', $intA ** $bool); // could be int @@ -161,11 +161,11 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('NAN', pow(-1,5.5)); - assertType('1', pow($s, 0)); - assertType('1', $s ** '0'); - assertType('1', $s ** false); - assertType('(float|int)', pow($s, 1)); - assertType('(float|int)', $s ** '1'); + assertType('*ERROR*', pow($s, 0)); + assertType('*ERROR*', $s ** '0'); + assertType('*ERROR*', $s ** false); + assertType('*ERROR*', pow($s, 1)); + assertType('*ERROR*', $s ** '1'); assertType('*ERROR*', $s ** $arr); assertType('*ERROR*', $s ** []); @@ -177,3 +177,23 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('*ERROR*', $bool ** $arr); assertType('*ERROR*', $bool ** []); }; + +function invalidConstantOperands(): void { + assertType('*ERROR*', 'a' ** 1); + assertType('*ERROR*', 1 ** 'a'); + + assertType('*ERROR*', [] ** 1); + assertType('*ERROR*', 1 ** []); + + assertType('*ERROR*', (new \stdClass()) ** 1); + assertType('*ERROR*', 1 ** (new \stdClass())); +} + +function validConstantOperands(): void { + assertType('1', '1' ** 1); + assertType('1', 1 ** '1'); + assertType('1', '1' ** '1'); + + assertType('1', true ** 1); + assertType('1', 1 ** false); +} From 4d807f2d832325bfb05f060b4a39e0b9a03bdfea Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 29 Nov 2023 22:17:10 +0100 Subject: [PATCH 2/6] fix --- src/Type/ExponentiateHelper.php | 63 +++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 06f2e9c10c..7d8c3098df 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -7,6 +7,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use function is_float; use function is_int; +use function pow; final class ExponentiateHelper { @@ -25,28 +26,9 @@ public static function exponentiate(Type $base, Type $exponent): Type return new NeverType(); } - $allowedOperandTypes = new UnionType([ - new IntegerType(), - new FloatType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]), - new BooleanType(), - new NullType(), - ]); - if (!$allowedOperandTypes->isSuperTypeOf($exponent)->yes()) { - return new ErrorType(); - } - if (!$allowedOperandTypes->isSuperTypeOf($base)->yes()) { - return new ErrorType(); - } - - if ($base instanceof ConstantScalarType) { - $result = self::exponentiateConstantScalar($base, $exponent); - if ($result !== null) { - return $result; - } + $result = self::exponentiateConstantScalar($base, $exponent); + if ($result !== null) { + return $result; } // exponentiation of a float, stays a float @@ -84,16 +66,37 @@ public static function exponentiate(Type $base, Type $exponent): Type ]); } - private static function exponentiateConstantScalar(ConstantScalarType $base, Type $exponent): ?Type + private static function exponentiateConstantScalar(Type $base, Type $exponent): ?Type { + $allowedOperandTypes = new UnionType([ + new IntegerType(), + new FloatType(), + new IntersectionType([ + new StringType(), + new AccessoryNumericStringType(), + ]), + new BooleanType(), + new NullType(), + ]); + if (!$allowedOperandTypes->isSuperTypeOf($exponent)->yes()) { + return new ErrorType(); + } + if (!$allowedOperandTypes->isSuperTypeOf($base)->yes()) { + return new ErrorType(); + } + + if (!$base instanceof ConstantScalarType) { + return null; + } + if ($exponent instanceof IntegerRangeType) { $min = null; $max = null; if ($exponent->getMin() !== null) { - $min = $base->getValue() ** $exponent->getMin(); + $min = self::pow($base->getValue(), $exponent->getMin()); } if ($exponent->getMax() !== null) { - $max = $base->getValue() ** $exponent->getMax(); + $max = self::pow($base->getValue(), $exponent->getMax()); } if (!is_float($min) && !is_float($max)) { @@ -102,7 +105,7 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ } if ($exponent instanceof ConstantScalarType) { - $result = $base->getValue() ** $exponent->getValue(); + $result = self::pow($base->getValue(), $exponent->getValue()); if (is_int($result)) { return new ConstantIntegerType($result); } @@ -112,4 +115,12 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ return null; } + /** + * @return float|int + */ + private static function pow(mixed $base, mixed $exp) + { + return pow($base, $exp); + } + } From dc0180e0d57fea19a7933204490db871d02bac86 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 17 Jul 2024 13:22:23 +0200 Subject: [PATCH 3/6] revert --- src/Type/ExponentiateHelper.php | 43 +++++++++++++-------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 7d8c3098df..2706d525a3 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -2,7 +2,6 @@ namespace PHPStan\Type; -use PHPStan\Type\Accessory\AccessoryNumericStringType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use function is_float; @@ -26,9 +25,22 @@ public static function exponentiate(Type $base, Type $exponent): Type return new NeverType(); } - $result = self::exponentiateConstantScalar($base, $exponent); - if ($result !== null) { - return $result; + $allowedExponentTypes = new UnionType([ + new IntegerType(), + new FloatType(), + new StringType(), + new BooleanType(), + new NullType(), + ]); + if (!$allowedExponentTypes->isSuperTypeOf($exponent)->yes()) { + return new ErrorType(); + } + + if ($base instanceof ConstantScalarType) { + $result = self::exponentiateConstantScalar($base, $exponent); + if ($result !== null) { + return $result; + } } // exponentiation of a float, stays a float @@ -66,29 +78,8 @@ public static function exponentiate(Type $base, Type $exponent): Type ]); } - private static function exponentiateConstantScalar(Type $base, Type $exponent): ?Type + private static function exponentiateConstantScalar(ConstantScalarType $base, Type $exponent): ?Type { - $allowedOperandTypes = new UnionType([ - new IntegerType(), - new FloatType(), - new IntersectionType([ - new StringType(), - new AccessoryNumericStringType(), - ]), - new BooleanType(), - new NullType(), - ]); - if (!$allowedOperandTypes->isSuperTypeOf($exponent)->yes()) { - return new ErrorType(); - } - if (!$allowedOperandTypes->isSuperTypeOf($base)->yes()) { - return new ErrorType(); - } - - if (!$base instanceof ConstantScalarType) { - return null; - } - if ($exponent instanceof IntegerRangeType) { $min = null; $max = null; From bc8af0d7d5ec4aadfc0d62e09041d855f1aa2bf8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 17 Jul 2024 13:28:55 +0200 Subject: [PATCH 4/6] Update ExponentiateHelper.php --- src/Type/ExponentiateHelper.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 2706d525a3..9b2c3a6581 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -4,6 +4,7 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; +use Throwable; use function is_float; use function is_int; use function pow; @@ -85,9 +86,15 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ $max = null; if ($exponent->getMin() !== null) { $min = self::pow($base->getValue(), $exponent->getMin()); + if ($min === null) { + return null; + } } if ($exponent->getMax() !== null) { $max = self::pow($base->getValue(), $exponent->getMax()); + if ($max === null) { + return null; + } } if (!is_float($min) && !is_float($max)) { @@ -97,6 +104,11 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ if ($exponent instanceof ConstantScalarType) { $result = self::pow($base->getValue(), $exponent->getValue()); + + if ($result === null) { + return null; + } + if (is_int($result)) { return new ConstantIntegerType($result); } @@ -106,12 +118,13 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ return null; } - /** - * @return float|int - */ - private static function pow(mixed $base, mixed $exp) + private static function pow(mixed $base, mixed $exp): float|int|null { - return pow($base, $exp); + try { + return pow($base, $exp); + } catch (Throwable) { + return null; + } } } From 165bebd741ce941bc446f3aeca16d2280d99e5f7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 17 Jul 2024 13:46:00 +0200 Subject: [PATCH 5/6] fix --- src/Type/ExponentiateHelper.php | 7 +++---- tests/PHPStan/Analyser/nsrt/pow.php | 14 +++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 9b2c3a6581..22ad0c15c2 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -87,13 +87,13 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ if ($exponent->getMin() !== null) { $min = self::pow($base->getValue(), $exponent->getMin()); if ($min === null) { - return null; + return new ErrorType(); } } if ($exponent->getMax() !== null) { $max = self::pow($base->getValue(), $exponent->getMax()); if ($max === null) { - return null; + return new ErrorType(); } } @@ -104,9 +104,8 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ if ($exponent instanceof ConstantScalarType) { $result = self::pow($base->getValue(), $exponent->getValue()); - if ($result === null) { - return null; + return new ErrorType(); } if (is_int($result)) { diff --git a/tests/PHPStan/Analyser/nsrt/pow.php b/tests/PHPStan/Analyser/nsrt/pow.php index e077cbc223..328de3f172 100644 --- a/tests/PHPStan/Analyser/nsrt/pow.php +++ b/tests/PHPStan/Analyser/nsrt/pow.php @@ -112,8 +112,8 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('int', pow($intA, 1)); assertType('int', $intA ** '1'); - assertType('*ERROR*', pow($intA, $s)); - assertType('*ERROR*', $intA ** $s); + assertType('(float|int)', pow($intA, $s)); + assertType('(float|int)', $intA ** $s); assertType('(float|int)', pow($intA, $bool)); // could be int assertType('(float|int)', $intA ** $bool); // could be int @@ -161,11 +161,11 @@ function doFoo(int $intA, int $intB, string $s, bool $bool, $numericS, float $fl assertType('NAN', pow(-1,5.5)); - assertType('*ERROR*', pow($s, 0)); - assertType('*ERROR*', $s ** '0'); - assertType('*ERROR*', $s ** false); - assertType('*ERROR*', pow($s, 1)); - assertType('*ERROR*', $s ** '1'); + assertType('1', pow($s, 0)); + assertType('1', $s ** '0'); + assertType('1', $s ** false); + assertType('(float|int)', pow($s, 1)); + assertType('(float|int)', $s ** '1'); assertType('*ERROR*', $s ** $arr); assertType('*ERROR*', $s ** []); From 1bb97bd75087cadcb2f1453d20d5ced3f71ae866 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 17 Jul 2024 13:59:55 +0200 Subject: [PATCH 6/6] Update ExponentiateHelper.php --- src/Type/ExponentiateHelper.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Type/ExponentiateHelper.php b/src/Type/ExponentiateHelper.php index 22ad0c15c2..10da4dd484 100644 --- a/src/Type/ExponentiateHelper.php +++ b/src/Type/ExponentiateHelper.php @@ -4,9 +4,10 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; -use Throwable; use function is_float; use function is_int; +use function is_numeric; +use function is_string; use function pow; final class ExponentiateHelper @@ -119,11 +120,13 @@ private static function exponentiateConstantScalar(ConstantScalarType $base, Typ private static function pow(mixed $base, mixed $exp): float|int|null { - try { - return pow($base, $exp); - } catch (Throwable) { + if (is_string($base) && !is_numeric($base)) { return null; } + if (is_string($exp) && !is_numeric($exp)) { + return null; + } + return pow($base, $exp); } }