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 | 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 std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, |
83 | const Expr *AssignLhs, |
84 | const SourceManager &Source, |
85 | const LangOptions &LO, |
86 | StringRef FunctionName, |
87 | const BinaryOperator *BO) { |
88 | const llvm::StringRef CondLhsStr = Lexer::getSourceText( |
89 | Range: Source.getExpansionRange(CondLhs->getSourceRange()), SM: Source, LangOpts: LO); |
90 | const llvm::StringRef CondRhsStr = Lexer::getSourceText( |
91 | Range: Source.getExpansionRange(CondRhs->getSourceRange()), SM: Source, LangOpts: LO); |
92 | const llvm::StringRef AssignLhsStr = Lexer::getSourceText( |
93 | Range: Source.getExpansionRange(AssignLhs->getSourceRange()), SM: Source, LangOpts: LO); |
94 | |
95 | clang::QualType GlobalImplicitCastType; |
96 | clang::QualType LhsType = CondLhs->getType() |
97 | .getCanonicalType() |
98 | .getNonReferenceType() |
99 | .getUnqualifiedType(); |
100 | clang::QualType RhsType = CondRhs->getType() |
101 | .getCanonicalType() |
102 | .getNonReferenceType() |
103 | .getUnqualifiedType(); |
104 | if (LhsType != RhsType) { |
105 | GlobalImplicitCastType = getNonTemplateAlias(QT: BO->getLHS()->getType()); |
106 | } |
107 | |
108 | return (AssignLhsStr + " = " + FunctionName + |
109 | (!GlobalImplicitCastType.isNull() |
110 | ? "<" + GlobalImplicitCastType.getAsString() + ">(" |
111 | : "(" ) + |
112 | CondLhsStr + ", " + CondRhsStr + ");" ) |
113 | .str(); |
114 | } |
115 | |
116 | UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context) |
117 | : ClangTidyCheck(Name, Context), |
118 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
119 | Default: utils::IncludeSorter::IS_LLVM), |
120 | areDiagsSelfContained()) {} |
121 | |
122 | void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
123 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
124 | } |
125 | |
126 | void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) { |
127 | auto AssignOperator = |
128 | binaryOperator(hasOperatorName(Name: "=" ), |
129 | hasLHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "AssignLhs" )), |
130 | hasRHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "AssignRhs" ))); |
131 | auto BinaryOperator = |
132 | binaryOperator(hasAnyOperatorName("<" , ">" , "<=" , ">=" ), |
133 | hasLHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "CondLhs" )), |
134 | hasRHS(InnerMatcher: expr(unless(isTypeDependent())).bind(ID: "CondRhs" ))) |
135 | .bind(ID: "binaryOp" ); |
136 | Finder->addMatcher( |
137 | NodeMatch: ifStmt(stmt().bind(ID: "if" ), unless(isIfInMacro()), |
138 | unless(hasElse(InnerMatcher: stmt())), // Ensure `if` has no `else` |
139 | hasCondition(InnerMatcher: BinaryOperator), |
140 | hasThen( |
141 | InnerMatcher: anyOf(stmt(AssignOperator), |
142 | compoundStmt(statementCountIs(N: 1), has(AssignOperator)))), |
143 | hasParent(stmt(unless(ifStmt(hasElse( |
144 | InnerMatcher: equalsBoundNode(ID: "if" ))))))), // Ensure `if` has no `else if` |
145 | Action: this); |
146 | } |
147 | |
148 | void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM, |
149 | Preprocessor *PP, |
150 | Preprocessor *ModuleExpanderPP) { |
151 | IncludeInserter.registerPreprocessor(PP); |
152 | } |
153 | |
154 | void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) { |
155 | const auto *If = Result.Nodes.getNodeAs<IfStmt>(ID: "if" ); |
156 | const clang::LangOptions &LO = Result.Context->getLangOpts(); |
157 | const auto *CondLhs = Result.Nodes.getNodeAs<Expr>(ID: "CondLhs" ); |
158 | const auto *CondRhs = Result.Nodes.getNodeAs<Expr>(ID: "CondRhs" ); |
159 | const auto *AssignLhs = Result.Nodes.getNodeAs<Expr>(ID: "AssignLhs" ); |
160 | const auto *AssignRhs = Result.Nodes.getNodeAs<Expr>(ID: "AssignRhs" ); |
161 | const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(ID: "binaryOp" ); |
162 | const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode(); |
163 | const SourceLocation IfLocation = If->getIfLoc(); |
164 | const SourceLocation ThenLocation = If->getEndLoc(); |
165 | |
166 | auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) { |
167 | const SourceManager &Source = *Result.SourceManager; |
168 | diag(Loc: IfLocation, Description: "use `%0` instead of `%1`" ) |
169 | << FunctionName << BinaryOp->getOpcodeStr() |
170 | << FixItHint::CreateReplacement( |
171 | RemoveRange: SourceRange(IfLocation, Lexer::getLocForEndOfToken( |
172 | Loc: ThenLocation, Offset: 0, SM: Source, LangOpts: LO)), |
173 | Code: createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO, |
174 | FunctionName, BO: BinaryOp)) |
175 | << IncludeInserter.createIncludeInsertion( |
176 | FileID: Source.getFileID(SpellingLoc: If->getBeginLoc()), Header: AlgorithmHeader); |
177 | }; |
178 | |
179 | if (minCondition(Op: BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, |
180 | Context: (*Result.Context))) { |
181 | ReplaceAndDiagnose("std::min" ); |
182 | } else if (maxCondition(Op: BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, |
183 | Context: (*Result.Context))) { |
184 | ReplaceAndDiagnose("std::max" ); |
185 | } |
186 | } |
187 | |
188 | } // namespace clang::tidy::readability |
189 | |