176 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//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>(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(Node);33}34} // namespace35 36static BindableMatcher<clang::Stmt>37intCastExpression(bool IsSigned, StringRef CastBindName = {}) {38 // std::cmp_{} functions trigger a compile-time error if either LHS or RHS39 // is a non-integer type, char, enum or bool40 // (unsigned char/ signed char are Ok and can be used).41 auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(42 IsSigned ? isSignedInteger() : isUnsignedInteger(),43 unless(isActualChar()), unless(booleanType()), unless(enumType())))));44 45 const auto ImplicitCastExpr =46 CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))47 : implicitCastExpr(hasSourceExpression(IntTypeExpr))48 .bind(CastBindName);49 50 const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));51 const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));52 const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));53 54 return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,55 FunctionalCastExpr));56}57 58static StringRef parseOpCode(BinaryOperator::Opcode Code) {59 switch (Code) {60 case BO_LT:61 return "cmp_less";62 case BO_GT:63 return "cmp_greater";64 case BO_LE:65 return "cmp_less_equal";66 case BO_GE:67 return "cmp_greater_equal";68 case BO_EQ:69 return "cmp_equal";70 case BO_NE:71 return "cmp_not_equal";72 default:73 llvm_unreachable("invalid opcode");74 }75}76 77UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck(78 StringRef Name, ClangTidyContext *Context)79 : ClangTidyCheck(Name, Context),80 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",81 utils::IncludeSorter::IS_LLVM),82 areDiagsSelfContained()),83 EnableQtSupport(Options.get("EnableQtSupport", false)) {}84 85void UseIntegerSignComparisonCheck::storeOptions(86 ClangTidyOptions::OptionMap &Opts) {87 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());88 Options.store(Opts, "EnableQtSupport", EnableQtSupport);89}90 91void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {92 const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression");93 const auto UnSignedIntCastExpr = intCastExpression(false);94 95 // Flag all operators "==", "<=", ">=", "<", ">", "!="96 // that are used between signed/unsigned97 const auto CompareOperator =98 binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),99 hasOperands(SignedIntCastExpr, UnSignedIntCastExpr),100 unless(isInTemplateInstantiation()))101 .bind("intComparison");102 103 Finder->addMatcher(CompareOperator, this);104}105 106void UseIntegerSignComparisonCheck::registerPPCallbacks(107 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {108 IncludeInserter.registerPreprocessor(PP);109}110 111void UseIntegerSignComparisonCheck::check(112 const MatchFinder::MatchResult &Result) {113 const auto *SignedCastExpression =114 Result.Nodes.getNodeAs<ImplicitCastExpr>("sIntCastExpression");115 assert(SignedCastExpression);116 117 // Ignore the match if we know that the signed int value is not negative.118 Expr::EvalResult EVResult;119 if (!SignedCastExpression->isValueDependent() &&120 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,121 *Result.Context) &&122 EVResult.Val.getInt().isNonNegative())123 return;124 125 const auto *BinaryOp =126 Result.Nodes.getNodeAs<BinaryOperator>("intComparison");127 assert(BinaryOp);128 129 const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();130 const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();131 const Expr *SubExprLHS = nullptr;132 const Expr *SubExprRHS = nullptr;133 SourceRange R1(LHS->getBeginLoc());134 SourceRange R2(BinaryOp->getOperatorLoc());135 SourceRange R3(Lexer::getLocForEndOfToken(136 RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));137 if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(LHS)) {138 SubExprLHS = LHSCast->getSubExpr();139 R1.setEnd(SubExprLHS->getBeginLoc().getLocWithOffset(-1));140 R2.setBegin(Lexer::getLocForEndOfToken(141 SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));142 }143 if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(RHS)) {144 SubExprRHS = RHSCast->getSubExpr();145 R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));146 R3.setBegin(Lexer::getLocForEndOfToken(147 SubExprRHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));148 }149 const DiagnosticBuilder Diag =150 diag(BinaryOp->getBeginLoc(),151 "comparison between 'signed' and 'unsigned' integers");152 StringRef CmpNamespace;153 StringRef CmpHeader;154 155 if (getLangOpts().CPlusPlus20) {156 CmpHeader = "<utility>";157 CmpNamespace = "std::";158 } else if (getLangOpts().CPlusPlus17 && EnableQtSupport) {159 CmpHeader = "<QtCore/q20utility.h>";160 CmpNamespace = "q20::";161 }162 163 // Prefer modernize-use-integer-sign-comparison when C++20 is available!164 Diag << FixItHint::CreateReplacement(165 CharSourceRange(R1, SubExprLHS != nullptr),166 Twine(CmpNamespace + parseOpCode(BinaryOp->getOpcode()) + "(").str());167 Diag << FixItHint::CreateReplacement(R2, ",");168 Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")");169 170 // If there is no include for cmp_{*} functions, we'll add it.171 Diag << IncludeInserter.createIncludeInsertion(172 Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader);173}174 175} // namespace clang::tidy::modernize176