[clang-tidy] Improve integer comparison by matching valid expressions outside implicitCastExpr (#134188)

Aims to fix #127471
Covered the edge case where an int expression is not necessarily
directly wrapped around an 'ImplicitCastExpr' which seemed to be a
requirement in 'use-integer-sign-comparison.cpp' check to trigger.

**For instance**:

```cpp
#include <vector>

bool f() {
  std::vector<int> v;
  unsigned int i = 0;

  return i >= v.size();
}
```
This commit is contained in:
David Rivera
2025-06-10 03:57:11 -04:00
committed by GitHub
parent 054646f335
commit e65d323166
3 changed files with 96 additions and 7 deletions

View File

@@ -39,21 +39,28 @@ intCastExpression(bool IsSigned,
// std::cmp_{} functions trigger a compile-time error if either LHS or RHS
// is a non-integer type, char, enum or bool
// (unsigned char/ signed char are Ok and can be used).
auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(
const auto HasIntegerType = hasType(hasCanonicalType(qualType(
isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
unless(isActualChar()), unless(booleanType()), unless(enumType())))));
unless(isActualChar()), unless(booleanType()), unless(enumType()))));
const auto IntTypeExpr = expr(HasIntegerType);
const auto ImplicitCastExpr =
CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))
: implicitCastExpr(hasSourceExpression(IntTypeExpr))
.bind(CastBindName);
const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
const auto ExplicitCastExpr =
anyOf(explicitCastExpr(has(ImplicitCastExpr)),
ignoringImpCasts(explicitCastExpr(has(ImplicitCastExpr))));
return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
FunctionalCastExpr));
// Match function calls or variable references not directly wrapped by an
// implicit cast
const auto CallIntExpr = CastBindName.empty()
? callExpr(HasIntegerType)
: callExpr(HasIntegerType).bind(CastBindName);
return expr(anyOf(ImplicitCastExpr, ExplicitCastExpr, CallIntExpr));
}
static StringRef parseOpCode(BinaryOperator::Opcode Code) {

View File

@@ -237,6 +237,10 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-designated-initializers>` check by avoiding
diagnosing designated initializers for ``std::array`` initializations.
- Improved :doc:`modernize-use-integer-sign-comparison
<clang-tidy/checks/modernize/use-integer-sign-comparison>` check by matching
valid integer expressions not directly wrapped around an implicit cast.
- Improved :doc:`modernize-use-ranges
<clang-tidy/checks/modernize/use-ranges>` check by updating suppress
warnings logic for ``nullptr`` in ``std::find``.

View File

@@ -121,3 +121,81 @@ int AllComparisons() {
return 0;
}
namespace PR127471 {
int getSignedValue();
unsigned int getUnsignedValue();
void callExprTest() {
if (getSignedValue() < getUnsignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getSignedValue() , getUnsignedValue()))
int sVar = 0;
if (getUnsignedValue() > sVar)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_greater(getUnsignedValue() , sVar))
unsigned int uVar = 0;
if (getSignedValue() > uVar)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_greater(getSignedValue() , uVar))
}
// Add a class with member functions for testing member function calls
class TestClass {
public:
int getSignedValue() { return -5; }
unsigned int getUnsignedValue() { return 5; }
};
void memberFunctionTests() {
TestClass obj;
if (obj.getSignedValue() < obj.getUnsignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(obj.getSignedValue() , obj.getUnsignedValue()))
}
void castFunctionTests() {
// C-style casts with function calls
if ((int)getUnsignedValue() < (unsigned int)getSignedValue())
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getUnsignedValue(),getSignedValue()))
// Static casts with function calls
if (static_cast<int>(getUnsignedValue()) < static_cast<unsigned int>(getSignedValue()))
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(getUnsignedValue(),getSignedValue()))
}
// Define tests
#define SIGNED_FUNC getSignedValue()
#define UNSIGNED_FUNC getUnsignedValue()
void defineTests() {
if (SIGNED_FUNC < UNSIGNED_FUNC)
return;
// CHECK-MESSAGES: :[[@LINE-2]]:13: warning: comparison between 'signed' and 'unsigned' integers [modernize-use-integer-sign-comparison]
// CHECK-FIXES: if (std::cmp_less(SIGNED_FUNC , UNSIGNED_FUNC))
}
// Template tests (should not warn)
template <typename T1>
void templateFunctionTest(T1 value) {
if (value() < getUnsignedValue())
return;
if (value() < (getSignedValue() || getUnsignedValue()))
return;
}
} // namespace PR127471