xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseIntegerSignComparisonCheck.cpp (revision aa580c2ec5eb4217c945a47a561181be7e7b1032)
18b63bfbfSqt-tatiana //===--- UseIntegerSignComparisonCheck.cpp - clang-tidy -------------------===//
28b63bfbfSqt-tatiana //
38b63bfbfSqt-tatiana // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
48b63bfbfSqt-tatiana // See https://llvm.org/LICENSE.txt for license information.
58b63bfbfSqt-tatiana // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
68b63bfbfSqt-tatiana //
78b63bfbfSqt-tatiana //===----------------------------------------------------------------------===//
88b63bfbfSqt-tatiana 
98b63bfbfSqt-tatiana #include "UseIntegerSignComparisonCheck.h"
108b63bfbfSqt-tatiana #include "clang/AST/Expr.h"
118b63bfbfSqt-tatiana #include "clang/ASTMatchers/ASTMatchFinder.h"
128b63bfbfSqt-tatiana #include "clang/Lex/Lexer.h"
138b63bfbfSqt-tatiana 
148b63bfbfSqt-tatiana using namespace clang::ast_matchers;
158b63bfbfSqt-tatiana using namespace clang::ast_matchers::internal;
168b63bfbfSqt-tatiana 
178b63bfbfSqt-tatiana namespace clang::tidy::modernize {
188b63bfbfSqt-tatiana 
198b63bfbfSqt-tatiana /// Find if the passed type is the actual "char" type,
208b63bfbfSqt-tatiana /// not applicable to explicit "signed char" or "unsigned char" types.
218b63bfbfSqt-tatiana static bool isActualCharType(const clang::QualType &Ty) {
228b63bfbfSqt-tatiana   using namespace clang;
238b63bfbfSqt-tatiana   const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
248b63bfbfSqt-tatiana   if (const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType))
258b63bfbfSqt-tatiana     return (BT->getKind() == BuiltinType::Char_U ||
268b63bfbfSqt-tatiana             BT->getKind() == BuiltinType::Char_S);
278b63bfbfSqt-tatiana   return false;
288b63bfbfSqt-tatiana }
298b63bfbfSqt-tatiana 
308b63bfbfSqt-tatiana namespace {
318b63bfbfSqt-tatiana AST_MATCHER(clang::QualType, isActualChar) {
328b63bfbfSqt-tatiana   return clang::tidy::modernize::isActualCharType(Node);
338b63bfbfSqt-tatiana }
348b63bfbfSqt-tatiana } // namespace
358b63bfbfSqt-tatiana 
368b63bfbfSqt-tatiana static BindableMatcher<clang::Stmt>
378b63bfbfSqt-tatiana intCastExpression(bool IsSigned,
388b63bfbfSqt-tatiana                   const std::string &CastBindName = std::string()) {
398b63bfbfSqt-tatiana   // std::cmp_{} functions trigger a compile-time error if either LHS or RHS
408b63bfbfSqt-tatiana   // is a non-integer type, char, enum or bool
418b63bfbfSqt-tatiana   // (unsigned char/ signed char are Ok and can be used).
428b63bfbfSqt-tatiana   auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(
438b63bfbfSqt-tatiana       isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
448b63bfbfSqt-tatiana       unless(isActualChar()), unless(booleanType()), unless(enumType())))));
458b63bfbfSqt-tatiana 
468b63bfbfSqt-tatiana   const auto ImplicitCastExpr =
478b63bfbfSqt-tatiana       CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))
488b63bfbfSqt-tatiana                            : implicitCastExpr(hasSourceExpression(IntTypeExpr))
498b63bfbfSqt-tatiana                                  .bind(CastBindName);
508b63bfbfSqt-tatiana 
518b63bfbfSqt-tatiana   const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
528b63bfbfSqt-tatiana   const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
538b63bfbfSqt-tatiana   const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
548b63bfbfSqt-tatiana 
558b63bfbfSqt-tatiana   return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
568b63bfbfSqt-tatiana                     FunctionalCastExpr));
578b63bfbfSqt-tatiana }
588b63bfbfSqt-tatiana 
598b63bfbfSqt-tatiana static StringRef parseOpCode(BinaryOperator::Opcode Code) {
608b63bfbfSqt-tatiana   switch (Code) {
618b63bfbfSqt-tatiana   case BO_LT:
628b63bfbfSqt-tatiana     return "cmp_less";
638b63bfbfSqt-tatiana   case BO_GT:
648b63bfbfSqt-tatiana     return "cmp_greater";
658b63bfbfSqt-tatiana   case BO_LE:
668b63bfbfSqt-tatiana     return "cmp_less_equal";
678b63bfbfSqt-tatiana   case BO_GE:
688b63bfbfSqt-tatiana     return "cmp_greater_equal";
698b63bfbfSqt-tatiana   case BO_EQ:
708b63bfbfSqt-tatiana     return "cmp_equal";
718b63bfbfSqt-tatiana   case BO_NE:
728b63bfbfSqt-tatiana     return "cmp_not_equal";
738b63bfbfSqt-tatiana   default:
748b63bfbfSqt-tatiana     return "";
758b63bfbfSqt-tatiana   }
768b63bfbfSqt-tatiana }
778b63bfbfSqt-tatiana 
788b63bfbfSqt-tatiana UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck(
798b63bfbfSqt-tatiana     StringRef Name, ClangTidyContext *Context)
808b63bfbfSqt-tatiana     : ClangTidyCheck(Name, Context),
818b63bfbfSqt-tatiana       IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
828b63bfbfSqt-tatiana                                                utils::IncludeSorter::IS_LLVM),
83*aa580c2eSqt-tatiana                       areDiagsSelfContained()),
84*aa580c2eSqt-tatiana       EnableQtSupport(Options.get("EnableQtSupport", false)) {}
858b63bfbfSqt-tatiana 
868b63bfbfSqt-tatiana void UseIntegerSignComparisonCheck::storeOptions(
878b63bfbfSqt-tatiana     ClangTidyOptions::OptionMap &Opts) {
888b63bfbfSqt-tatiana   Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
89*aa580c2eSqt-tatiana   Options.store(Opts, "EnableQtSupport", EnableQtSupport);
908b63bfbfSqt-tatiana }
918b63bfbfSqt-tatiana 
928b63bfbfSqt-tatiana void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {
938b63bfbfSqt-tatiana   const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression");
948b63bfbfSqt-tatiana   const auto UnSignedIntCastExpr = intCastExpression(false);
958b63bfbfSqt-tatiana 
968b63bfbfSqt-tatiana   // Flag all operators "==", "<=", ">=", "<", ">", "!="
978b63bfbfSqt-tatiana   // that are used between signed/unsigned
988b63bfbfSqt-tatiana   const auto CompareOperator =
998b63bfbfSqt-tatiana       binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),
1008b63bfbfSqt-tatiana                      hasOperands(SignedIntCastExpr, UnSignedIntCastExpr),
1018b63bfbfSqt-tatiana                      unless(isInTemplateInstantiation()))
1028b63bfbfSqt-tatiana           .bind("intComparison");
1038b63bfbfSqt-tatiana 
1048b63bfbfSqt-tatiana   Finder->addMatcher(CompareOperator, this);
1058b63bfbfSqt-tatiana }
1068b63bfbfSqt-tatiana 
1078b63bfbfSqt-tatiana void UseIntegerSignComparisonCheck::registerPPCallbacks(
1088b63bfbfSqt-tatiana     const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
1098b63bfbfSqt-tatiana   IncludeInserter.registerPreprocessor(PP);
1108b63bfbfSqt-tatiana }
1118b63bfbfSqt-tatiana 
1128b63bfbfSqt-tatiana void UseIntegerSignComparisonCheck::check(
1138b63bfbfSqt-tatiana     const MatchFinder::MatchResult &Result) {
1148b63bfbfSqt-tatiana   const auto *SignedCastExpression =
1158b63bfbfSqt-tatiana       Result.Nodes.getNodeAs<ImplicitCastExpr>("sIntCastExpression");
1168b63bfbfSqt-tatiana   assert(SignedCastExpression);
1178b63bfbfSqt-tatiana 
1188b63bfbfSqt-tatiana   // Ignore the match if we know that the signed int value is not negative.
1198b63bfbfSqt-tatiana   Expr::EvalResult EVResult;
1208b63bfbfSqt-tatiana   if (!SignedCastExpression->isValueDependent() &&
1218b63bfbfSqt-tatiana       SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
1228b63bfbfSqt-tatiana                                                         *Result.Context)) {
1238b63bfbfSqt-tatiana     const llvm::APSInt SValue = EVResult.Val.getInt();
1248b63bfbfSqt-tatiana     if (SValue.isNonNegative())
1258b63bfbfSqt-tatiana       return;
1268b63bfbfSqt-tatiana   }
1278b63bfbfSqt-tatiana 
1288b63bfbfSqt-tatiana   const auto *BinaryOp =
1298b63bfbfSqt-tatiana       Result.Nodes.getNodeAs<BinaryOperator>("intComparison");
1308b63bfbfSqt-tatiana   if (BinaryOp == nullptr)
1318b63bfbfSqt-tatiana     return;
1328b63bfbfSqt-tatiana 
1338b63bfbfSqt-tatiana   const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode();
1348b63bfbfSqt-tatiana 
1358b63bfbfSqt-tatiana   const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();
1368b63bfbfSqt-tatiana   const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();
1378b63bfbfSqt-tatiana   if (LHS == nullptr || RHS == nullptr)
1388b63bfbfSqt-tatiana     return;
1398b63bfbfSqt-tatiana   const Expr *SubExprLHS = nullptr;
1408b63bfbfSqt-tatiana   const Expr *SubExprRHS = nullptr;
1418b63bfbfSqt-tatiana   SourceRange R1 = SourceRange(LHS->getBeginLoc());
1428b63bfbfSqt-tatiana   SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc());
1438b63bfbfSqt-tatiana   SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken(
1448b63bfbfSqt-tatiana       RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
1458b63bfbfSqt-tatiana   if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(LHS)) {
1468b63bfbfSqt-tatiana     SubExprLHS = LHSCast->getSubExpr();
1478b63bfbfSqt-tatiana     R1 = SourceRange(LHS->getBeginLoc(),
1488b63bfbfSqt-tatiana                      SubExprLHS->getBeginLoc().getLocWithOffset(-1));
1498b63bfbfSqt-tatiana     R2.setBegin(Lexer::getLocForEndOfToken(
1508b63bfbfSqt-tatiana         SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
1518b63bfbfSqt-tatiana   }
1528b63bfbfSqt-tatiana   if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(RHS)) {
1538b63bfbfSqt-tatiana     SubExprRHS = RHSCast->getSubExpr();
1548b63bfbfSqt-tatiana     R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));
1558b63bfbfSqt-tatiana   }
1568b63bfbfSqt-tatiana   DiagnosticBuilder Diag =
1578b63bfbfSqt-tatiana       diag(BinaryOp->getBeginLoc(),
1588b63bfbfSqt-tatiana            "comparison between 'signed' and 'unsigned' integers");
159*aa580c2eSqt-tatiana   std::string CmpNamespace;
160*aa580c2eSqt-tatiana   llvm::StringRef CmpHeader;
161*aa580c2eSqt-tatiana 
162*aa580c2eSqt-tatiana   if (getLangOpts().CPlusPlus20) {
163*aa580c2eSqt-tatiana     CmpHeader = "<utility>";
164*aa580c2eSqt-tatiana     CmpNamespace = llvm::Twine("std::" + parseOpCode(OpCode)).str();
165*aa580c2eSqt-tatiana   } else if (getLangOpts().CPlusPlus17 && EnableQtSupport) {
166*aa580c2eSqt-tatiana     CmpHeader = "<QtCore/q20utility.h>";
167*aa580c2eSqt-tatiana     CmpNamespace = llvm::Twine("q20::" + parseOpCode(OpCode)).str();
168*aa580c2eSqt-tatiana   }
169*aa580c2eSqt-tatiana 
1708b63bfbfSqt-tatiana   // Prefer modernize-use-integer-sign-comparison when C++20 is available!
1718b63bfbfSqt-tatiana   Diag << FixItHint::CreateReplacement(
1728b63bfbfSqt-tatiana       CharSourceRange(R1, SubExprLHS != nullptr),
1738b63bfbfSqt-tatiana       llvm::Twine(CmpNamespace + "(").str());
1748b63bfbfSqt-tatiana   Diag << FixItHint::CreateReplacement(R2, ",");
1758b63bfbfSqt-tatiana   Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")");
1768b63bfbfSqt-tatiana 
1778b63bfbfSqt-tatiana   // If there is no include for cmp_{*} functions, we'll add it.
1788b63bfbfSqt-tatiana   Diag << IncludeInserter.createIncludeInsertion(
1798b63bfbfSqt-tatiana       Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader);
1808b63bfbfSqt-tatiana }
1818b63bfbfSqt-tatiana 
1828b63bfbfSqt-tatiana } // namespace clang::tidy::modernize
183