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
14using namespace clang::ast_matchers;
15using namespace clang::ast_matchers::internal;
16
17namespace 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.
21static 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>(Val: DesugaredType))
25 return (BT->getKind() == BuiltinType::Char_U ||
26 BT->getKind() == BuiltinType::Char_S);
27 return false;
28}
29
30namespace {
31AST_MATCHER(clang::QualType, isActualChar) {
32 return clang::tidy::modernize::isActualCharType(Ty: Node);
33}
34} // namespace
35
36static BindableMatcher<clang::Stmt>
37intCastExpression(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 const auto HasIntegerType = hasType(InnerMatcher: hasCanonicalType(InnerMatcher: qualType(
43 isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
44 unless(isActualChar()), unless(booleanType()), unless(enumType()))));
45
46 const auto IntTypeExpr = expr(HasIntegerType);
47
48 const auto ImplicitCastExpr =
49 CastBindName.empty() ? implicitCastExpr(hasSourceExpression(InnerMatcher: IntTypeExpr))
50 : implicitCastExpr(hasSourceExpression(InnerMatcher: IntTypeExpr))
51 .bind(ID: CastBindName);
52
53 const auto ExplicitCastExpr =
54 anyOf(explicitCastExpr(has(ImplicitCastExpr)),
55 ignoringImpCasts(InnerMatcher: explicitCastExpr(has(ImplicitCastExpr))));
56
57 // Match function calls or variable references not directly wrapped by an
58 // implicit cast
59 const auto CallIntExpr = CastBindName.empty()
60 ? callExpr(HasIntegerType)
61 : callExpr(HasIntegerType).bind(ID: CastBindName);
62
63 return expr(anyOf(ImplicitCastExpr, ExplicitCastExpr, CallIntExpr));
64}
65
66static StringRef parseOpCode(BinaryOperator::Opcode Code) {
67 switch (Code) {
68 case BO_LT:
69 return "cmp_less";
70 case BO_GT:
71 return "cmp_greater";
72 case BO_LE:
73 return "cmp_less_equal";
74 case BO_GE:
75 return "cmp_greater_equal";
76 case BO_EQ:
77 return "cmp_equal";
78 case BO_NE:
79 return "cmp_not_equal";
80 default:
81 return "";
82 }
83}
84
85UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck(
86 StringRef Name, ClangTidyContext *Context)
87 : ClangTidyCheck(Name, Context),
88 IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle",
89 Default: utils::IncludeSorter::IS_LLVM),
90 areDiagsSelfContained()),
91 EnableQtSupport(Options.get(LocalName: "EnableQtSupport", Default: false)) {}
92
93void UseIntegerSignComparisonCheck::storeOptions(
94 ClangTidyOptions::OptionMap &Opts) {
95 Options.store(Options&: Opts, LocalName: "IncludeStyle", Value: IncludeInserter.getStyle());
96 Options.store(Options&: Opts, LocalName: "EnableQtSupport", Value: EnableQtSupport);
97}
98
99void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {
100 const auto SignedIntCastExpr = intCastExpression(IsSigned: true, CastBindName: "sIntCastExpression");
101 const auto UnSignedIntCastExpr = intCastExpression(IsSigned: false);
102
103 // Flag all operators "==", "<=", ">=", "<", ">", "!="
104 // that are used between signed/unsigned
105 const auto CompareOperator =
106 binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),
107 hasOperands(Matcher1: SignedIntCastExpr, Matcher2: UnSignedIntCastExpr),
108 unless(isInTemplateInstantiation()))
109 .bind(ID: "intComparison");
110
111 Finder->addMatcher(NodeMatch: CompareOperator, Action: this);
112}
113
114void UseIntegerSignComparisonCheck::registerPPCallbacks(
115 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
116 IncludeInserter.registerPreprocessor(PP);
117}
118
119void UseIntegerSignComparisonCheck::check(
120 const MatchFinder::MatchResult &Result) {
121 const auto *SignedCastExpression =
122 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "sIntCastExpression");
123 assert(SignedCastExpression);
124
125 // Ignore the match if we know that the signed int value is not negative.
126 Expr::EvalResult EVResult;
127 if (!SignedCastExpression->isValueDependent() &&
128 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
129 *Result.Context)) {
130 const llvm::APSInt SValue = EVResult.Val.getInt();
131 if (SValue.isNonNegative())
132 return;
133 }
134
135 const auto *BinaryOp =
136 Result.Nodes.getNodeAs<BinaryOperator>(ID: "intComparison");
137 if (BinaryOp == nullptr)
138 return;
139
140 const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode();
141
142 const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();
143 const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();
144 if (LHS == nullptr || RHS == nullptr)
145 return;
146 const Expr *SubExprLHS = nullptr;
147 const Expr *SubExprRHS = nullptr;
148 SourceRange R1 = SourceRange(LHS->getBeginLoc());
149 SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc());
150 SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken(
151 Loc: RHS->getEndLoc(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts()));
152 if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(Val: LHS)) {
153 SubExprLHS = LHSCast->getSubExpr();
154 R1 = SourceRange(LHS->getBeginLoc(),
155 SubExprLHS->getBeginLoc().getLocWithOffset(-1));
156 R2.setBegin(Lexer::getLocForEndOfToken(
157 Loc: SubExprLHS->getEndLoc(), Offset: 0, SM: *Result.SourceManager, LangOpts: getLangOpts()));
158 }
159 if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(Val: RHS)) {
160 SubExprRHS = RHSCast->getSubExpr();
161 R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));
162 }
163 DiagnosticBuilder Diag =
164 diag(Loc: BinaryOp->getBeginLoc(),
165 Description: "comparison between 'signed' and 'unsigned' integers");
166 std::string CmpNamespace;
167 llvm::StringRef CmpHeader;
168
169 if (getLangOpts().CPlusPlus20) {
170 CmpHeader = "<utility>";
171 CmpNamespace = llvm::Twine("std::" + parseOpCode(Code: OpCode)).str();
172 } else if (getLangOpts().CPlusPlus17 && EnableQtSupport) {
173 CmpHeader = "<QtCore/q20utility.h>";
174 CmpNamespace = llvm::Twine("q20::" + parseOpCode(Code: OpCode)).str();
175 }
176
177 // Prefer modernize-use-integer-sign-comparison when C++20 is available!
178 Diag << FixItHint::CreateReplacement(
179 RemoveRange: CharSourceRange(R1, SubExprLHS != nullptr),
180 Code: llvm::Twine(CmpNamespace + "(").str());
181 Diag << FixItHint::CreateReplacement(RemoveRange: R2, Code: ",");
182 Diag << FixItHint::CreateReplacement(RemoveRange: CharSourceRange::getCharRange(R: R3), Code: ")");
183
184 // If there is no include for cmp_{*} functions, we'll add it.
185 Diag << IncludeInserter.createIncludeInsertion(
186 FileID: Result.SourceManager->getFileID(SpellingLoc: BinaryOp->getBeginLoc()), Header: CmpHeader);
187}
188
189} // namespace clang::tidy::modernize
190

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/modernize/UseIntegerSignComparisonCheck.cpp