Skip to content

Commit 145b8bf

Browse files
committed
Update PrintfHelper.php
Make `public function getScanfPlaceholdersCount(string $format): ?int` returning the sscanf() vetted number of placeholders that give/return/assign conversions. refs: - #5591 - phpstan/phpstan#14567
1 parent 99d6996 commit 145b8bf

1 file changed

Lines changed: 30 additions & 10 deletions

File tree

src/Rules/Functions/PrintfHelper.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
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;
14+
use function restore_error_handler;
15+
use function set_error_handler;
1316
use function sprintf;
17+
use function sscanf;
1418
use function strlen;
1519
use const PREG_SET_ORDER;
1620

@@ -26,24 +30,44 @@ public function __construct(private PhpVersion $phpVersion)
2630

2731
public function getPrintfPlaceholdersCount(string $format): ?int
2832
{
29-
return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format, false);
33+
return $this->getPlaceholdersCount(self::PRINTF_SPECIFIER_PATTERN, $format);
3034
}
3135

3236
/** @phpstan-return array<int, non-empty-list<PrintfPlaceholder>> parameter index => placeholders */
3337
public function getPrintfPlaceholders(string $format): ?array
3438
{
35-
return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format, false);
39+
return $this->parsePlaceholders(self::PRINTF_SPECIFIER_PATTERN, $format);
3640
}
3741

3842
public function getScanfPlaceholdersCount(string $format): ?int
3943
{
40-
return $this->getPlaceholdersCount('(?<specifier>[cdDeEfinosuxX%s]|\[[^\]]+\])', $format, true);
44+
$throws = $this->phpVersion->throwsValueErrorForInternalFunctions();
45+
try {
46+
if ($throws === false) {
47+
set_error_handler(
48+
static function ($s, $m) {
49+
throw new ValueError($m, 0);
50+
},
51+
);
52+
}
53+
$result = sscanf('', '%*n' . $format);
54+
if ($result === null) {
55+
return null;
56+
}
57+
return count($result);
58+
} catch (ValueError) {
59+
return null;
60+
} finally {
61+
if ($throws === false) {
62+
restore_error_handler();
63+
}
64+
}
4165
}
4266

4367
/**
4468
* @phpstan-return array<int, non-empty-list<PrintfPlaceholder>>|null parameter index => placeholders
4569
*/
46-
private function parsePlaceholders(string $specifiersPattern, string $format, bool $isScanf): ?array
70+
private function parsePlaceholders(string $specifiersPattern, string $format): ?array
4771
{
4872
$addSpecifier = '';
4973
if ($this->phpVersion->supportsHhPrintfSpecifier()) {
@@ -72,10 +96,6 @@ private function parsePlaceholders(string $specifiersPattern, string $format, bo
7296
$showValueSuffix = false;
7397

7498
if (isset($placeholder['width']) && $placeholder['width'] !== '') {
75-
if ($isScanf) {
76-
// In scanf, * means assignment suppression - skip this placeholder entirely
77-
continue;
78-
}
7999
$parsedPlaceholders[] = new PrintfPlaceholder(
80100
sprintf('"%s" (width)', $placeholder[0]),
81101
$parameterIdx++,
@@ -136,9 +156,9 @@ private function getAcceptingTypeBySpecifier(string $specifier): string
136156
return 'mixed';
137157
}
138158

139-
private function getPlaceholdersCount(string $specifiersPattern, string $format, bool $isScanf): ?int
159+
private function getPlaceholdersCount(string $specifiersPattern, string $format): ?int
140160
{
141-
$placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format, $isScanf);
161+
$placeholdersMap = $this->parsePlaceholders($specifiersPattern, $format);
142162
if ($placeholdersMap === null) {
143163
return null;
144164
}

0 commit comments

Comments
 (0)