Skip to content

Commit cd30cea

Browse files
committed
Fix counting *scanf() format string placeholders
Update PrintfHelper.php to make public function getScanfPlaceholdersCount(string $format): ?int` return the sscanf() vetted number of placeholders that return/assign conversions. Addresses long-standing regressions reported originally in [phpstan-10260]. References: - [phpstan-10260] - phpstan/phpstan#10260 (comment) - [phpstan-src-5591] - [phpstan-14567] - https://3v4l.org/WR85Q [phpstan-10260]: phpstan/phpstan#10260 [phpstan-src-5591]: #5591 [phpstan-14567]: phpstan/phpstan#14567
1 parent 99d6996 commit cd30cea

1 file changed

Lines changed: 20 additions & 10 deletions

File tree

src/Rules/Functions/PrintfHelper.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
use Nette\Utils\Strings;
66
use PHPStan\DependencyInjection\AutowiredService;
77
use PHPStan\Php\PhpVersion;
8+
use ValueError;
89
use function array_filter;
910
use function array_keys;
1011
use function count;
1112
use function in_array;
1213
use function max;
1314
use function sprintf;
15+
use function sscanf;
1416
use function strlen;
1517
use const PREG_SET_ORDER;
1618

@@ -26,24 +28,36 @@ public function __construct(private PhpVersion $phpVersion)
2628

2729
public function getPrintfPlaceholdersCount(string $format): ?int
2830
{
29-
return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format, false);
31+
return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format);
3032
}
3133

3234
/** @phpstan-return array<int, non-empty-list<PrintfPlaceholder>> parameter index => placeholders */
3335
public function getPrintfPlaceholders(string $format): ?array
3436
{
35-
return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format, false);
37+
return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format);
3638
}
3739

3840
public function getScanfPlaceholdersCount(string $format): ?int
3941
{
40-
return $this->getPlaceholdersCount('(?<specifier>[cdDeEfinosuxX%s]|\[[^\]]+\])', $format, true);
42+
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
43+
try {
44+
$result = sscanf('', '%*n' . $format);
45+
} catch (ValueError) {
46+
return null;
47+
}
48+
} else {
49+
$result = @sscanf('', '%*n' . $format);
50+
}
51+
if ($result === null) {
52+
return null;
53+
}
54+
return count($result);
4155
}
4256

4357
/**
4458
* @phpstan-return array<int, non-empty-list<PrintfPlaceholder>>|null parameter index => placeholders
4559
*/
46-
private function parsePlaceholders(string $specifiersPattern, string $format, bool $isScanf): ?array
60+
private function parsePlaceholders(string $specifiersPattern, string $format): ?array
4761
{
4862
$addSpecifier = '';
4963
if ($this->phpVersion->supportsHhPrintfSpecifier()) {
@@ -72,10 +86,6 @@ private function parsePlaceholders(string $specifiersPattern, string $format, bo
7286
$showValueSuffix = false;
7387

7488
if (isset($placeholder['width']) && $placeholder['width'] !== '') {
75-
if ($isScanf) {
76-
// In scanf, * means assignment suppression - skip this placeholder entirely
77-
continue;
78-
}
7989
$parsedPlaceholders[] = new PrintfPlaceholder(
8090
sprintf('"%s" (width)', $placeholder[0]),
8191
$parameterIdx++,
@@ -136,9 +146,9 @@ private function getAcceptingTypeBySpecifier(string $specifier): string
136146
return 'mixed';
137147
}
138148

139-
private function getPlaceholdersCount(string $specifiersPattern, string $format, bool $isScanf): ?int
149+
private function getPlaceholdersCount(string $specifiersPattern, string $format): ?int
140150
{
141-
$placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format, $isScanf);
151+
$placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format);
142152
if ($placeholdersMap === null) {
143153
return null;
144154
}

0 commit comments

Comments
 (0)