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