xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseIntegerSignComparisonCheck.cpp (revision aa580c2ec5eb4217c945a47a561181be7e7b1032)
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       EnableQtSupport(Options.get("EnableQtSupport", false)) {}
85 
86 void UseIntegerSignComparisonCheck::storeOptions(
87     ClangTidyOptions::OptionMap &Opts) {
88   Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
89   Options.store(Opts, "EnableQtSupport", EnableQtSupport);
90 }
91 
92 void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {
93   const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression");
94   const auto UnSignedIntCastExpr = intCastExpression(false);
95 
96   // Flag all operators "==", "<=", ">=", "<", ">", "!="
97   // that are used between signed/unsigned
98   const auto CompareOperator =
99       binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),
100                      hasOperands(SignedIntCastExpr, UnSignedIntCastExpr),
101                      unless(isInTemplateInstantiation()))
102           .bind("intComparison");
103 
104   Finder->addMatcher(CompareOperator, this);
105 }
106 
107 void UseIntegerSignComparisonCheck::registerPPCallbacks(
108     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
109   IncludeInserter.registerPreprocessor(PP);
110 }
111 
112 void UseIntegerSignComparisonCheck::check(
113     const MatchFinder::MatchResult &Result) {
114   const auto *SignedCastExpression =
115       Result.Nodes.getNodeAs<ImplicitCastExpr>("sIntCastExpression");
116   assert(SignedCastExpression);
117 
118   // Ignore the match if we know that the signed int value is not negative.
119   Expr::EvalResult EVResult;
120   if (!SignedCastExpression->isValueDependent() &&
121       SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
122                                                         *Result.Context)) {
123     const llvm::APSInt SValue = EVResult.Val.getInt();
124     if (SValue.isNonNegative())
125       return;
126   }
127 
128   const auto *BinaryOp =
129       Result.Nodes.getNodeAs<BinaryOperator>("intComparison");
130   if (BinaryOp == nullptr)
131     return;
132 
133   const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode();
134 
135   const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();
136   const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();
137   if (LHS == nullptr || RHS == nullptr)
138     return;
139   const Expr *SubExprLHS = nullptr;
140   const Expr *SubExprRHS = nullptr;
141   SourceRange R1 = SourceRange(LHS->getBeginLoc());
142   SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc());
143   SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken(
144       RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
145   if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(LHS)) {
146     SubExprLHS = LHSCast->getSubExpr();
147     R1 = SourceRange(LHS->getBeginLoc(),
148                      SubExprLHS->getBeginLoc().getLocWithOffset(-1));
149     R2.setBegin(Lexer::getLocForEndOfToken(
150         SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
151   }
152   if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(RHS)) {
153     SubExprRHS = RHSCast->getSubExpr();
154     R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));
155   }
156   DiagnosticBuilder Diag =
157       diag(BinaryOp->getBeginLoc(),
158            "comparison between 'signed' and 'unsigned' integers");
159   std::string CmpNamespace;
160   llvm::StringRef CmpHeader;
161 
162   if (getLangOpts().CPlusPlus20) {
163     CmpHeader = "<utility>";
164     CmpNamespace = llvm::Twine("std::" + parseOpCode(OpCode)).str();
165   } else if (getLangOpts().CPlusPlus17 && EnableQtSupport) {
166     CmpHeader = "<QtCore/q20utility.h>";
167     CmpNamespace = llvm::Twine("q20::" + parseOpCode(OpCode)).str();
168   }
169 
170   // Prefer modernize-use-integer-sign-comparison when C++20 is available!
171   Diag << FixItHint::CreateReplacement(
172       CharSourceRange(R1, SubExprLHS != nullptr),
173       llvm::Twine(CmpNamespace + "(").str());
174   Diag << FixItHint::CreateReplacement(R2, ",");
175   Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")");
176 
177   // If there is no include for cmp_{*} functions, we'll add it.
178   Diag << IncludeInserter.createIncludeInsertion(
179       Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader);
180 }
181 
182 } // namespace clang::tidy::modernize
183