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 172