Skip to content

Commit 8b63bfb

Browse files
authored
[clang-tidy] Create a check for signed and unsigned integers comparison (#113144)
- modernize-use-integer-sign-comparison replaces comparisons between signed and unsigned integers with their safe C++20 ``std::cmp_*`` alternative, if available.
1 parent 624cc70 commit 8b63bfb

File tree

8 files changed

+383
-1
lines changed

8 files changed

+383
-1
lines changed

clang-tools-extra/clang-tidy/modernize/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_clang_library(clangTidyModernizeModule STATIC
3636
UseEmplaceCheck.cpp
3737
UseEqualsDefaultCheck.cpp
3838
UseEqualsDeleteCheck.cpp
39+
UseIntegerSignComparisonCheck.cpp
3940
UseNodiscardCheck.cpp
4041
UseNoexceptCheck.cpp
4142
UseNullptrCheck.cpp

clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "UseEmplaceCheck.h"
3838
#include "UseEqualsDefaultCheck.h"
3939
#include "UseEqualsDeleteCheck.h"
40+
#include "UseIntegerSignComparisonCheck.h"
4041
#include "UseNodiscardCheck.h"
4142
#include "UseNoexceptCheck.h"
4243
#include "UseNullptrCheck.h"
@@ -76,6 +77,8 @@ class ModernizeModule : public ClangTidyModule {
7677
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
7778
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
7879
"modernize-use-designated-initializers");
80+
CheckFactories.registerCheck<UseIntegerSignComparisonCheck>(
81+
"modernize-use-integer-sign-comparison");
7982
CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
8083
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
8184
"modernize-use-starts-ends-with");
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//===--- UseIntegerSignComparisonCheck.cpp - clang-tidy -------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "UseIntegerSignComparisonCheck.h"
10+
#include "clang/AST/Expr.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/Lex/Lexer.h"
13+
14+
using namespace clang::ast_matchers;
15+
using namespace clang::ast_matchers::internal;
16+
17+
namespace clang::tidy::modernize {
18+
19+
/// Find if the passed type is the actual "char" type,
20+
/// not applicable to explicit "signed char" or "unsigned char" types.
21+
static bool isActualCharType(const clang::QualType &Ty) {
22+
using namespace clang;
23+
const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
24+
if (const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType))
25+
return (BT->getKind() == BuiltinType::Char_U ||
26+
BT->getKind() == BuiltinType::Char_S);
27+
return false;
28+
}
29+
30+
namespace {
31+
AST_MATCHER(clang::QualType, isActualChar) {
32+
return clang::tidy::modernize::isActualCharType(Node);
33+
}
34+
} // namespace
35+
36+
static BindableMatcher<clang::Stmt>
37+
intCastExpression(bool IsSigned,
38+
const std::string &CastBindName = std::string()) {
39+
// std::cmp_{} functions trigger a compile-time error if either LHS or RHS
40+
// is a non-integer type, char, enum or bool
41+
// (unsigned char/ signed char are Ok and can be used).
42+
auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(
43+
isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
44+
unless(isActualChar()), unless(booleanType()), unless(enumType())))));
45+
46+
const auto ImplicitCastExpr =
47+
CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))
48+
: implicitCastExpr(hasSourceExpression(IntTypeExpr))
49+
.bind(CastBindName);
50+
51+
const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
52+
const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
53+
const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
54+
55+
return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
56+
FunctionalCastExpr));
57+
}
58+
59+
static StringRef parseOpCode(BinaryOperator::Opcode Code) {
60+
switch (Code) {
61+
case BO_LT:
62+
return "cmp_less";
63+
case BO_GT:
64+
return "cmp_greater";
65+
case BO_LE:
66+
return "cmp_less_equal";
67+
case BO_GE:
68+
return "cmp_greater_equal";
69+
case BO_EQ:
70+
return "cmp_equal";
71+
case BO_NE:
72+
return "cmp_not_equal";
73+
default:
74+
return "";
75+
}
76+
}
77+
78+
UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck(
79+
StringRef Name, ClangTidyContext *Context)
80+
: ClangTidyCheck(Name, Context),
81+
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
82+
utils::IncludeSorter::IS_LLVM),
83+
areDiagsSelfContained()) {}
84+
85+
void UseIntegerSignComparisonCheck::storeOptions(
86+
ClangTidyOptions::OptionMap &Opts) {
87+
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
88+
}
89+
90+
void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {
91+
const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression");
92+
const auto UnSignedIntCastExpr = intCastExpression(false);
93+
94+
// Flag all operators "==", "<=", ">=", "<", ">", "!="
95+
// that are used between signed/unsigned
96+
const auto CompareOperator =
97+
binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),
98+
hasOperands(SignedIntCastExpr, UnSignedIntCastExpr),
99+
unless(isInTemplateInstantiation()))
100+
.bind("intComparison");
101+
102+
Finder->addMatcher(CompareOperator, this);
103+
}
104+
105+
void UseIntegerSignComparisonCheck::registerPPCallbacks(
106+
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
107+
IncludeInserter.registerPreprocessor(PP);
108+
}
109+
110+
void UseIntegerSignComparisonCheck::check(
111+
const MatchFinder::MatchResult &Result) {
112+
const auto *SignedCastExpression =
113+
Result.Nodes.getNodeAs<ImplicitCastExpr>("sIntCastExpression");
114+
assert(SignedCastExpression);
115+
116+
// Ignore the match if we know that the signed int value is not negative.
117+
Expr::EvalResult EVResult;
118+
if (!SignedCastExpression->isValueDependent() &&
119+
SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
120+
*Result.Context)) {
121+
const llvm::APSInt SValue = EVResult.Val.getInt();
122+
if (SValue.isNonNegative())
123+
return;
124+
}
125+
126+
const auto *BinaryOp =
127+
Result.Nodes.getNodeAs<BinaryOperator>("intComparison");
128+
if (BinaryOp == nullptr)
129+
return;
130+
131+
const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode();
132+
133+
const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();
134+
const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();
135+
if (LHS == nullptr || RHS == nullptr)
136+
return;
137+
const Expr *SubExprLHS = nullptr;
138+
const Expr *SubExprRHS = nullptr;
139+
SourceRange R1 = SourceRange(LHS->getBeginLoc());
140+
SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc());
141+
SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken(
142+
RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
143+
if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(LHS)) {
144+
SubExprLHS = LHSCast->getSubExpr();
145+
R1 = SourceRange(LHS->getBeginLoc(),
146+
SubExprLHS->getBeginLoc().getLocWithOffset(-1));
147+
R2.setBegin(Lexer::getLocForEndOfToken(
148+
SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
149+
}
150+
if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(RHS)) {
151+
SubExprRHS = RHSCast->getSubExpr();
152+
R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));
153+
}
154+
DiagnosticBuilder Diag =
155+
diag(BinaryOp->getBeginLoc(),
156+
"comparison between 'signed' and 'unsigned' integers");
157+
const std::string CmpNamespace = ("std::" + parseOpCode(OpCode)).str();
158+
const std::string CmpHeader = "<utility>";
159+
// Prefer modernize-use-integer-sign-comparison when C++20 is available!
160+
Diag << FixItHint::CreateReplacement(
161+
CharSourceRange(R1, SubExprLHS != nullptr),
162+
llvm::Twine(CmpNamespace + "(").str());
163+
Diag << FixItHint::CreateReplacement(R2, ",");
164+
Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")");
165+
166+
// If there is no include for cmp_{*} functions, we'll add it.
167+
Diag << IncludeInserter.createIncludeInsertion(
168+
Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader);
169+
}
170+
171+
} // namespace clang::tidy::modernize
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===--- UseIntegerSignComparisonCheck.h - clang-tidy -----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEINTEGERSIGNCOMPARISONCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEINTEGERSIGNCOMPARISONCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include "../utils/IncludeInserter.h"
14+
#include "clang/ASTMatchers/ASTMatchFinder.h"
15+
16+
namespace clang::tidy::modernize {
17+
18+
/// Replace comparisons between signed and unsigned integers with their safe
19+
/// C++20 ``std::cmp_*`` alternative, if available.
20+
///
21+
/// For the user-facing documentation see:
22+
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-integer-sign-comparison.html
23+
class UseIntegerSignComparisonCheck : public ClangTidyCheck {
24+
public:
25+
UseIntegerSignComparisonCheck(StringRef Name, ClangTidyContext *Context);
26+
27+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
28+
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
29+
Preprocessor *ModuleExpanderPP) override;
30+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
31+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
32+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
33+
return LangOpts.CPlusPlus20;
34+
}
35+
36+
private:
37+
utils::IncludeInserter IncludeInserter;
38+
};
39+
40+
} // namespace clang::tidy::modernize
41+
42+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEINTEGERSIGNCOMPARISONCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ New checks
136136
Gives warnings for tagged unions, where the number of tags is
137137
different from the number of data members inside the union.
138138

139+
- New :doc:`modernize-use-integer-sign-comparison
140+
<clang-tidy/checks/modernize/use-integer-sign-comparison>` check.
141+
142+
Replace comparisons between signed and unsigned integers with their safe
143+
C++20 ``std::cmp_*`` alternative, if available.
144+
139145
- New :doc:`portability-template-virtual-member-function
140146
<clang-tidy/checks/portability/template-virtual-member-function>` check.
141147

142-
Finds cases when an uninstantiated virtual member function in a template class
148+
Finds cases when an uninstantiated virtual member function in a template class
143149
causes cross-compiler incompatibility.
144150

145151
New check aliases

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ Clang-Tidy Checks
301301
:doc:`modernize-use-emplace <modernize/use-emplace>`, "Yes"
302302
:doc:`modernize-use-equals-default <modernize/use-equals-default>`, "Yes"
303303
:doc:`modernize-use-equals-delete <modernize/use-equals-delete>`, "Yes"
304+
:doc:`modernize-use-integer-sign-comparison <modernize/use-integer-sign-comparison>`, "Yes"
304305
:doc:`modernize-use-nodiscard <modernize/use-nodiscard>`, "Yes"
305306
:doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
306307
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.. title:: clang-tidy - modernize-use-integer-sign-comparison
2+
3+
modernize-use-integer-sign-comparison
4+
=====================================
5+
6+
Replace comparisons between signed and unsigned integers with their safe
7+
C++20 ``std::cmp_*`` alternative, if available.
8+
9+
The check provides a replacement only for C++20 or later, otherwise
10+
it highlights the problem and expects the user to fix it manually.
11+
12+
Examples of fixes created by the check:
13+
14+
.. code-block:: c++
15+
16+
unsigned int func(int a, unsigned int b) {
17+
return a == b;
18+
}
19+
20+
becomes
21+
22+
.. code-block:: c++
23+
24+
#include <utility>
25+
26+
unsigned int func(int a, unsigned int b) {
27+
return std::cmp_equal(a, b);
28+
}
29+
30+
Options
31+
-------
32+
33+
.. option:: IncludeStyle
34+
35+
A string specifying which include-style is used, `llvm` or `google`.
36+
Default is `llvm`.

0 commit comments

Comments
 (0)