| 1 | //===--- UseStdMinMaxCheck.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 "UseStdMinMaxCheck.h" |
| 10 | #include "../utils/ASTUtils.h" |
| 11 | #include "clang/AST/ASTContext.h" |
| 12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 13 | #include "clang/Lex/Preprocessor.h" |
| 14 | |
| 15 | using namespace clang::ast_matchers; |
| 16 | |
| 17 | namespace clang::tidy::readability { |
| 18 | |
| 19 | namespace { |
| 20 | |
| 21 | // Ignore if statements that are inside macros. |
| 22 | AST_MATCHER(IfStmt, isIfInMacro) { |
| 23 | return Node.getIfLoc().isMacroID() || Node.getEndLoc().isMacroID(); |
| 24 | } |
| 25 | |
| 26 | } // namespace |
| 27 | |
| 28 | static const llvm::StringRef ("<algorithm>" ); |
| 29 | |
| 30 | static bool minCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, |
| 31 | const Expr *CondRhs, const Expr *AssignLhs, |
| 32 | const Expr *AssignRhs, const ASTContext &Context) { |
| 33 | if ((Op == BO_LT || Op == BO_LE) && |
| 34 | (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && |
| 35 | tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) |
| 36 | return true; |
| 37 | |
| 38 | if ((Op == BO_GT || Op == BO_GE) && |
| 39 | (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && |
| 40 | tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) |
| 41 | return true; |
| 42 | |
| 43 | return false; |
| 44 | } |
| 45 | |
| 46 | static bool maxCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, |
| 47 | const Expr *CondRhs, const Expr *AssignLhs, |
| 48 | const Expr *AssignRhs, const ASTContext &Context) { |
| 49 | if ((Op == BO_LT || Op == BO_LE) && |
| 50 | (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && |
| 51 | tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) |
| 52 | return true; |
| 53 | |
| 54 | if ((Op == BO_GT || Op == BO_GE) && |
| 55 | (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && |
| 56 | tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) |
| 57 | return true; |
| 58 | |
| 59 | return false; |
| 60 | } |
| 61 | |
| 62 | static QualType getNonTemplateAlias(QualType QT) { |
| 63 | while (true) { |
| 64 | // cast to a TypedefType |
| 65 | if (const TypedefType *TT = dyn_cast<TypedefType>(Val&: QT)) { |
| 66 | // check if the typedef is a template and if it is dependent |
| 67 | if (!TT->getDecl()->getDescribedTemplate() && |
| 68 | !TT->getDecl()->getDeclContext()->isDependentContext()) |
| 69 | return QT; |
| 70 | QT = TT->getDecl()->getUnderlyingType(); |
| 71 | } |
| 72 | // cast to elaborated type |
| 73 | else if (const ElaboratedType *ET = dyn_cast<ElaboratedType>(Val&: QT)) { |
| 74 | QT = ET->getNamedType(); |
| 75 | } else { |
| 76 | break; |
| 77 | } |
| 78 | } |
| 79 | return QT; |
| 80 | } |
| 81 | |
| 82 | static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs, |
| 83 | QualType ComparedType) { |
| 84 | QualType LhsType = CondLhs->getType(); |
| 85 | QualType RhsType = CondRhs->getType(); |
| 86 | QualType LhsCanonicalType = |
| 87 | LhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); |
| 88 | QualType RhsCanonicalType = |
| 89 | RhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); |
| 90 | QualType GlobalImplicitCastType; |
| 91 | if (LhsCanonicalType != RhsCanonicalType) { |
| 92 | if (llvm::isa<IntegerLiteral>(Val: CondRhs)) { |
| 93 | GlobalImplicitCastType = getNonTemplateAlias(QT: LhsType); |
| 94 | } else if (llvm::isa<IntegerLiteral>(Val: CondLhs)) { |
| 95 | GlobalImplicitCastType = getNonTemplateAlias(QT: RhsType); |
| 96 | } else { |
| 97 | GlobalImplicitCastType = getNonTemplateAlias(QT: ComparedType); |
| 98 | } |
| 99 | } |
| 100 | return GlobalImplicitCastType; |
| 101 | } |
| 102 | |
| 103 | static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, |
| 104 | const Expr *AssignLhs, |
| 105 | const SourceManager &Source, |
| 106 | const LangOptions &LO, |
| 107 | StringRef FunctionName, |
| 108 | const BinaryOperator *BO) { |
| 109 | const llvm::StringRef CondLhsStr = Lexer::getSourceText( |
| 110 | Range: Source.getExpansionRange(CondLhs->getSourceRange()), SM: Source, LangOpts: LO); |
| 111 | const llvm::StringRef CondRhsStr = Lexer::getSourceText( |
| 112 | Range: Source.getExpansionRange(CondRhs->getSourceRange()), SM: Source, LangOpts: LO); |
| 113 | const llvm::StringRef AssignLhsStr = Lexer::getSourceText( |
| 114 | Range: Source.getExpansionRange(AssignLhs->getSourceRange()), SM: Source, LangOpts: LO); |
| 115 | |
| 116 | QualType GlobalImplicitCastType = |
| 117 | getReplacementCastType(CondLhs, CondRhs, ComparedType: BO->getLHS()->getType()); |
| 118 | |
| 119 | return (AssignLhsStr + " = " + FunctionName + |
| 120 | (!GlobalImplicitCastType.isNull() |
| 121 | ? "<" + GlobalImplicitCastType.getAsString() + ">(" |
| 122 | : "(" ) + |
| 123 | CondLhsStr + ", " + CondRhsStr + ");" ) |
| 124 | .str(); |
| 125 | } |
| 126 | |
| 127 | UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context) |
| 128 | : ClangTidyCheck(Name, Context), |
| 129 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
| 130 | Default: utils::IncludeSorter::IS_LLVM), |
| 131 | areDiagsSelfContained()) {} |
| 132 | |
| 133 | void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 134 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
| 135 | } |
| 136 | |
| 137 | void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) { |
| 138 | auto AssignOperator = |
| 139 | binaryOperator(hasOperatorName(Name: "=" ), |
| 140 | hasLHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "AssignLhs" )), |
| 141 | hasRHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "AssignRhs" ))); |
| 142 | auto BinaryOperator = |
| 143 | binaryOperator(hasAnyOperatorName("<" , ">" , "<=" , ">=" ), |
| 144 | hasLHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "CondLhs" )), |
| 145 | hasRHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "CondRhs" ))) |
| 146 | .bind(ID: "binaryOp" ); |
| 147 | Finder->addMatcher( |
| 148 | NodeMatch: ifStmt(stmt().bind(ID: "if" ), unless(isIfInMacro()), |
| 149 | unless(hasElse(InnerMatcher: stmt())), // Ensure `if` has no `else` |
| 150 | hasCondition(InnerMatcher: BinaryOperator), |
| 151 | hasThen( |
| 152 | InnerMatcher: anyOf(stmt(AssignOperator), |
| 153 | compoundStmt(statementCountIs(N: 1), has(AssignOperator)))), |
| 154 | hasParent(stmt(unless(ifStmt(hasElse( |
| 155 | InnerMatcher: equalsBoundNode(ID: "if" ))))))), // Ensure `if` has no `else if` |
| 156 | Action: this); |
| 157 | } |
| 158 | |
| 159 | void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM, |
| 160 | Preprocessor *PP, |
| 161 | Preprocessor *ModuleExpanderPP) { |
| 162 | IncludeInserter.registerPreprocessor(PP); |
| 163 | } |
| 164 | |
| 165 | void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) { |
| 166 | const auto *If = Result.Nodes.getNodeAs<IfStmt>(ID: "if" ); |
| 167 | const clang::LangOptions &LO = Result.Context->getLangOpts(); |
| 168 | const auto *CondLhs = Result.Nodes.getNodeAs<Expr>(ID: "CondLhs" ); |
| 169 | const auto *CondRhs = Result.Nodes.getNodeAs<Expr>(ID: "CondRhs" ); |
| 170 | const auto *AssignLhs = Result.Nodes.getNodeAs<Expr>(ID: "AssignLhs" ); |
| 171 | const auto *AssignRhs = Result.Nodes.getNodeAs<Expr>(ID: "AssignRhs" ); |
| 172 | const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(ID: "binaryOp" ); |
| 173 | const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode(); |
| 174 | const SourceLocation IfLocation = If->getIfLoc(); |
| 175 | const SourceLocation ThenLocation = If->getEndLoc(); |
| 176 | |
| 177 | auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) { |
| 178 | const SourceManager &Source = *Result.SourceManager; |
| 179 | diag(Loc: IfLocation, Description: "use `%0` instead of `%1`" ) |
| 180 | << FunctionName << BinaryOp->getOpcodeStr() |
| 181 | << FixItHint::CreateReplacement( |
| 182 | RemoveRange: SourceRange(IfLocation, Lexer::getLocForEndOfToken( |
| 183 | Loc: ThenLocation, Offset: 0, SM: Source, LangOpts: LO)), |
| 184 | Code: createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO, |
| 185 | FunctionName, BO: BinaryOp)) |
| 186 | << IncludeInserter.createIncludeInsertion( |
| 187 | FileID: Source.getFileID(SpellingLoc: If->getBeginLoc()), Header: AlgorithmHeader); |
| 188 | }; |
| 189 | |
| 190 | if (minCondition(Op: BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, |
| 191 | Context: (*Result.Context))) { |
| 192 | ReplaceAndDiagnose("std::min" ); |
| 193 | } else if (maxCondition(Op: BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, |
| 194 | Context: (*Result.Context))) { |
| 195 | ReplaceAndDiagnose("std::max" ); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | } // namespace clang::tidy::readability |
| 200 | |