//===--- UseIntegerSignComparisonCheck.cpp - clang-tidy -------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "UseIntegerSignComparisonCheck.h" #include "clang/AST/Expr.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; using namespace clang::ast_matchers::internal; namespace clang::tidy::modernize { /// Find if the passed type is the actual "char" type, /// not applicable to explicit "signed char" or "unsigned char" types. static bool isActualCharType(const clang::QualType &Ty) { using namespace clang; const Type *DesugaredType = Ty->getUnqualifiedDesugaredType(); if (const auto *BT = llvm::dyn_cast(DesugaredType)) return (BT->getKind() == BuiltinType::Char_U || BT->getKind() == BuiltinType::Char_S); return false; } namespace { AST_MATCHER(clang::QualType, isActualChar) { return clang::tidy::modernize::isActualCharType(Node); } } // namespace static BindableMatcher intCastExpression(bool IsSigned, const std::string &CastBindName = std::string()) { // std::cmp_{} functions trigger a compile-time error if either LHS or RHS // is a non-integer type, char, enum or bool // (unsigned char/ signed char are Ok and can be used). auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType( isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(), unless(isActualChar()), unless(booleanType()), unless(enumType()))))); const auto ImplicitCastExpr = CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr)) : implicitCastExpr(hasSourceExpression(IntTypeExpr)) .bind(CastBindName); const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr)); const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr)); const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr)); return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr, FunctionalCastExpr)); } static StringRef parseOpCode(BinaryOperator::Opcode Code) { switch (Code) { case BO_LT: return "cmp_less"; case BO_GT: return "cmp_greater"; case BO_LE: return "cmp_less_equal"; case BO_GE: return "cmp_greater_equal"; case BO_EQ: return "cmp_equal"; case BO_NE: return "cmp_not_equal"; default: return ""; } } UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", utils::IncludeSorter::IS_LLVM), areDiagsSelfContained()), EnableQtSupport(Options.get("EnableQtSupport", false)) {} void UseIntegerSignComparisonCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); Options.store(Opts, "EnableQtSupport", EnableQtSupport); } void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) { const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression"); const auto UnSignedIntCastExpr = intCastExpression(false); // Flag all operators "==", "<=", ">=", "<", ">", "!=" // that are used between signed/unsigned const auto CompareOperator = binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="), hasOperands(SignedIntCastExpr, UnSignedIntCastExpr), unless(isInTemplateInstantiation())) .bind("intComparison"); Finder->addMatcher(CompareOperator, this); } void UseIntegerSignComparisonCheck::registerPPCallbacks( const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { IncludeInserter.registerPreprocessor(PP); } void UseIntegerSignComparisonCheck::check( const MatchFinder::MatchResult &Result) { const auto *SignedCastExpression = Result.Nodes.getNodeAs("sIntCastExpression"); assert(SignedCastExpression); // Ignore the match if we know that the signed int value is not negative. Expr::EvalResult EVResult; if (!SignedCastExpression->isValueDependent() && SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, *Result.Context)) { const llvm::APSInt SValue = EVResult.Val.getInt(); if (SValue.isNonNegative()) return; } const auto *BinaryOp = Result.Nodes.getNodeAs("intComparison"); if (BinaryOp == nullptr) return; const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode(); const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts(); const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts(); if (LHS == nullptr || RHS == nullptr) return; const Expr *SubExprLHS = nullptr; const Expr *SubExprRHS = nullptr; SourceRange R1 = SourceRange(LHS->getBeginLoc()); SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc()); SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken( RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts())); if (const auto *LHSCast = llvm::dyn_cast(LHS)) { SubExprLHS = LHSCast->getSubExpr(); R1 = SourceRange(LHS->getBeginLoc(), SubExprLHS->getBeginLoc().getLocWithOffset(-1)); R2.setBegin(Lexer::getLocForEndOfToken( SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts())); } if (const auto *RHSCast = llvm::dyn_cast(RHS)) { SubExprRHS = RHSCast->getSubExpr(); R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1)); } DiagnosticBuilder Diag = diag(BinaryOp->getBeginLoc(), "comparison between 'signed' and 'unsigned' integers"); std::string CmpNamespace; llvm::StringRef CmpHeader; if (getLangOpts().CPlusPlus20) { CmpHeader = ""; CmpNamespace = llvm::Twine("std::" + parseOpCode(OpCode)).str(); } else if (getLangOpts().CPlusPlus17 && EnableQtSupport) { CmpHeader = ""; CmpNamespace = llvm::Twine("q20::" + parseOpCode(OpCode)).str(); } // Prefer modernize-use-integer-sign-comparison when C++20 is available! Diag << FixItHint::CreateReplacement( CharSourceRange(R1, SubExprLHS != nullptr), llvm::Twine(CmpNamespace + "(").str()); Diag << FixItHint::CreateReplacement(R2, ","); Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")"); // If there is no include for cmp_{*} functions, we'll add it. Diag << IncludeInserter.createIncludeInsertion( Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader); } } // namespace clang::tidy::modernize