mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #3877 - prevent impossible subtr comparisons
This commit is contained in:
parent
b88b169464
commit
84945a7d1b
@ -157,6 +157,88 @@ class BinaryOpAnalyzer
|
||||
$stmt_left_type = $statements_analyzer->node_data->getType($stmt->left);
|
||||
$stmt_right_type = $statements_analyzer->node_data->getType($stmt->right);
|
||||
|
||||
if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical)
|
||||
&& $stmt->left instanceof PhpParser\Node\Expr\FuncCall
|
||||
&& $stmt->left->name instanceof PhpParser\Node\Name
|
||||
&& $stmt->left->name->parts === ['substr']
|
||||
&& isset($stmt->left->args[1])
|
||||
&& $stmt_right_type
|
||||
&& $stmt_right_type->hasLiteralString()
|
||||
) {
|
||||
$from_type = $statements_analyzer->node_data->getType($stmt->left->args[1]->value);
|
||||
|
||||
$length_type = isset($stmt->left->args[2])
|
||||
? ($statements_analyzer->node_data->getType($stmt->left->args[2]->value) ?: Type::getMixed())
|
||||
: null;
|
||||
|
||||
$string_length = null;
|
||||
|
||||
if ($from_type && $from_type->isSingleIntLiteral() && $length_type === null) {
|
||||
$string_length = -$from_type->getSingleIntLiteral()->value;
|
||||
} elseif ($length_type && $length_type->isSingleIntLiteral()) {
|
||||
$string_length = $length_type->getSingleIntLiteral()->value;
|
||||
}
|
||||
|
||||
if ($string_length > 0) {
|
||||
foreach ($stmt_right_type->getAtomicTypes() as $atomic_right_type) {
|
||||
if ($atomic_right_type instanceof Type\Atomic\TLiteralString) {
|
||||
if (strlen($atomic_right_type->value) !== $string_length) {
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
||||
) {
|
||||
if ($atomic_right_type->from_docblock) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\DocblockTypeContradiction(
|
||||
$atomic_right_type . ' string length is not ' . $string_length,
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\TypeDoesNotContainType(
|
||||
$atomic_right_type . ' string length is not ' . $string_length,
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($atomic_right_type->from_docblock) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\RedundantConditionGivenDocblockType(
|
||||
$atomic_right_type . ' string length is never ' . $string_length,
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\RedundantCondition(
|
||||
$atomic_right_type . ' string length is never ' . $string_length,
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
||||
&& $stmt_left_type
|
||||
&& $stmt_right_type
|
||||
|
@ -93,20 +93,6 @@ array_map(
|
||||
error_log('Bad argument');
|
||||
exit(1);
|
||||
}
|
||||
} elseif (substr($arg, 0, 2) === '-' && $arg !== '-' && $arg !== '--') {
|
||||
$arg_name = preg_replace('/=.*$/', '', substr($arg, 1));
|
||||
|
||||
if (!in_array($arg_name, $valid_short_options, true)
|
||||
&& !in_array($arg_name . ':', $valid_short_options, true)
|
||||
) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'Unrecognised argument "-' . $arg_name . '"' . PHP_EOL
|
||||
. 'Type --help to see a list of supported arguments' . PHP_EOL
|
||||
);
|
||||
error_log('Bad argument');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
$args
|
||||
|
@ -84,17 +84,6 @@ array_map(
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
} elseif (substr($arg, 0, 2) === '-' && $arg !== '-' && $arg !== '--') {
|
||||
$arg_name = preg_replace('/=.*$/', '', substr($arg, 1));
|
||||
|
||||
if (!in_array($arg_name, $valid_short_options) && !in_array($arg_name . ':', $valid_short_options)) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'Unrecognised argument "-' . $arg_name . '"' . PHP_EOL
|
||||
. 'Type --help to see a list of supported arguments'. PHP_EOL
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
$args
|
||||
|
@ -106,17 +106,6 @@ array_map(
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
} elseif (substr($arg, 0, 2) === '-' && $arg !== '-' && $arg !== '--') {
|
||||
$arg_name = preg_replace('/=.*$/', '', substr($arg, 1));
|
||||
|
||||
if (!in_array($arg_name, $valid_short_options) && !in_array($arg_name . ':', $valid_short_options)) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'Unrecognised argument "-' . $arg_name . '"' . PHP_EOL
|
||||
. 'Type --help to see a list of supported arguments'. PHP_EOL
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
$args
|
||||
|
@ -358,6 +358,17 @@ class BinaryOperationTest extends TestCase
|
||||
$a = ~true;',
|
||||
'error_message' => 'InvalidOperand',
|
||||
],
|
||||
'substrImpossible' => [
|
||||
'<?php
|
||||
class HelloWorld
|
||||
{
|
||||
public function sayHello(string $s): void
|
||||
{
|
||||
if (substr($s, 0, 6) === "abc") {}
|
||||
}
|
||||
}',
|
||||
'error_message' => 'TypeDoesNotContainType',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user