diff --git a/src/Rules/Functions/PrintfHelper.php b/src/Rules/Functions/PrintfHelper.php index 411972885d..4c72899fe8 100644 --- a/src/Rules/Functions/PrintfHelper.php +++ b/src/Rules/Functions/PrintfHelper.php @@ -2,15 +2,21 @@ namespace PHPStan\Rules\Functions; +use ErrorException; use Nette\Utils\Strings; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; +use Throwable; use function array_filter; use function array_keys; +use function array_slice; use function count; use function in_array; use function max; +use function restore_error_handler; +use function set_error_handler; use function sprintf; +use function sscanf; use function strlen; use const PREG_SET_ORDER; @@ -37,7 +43,30 @@ public function getPrintfPlaceholders(string $format): ?array public function getScanfPlaceholdersCount(string $format): ?int { - return $this->getPlaceholdersCount('(?[cdDeEfinosuxX%s]|\[[^\]]+\])', $format, true); + try { + // if we would *know* that simple downgrader can handle + // Throwable -> ErrorException then 7.4 required error + // handler could be injected this way into the try/catch + // & appending finally as an extension. *dreaming* + set_error_handler( + static function ($s, $m, ...$vv) { + $vv = array_slice($vv, 0, 2); + throw new ErrorException($m, 0, $s, ...$vv); + }, + ); + $nFormat = '%*n' . $format; + $result = sscanf('', $nFormat); + } catch (Throwable) { + return null; + } finally { + restore_error_handler(); + } + // one day phpstan may report here that $result can never be null for sscanf('', $nFormat), + // which is actually correct: https://3v4l.org/rO7Ni + if ($result === null) { + return null; + } + return count($result); } /** diff --git a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php index 1e3ab9ddd7..d31344d8cd 100644 --- a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php @@ -84,6 +84,14 @@ public function testFile(): void 'Call to sprintf contains 2 placeholders, 1 value given.', 29, ], + [ + 'Call to sscanf contains an invalid placeholder.', + 38, + ], + [ + 'Call to fscanf contains an invalid placeholder.', + 39, + ], [ 'Call to sprintf contains 2 placeholders, 1 value given.', 45, @@ -147,4 +155,9 @@ public function testBug10260(): void $this->analyse([__DIR__ . '/data/bug-10260.php'], []); } + public function testBug14567(): void + { + $this->analyse([__DIR__ . '/data/bug-14567.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-14567.php b/tests/PHPStan/Rules/Functions/data/bug-14567.php new file mode 100644 index 0000000000..127d3c577f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-14567.php @@ -0,0 +1,18 @@ +