mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 13:51:54 +01:00
Merge pull request #9904 from kkmuffme/sprintf-additional-validations-and-bugfix
Sprintf additional validations and bugfix
This commit is contained in:
commit
2c50745d8f
@ -22,6 +22,7 @@ use ValueError;
|
|||||||
use function array_fill;
|
use function array_fill;
|
||||||
use function array_pop;
|
use function array_pop;
|
||||||
use function count;
|
use function count;
|
||||||
|
use function preg_match;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +64,7 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
|||||||
$node_type_provider = $statements_source->getNodeTypeProvider();
|
$node_type_provider = $statements_source->getNodeTypeProvider();
|
||||||
foreach ($call_args as $index => $call_arg) {
|
foreach ($call_args as $index => $call_arg) {
|
||||||
$type = $node_type_provider->getType($call_arg->value);
|
$type = $node_type_provider->getType($call_arg->value);
|
||||||
if ($type === null && $event->getFunctionId() === 'printf') {
|
if ($type === null && $index === 0 && $event->getFunctionId() === 'printf') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +73,56 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($index === 0 && $type->isSingleStringLiteral()) {
|
if ($index === 0 && $type->isSingleStringLiteral()) {
|
||||||
|
if ($type->getSingleStringLiteral()->value === '') {
|
||||||
|
IssueBuffer::maybeAdd(
|
||||||
|
new InvalidArgument(
|
||||||
|
'Argument 1 of ' . $event->getFunctionId() . ' must not be an empty string',
|
||||||
|
$event->getCodeLocation(),
|
||||||
|
$event->getFunctionId(),
|
||||||
|
),
|
||||||
|
$statements_source->getSuppressedIssues(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($event->getFunctionId() === 'printf') {
|
||||||
|
return Type::getInt(false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Type::getString('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are probably additional formats that return an empty string, this is just a starting point
|
||||||
|
if (preg_match('/^%(?:\d+\$)?[-+]?0(?:\.0)?s$/', $type->getSingleStringLiteral()->value) === 1) {
|
||||||
|
IssueBuffer::maybeAdd(
|
||||||
|
new InvalidArgument(
|
||||||
|
'The pattern of argument 1 of ' . $event->getFunctionId()
|
||||||
|
. ' will always return an empty string',
|
||||||
|
$event->getCodeLocation(),
|
||||||
|
$event->getFunctionId(),
|
||||||
|
),
|
||||||
|
$statements_source->getSuppressedIssues(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($event->getFunctionId() === 'printf') {
|
||||||
|
return Type::getInt(false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Type::getString('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// these placeholders are too complex to handle for now
|
||||||
|
if (preg_match(
|
||||||
|
'/%(?:\d+\$)?[-+]?(?:\d+|\*)(?:\.(?:\d+|\*))?[bcdouxXeEfFgGhHs]/',
|
||||||
|
$type->getSingleStringLiteral()->value,
|
||||||
|
) === 1) {
|
||||||
|
if ($event->getFunctionId() === 'printf') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the core stubs are wrong for these too, since these might be empty strings
|
||||||
|
// e.g. sprintf(\'%0.*s\', 0, "abc")
|
||||||
|
return Type::getString();
|
||||||
|
}
|
||||||
|
|
||||||
$args_count = count($call_args) - 1;
|
$args_count = count($call_args) - 1;
|
||||||
$dummy = array_fill(0, $args_count, '');
|
$dummy = array_fill(0, $args_count, '');
|
||||||
|
|
||||||
@ -84,6 +135,20 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
|||||||
$result = @sprintf($type->getSingleStringLiteral()->value, ...$dummy);
|
$result = @sprintf($type->getSingleStringLiteral()->value, ...$dummy);
|
||||||
if ($initial_result === null) {
|
if ($initial_result === null) {
|
||||||
$initial_result = $result;
|
$initial_result = $result;
|
||||||
|
|
||||||
|
if ($result === $type->getSingleStringLiteral()->value) {
|
||||||
|
IssueBuffer::maybeAdd(
|
||||||
|
new InvalidArgument(
|
||||||
|
'Argument 1 of ' . $event->getFunctionId()
|
||||||
|
. ' does not contain any placeholders',
|
||||||
|
$event->getCodeLocation(),
|
||||||
|
$event->getFunctionId(),
|
||||||
|
),
|
||||||
|
$statements_source->getSuppressedIssues(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ValueError $value_error) {
|
} catch (ValueError $value_error) {
|
||||||
// PHP 8
|
// PHP 8
|
||||||
@ -189,6 +254,12 @@ class SprintfReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
|||||||
if ($initial_result !== null && $initial_result !== false && $initial_result !== '') {
|
if ($initial_result !== null && $initial_result !== false && $initial_result !== '') {
|
||||||
return Type::getNonEmptyString();
|
return Type::getNonEmptyString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we didn't have a valid result
|
||||||
|
// the pattern is invalid or not yet supported by the return type provider
|
||||||
|
if ($initial_result === null || $initial_result === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($index === 0 && $event->getFunctionId() === 'printf') {
|
if ($index === 0 && $event->getFunctionId() === 'printf') {
|
||||||
|
@ -131,6 +131,57 @@ class SprintfTest extends TestCase
|
|||||||
'$val===' => 'int<0, max>',
|
'$val===' => 'int<0, max>',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
yield 'sprintfEmptyStringFormat' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$val = sprintf("", "abc");
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$val===' => '\'\'',
|
||||||
|
],
|
||||||
|
'ignored_issues' => [
|
||||||
|
'InvalidArgument',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'sprintfPaddedEmptyStringFormat' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$val = sprintf("%0.0s", "abc");
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$val===' => '\'\'',
|
||||||
|
],
|
||||||
|
'ignored_issues' => [
|
||||||
|
'InvalidArgument',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'sprintfComplexPlaceholderNotYetSupported1' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$val = sprintf(\'%*.0s\', 0, "abc");
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$val===' => 'string',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'sprintfComplexPlaceholderNotYetSupported2' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$val = sprintf(\'%0.*s\', 0, "abc");
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$val===' => 'string',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
yield 'sprintfComplexPlaceholderNotYetSupported3' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$val = sprintf(\'%*.*s\', 0, 0, "abc");
|
||||||
|
',
|
||||||
|
'assertions' => [
|
||||||
|
'$val===' => 'string',
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function providerInvalidCodeParse(): iterable
|
public function providerInvalidCodeParse(): iterable
|
||||||
@ -184,6 +235,24 @@ class SprintfTest extends TestCase
|
|||||||
',
|
',
|
||||||
'error_message' => 'InvalidArgument',
|
'error_message' => 'InvalidArgument',
|
||||||
],
|
],
|
||||||
|
'sprintfEmptyFormat' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$x = sprintf("", "abc");
|
||||||
|
',
|
||||||
|
'error_message' => 'InvalidArgument',
|
||||||
|
],
|
||||||
|
'sprintfFormatWithoutPlaceholders' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$x = sprintf("hello", "abc");
|
||||||
|
',
|
||||||
|
'error_message' => 'InvalidArgument',
|
||||||
|
],
|
||||||
|
'sprintfPaddedComplexEmptyStringFormat' => [
|
||||||
|
'code' => '<?php
|
||||||
|
$x = sprintf("%1$+0.0s", "abc");
|
||||||
|
',
|
||||||
|
'error_message' => 'InvalidArgument',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user