//===--- UseStdMinMaxCheck.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 "UseStdMinMaxCheck.h" #include "../utils/ASTUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Preprocessor.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { namespace { // Ignore if statements that are inside macros. AST_MATCHER(IfStmt, isIfInMacro) { return Node.getIfLoc().isMacroID() || Node.getEndLoc().isMacroID(); } } // namespace static const llvm::StringRef AlgorithmHeader(""); static bool minCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const Expr *AssignRhs, const ASTContext &Context) { if ((Op == BO_LT || Op == BO_LE) && (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) return true; if ((Op == BO_GT || Op == BO_GE) && (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) return true; return false; } static bool maxCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const Expr *AssignRhs, const ASTContext &Context) { if ((Op == BO_LT || Op == BO_LE) && (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) return true; if ((Op == BO_GT || Op == BO_GE) && (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) return true; return false; } static QualType getNonTemplateAlias(QualType QT) { while (true) { // cast to a TypedefType if (const TypedefType *TT = dyn_cast(QT)) { // check if the typedef is a template and if it is dependent if (!TT->getDecl()->getDescribedTemplate() && !TT->getDecl()->getDeclContext()->isDependentContext()) return QT; QT = TT->getDecl()->getUnderlyingType(); } // cast to elaborated type else if (const ElaboratedType *ET = dyn_cast(QT)) { QT = ET->getNamedType(); } else { break; } } return QT; } static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs, QualType ComparedType) { QualType LhsType = CondLhs->getType(); QualType RhsType = CondRhs->getType(); QualType LhsCanonicalType = LhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); QualType RhsCanonicalType = RhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); QualType GlobalImplicitCastType; if (LhsCanonicalType != RhsCanonicalType) { if (llvm::isa(CondRhs)) { GlobalImplicitCastType = getNonTemplateAlias(LhsType); } else if (llvm::isa(CondLhs)) { GlobalImplicitCastType = getNonTemplateAlias(RhsType); } else { GlobalImplicitCastType = getNonTemplateAlias(ComparedType); } } return GlobalImplicitCastType; } static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const SourceManager &Source, const LangOptions &LO, StringRef FunctionName, const BinaryOperator *BO) { const llvm::StringRef CondLhsStr = Lexer::getSourceText( Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO); const llvm::StringRef CondRhsStr = Lexer::getSourceText( Source.getExpansionRange(CondRhs->getSourceRange()), Source, LO); const llvm::StringRef AssignLhsStr = Lexer::getSourceText( Source.getExpansionRange(AssignLhs->getSourceRange()), Source, LO); QualType GlobalImplicitCastType = getReplacementCastType(CondLhs, CondRhs, BO->getLHS()->getType()); return (AssignLhsStr + " = " + FunctionName + (!GlobalImplicitCastType.isNull() ? "<" + GlobalImplicitCastType.getAsString() + ">(" : "(") + CondLhsStr + ", " + CondRhsStr + ");") .str(); } UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", utils::IncludeSorter::IS_LLVM), areDiagsSelfContained()) {} void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); } void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) { auto AssignOperator = binaryOperator(hasOperatorName("="), hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")), hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs"))); auto BinaryOperator = binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="), hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")), hasRHS(expr(unless(isTypeDependent())).bind("CondRhs"))) .bind("binaryOp"); Finder->addMatcher( ifStmt(stmt().bind("if"), unless(isIfInMacro()), unless(hasElse(stmt())), // Ensure `if` has no `else` hasCondition(BinaryOperator), hasThen( anyOf(stmt(AssignOperator), compoundStmt(statementCountIs(1), has(AssignOperator)))), hasParent(stmt(unless(ifStmt(hasElse( equalsBoundNode("if"))))))), // Ensure `if` has no `else if` this); } void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { IncludeInserter.registerPreprocessor(PP); } void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) { const auto *If = Result.Nodes.getNodeAs("if"); const clang::LangOptions &LO = Result.Context->getLangOpts(); const auto *CondLhs = Result.Nodes.getNodeAs("CondLhs"); const auto *CondRhs = Result.Nodes.getNodeAs("CondRhs"); const auto *AssignLhs = Result.Nodes.getNodeAs("AssignLhs"); const auto *AssignRhs = Result.Nodes.getNodeAs("AssignRhs"); const auto *BinaryOp = Result.Nodes.getNodeAs("binaryOp"); const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode(); const SourceLocation IfLocation = If->getIfLoc(); const SourceLocation ThenLocation = If->getEndLoc(); auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) { const SourceManager &Source = *Result.SourceManager; diag(IfLocation, "use `%0` instead of `%1`") << FunctionName << BinaryOp->getOpcodeStr() << FixItHint::CreateReplacement( SourceRange(IfLocation, Lexer::getLocForEndOfToken( ThenLocation, 0, Source, LO)), createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO, FunctionName, BinaryOp)) << IncludeInserter.createIncludeInsertion( Source.getFileID(If->getBeginLoc()), AlgorithmHeader); }; if (minCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, (*Result.Context))) { ReplaceAndDiagnose("std::min"); } else if (maxCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, (*Result.Context))) { ReplaceAndDiagnose("std::max"); } } } // namespace clang::tidy::readability