clang-tools 22.0.0git
UseStdMinMaxCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
15using namespace clang::ast_matchers;
16
18
19namespace {
20
21// Ignore if statements that are inside macros.
22AST_MATCHER(IfStmt, isIfInMacro) {
23 return Node.getIfLoc().isMacroID() || Node.getEndLoc().isMacroID();
24}
25
26} // namespace
27
28static const llvm::StringRef AlgorithmHeader("<algorithm>");
29
30static 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
46static 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
62static QualType getNonTemplateAlias(QualType QT) {
63 while (true) {
64 // cast to a TypedefType
65 if (const auto *TT = dyn_cast<TypedefType>(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->desugar();
71 } else {
72 break;
73 }
74 }
75 return QT;
76}
77
78static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs,
79 QualType ComparedType) {
80 const QualType LhsType = CondLhs->getType();
81 const QualType RhsType = CondRhs->getType();
82 const QualType LhsCanonicalType =
83 LhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType();
84 const QualType RhsCanonicalType =
85 RhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType();
86 QualType GlobalImplicitCastType;
87 if (LhsCanonicalType != RhsCanonicalType) {
88 if (llvm::isa<IntegerLiteral>(CondRhs)) {
89 GlobalImplicitCastType = getNonTemplateAlias(LhsType);
90 } else if (llvm::isa<IntegerLiteral>(CondLhs)) {
91 GlobalImplicitCastType = getNonTemplateAlias(RhsType);
92 } else {
93 GlobalImplicitCastType = getNonTemplateAlias(ComparedType);
94 }
95 }
96 return GlobalImplicitCastType;
97}
98
99static std::string
100createReplacement(const Expr *CondLhs, const Expr *CondRhs,
101 const Expr *AssignLhs, const SourceManager &Source,
102 const LangOptions &LO, StringRef FunctionName,
103 const BinaryOperator *BO, StringRef Comment = "") {
104 const llvm::StringRef CondLhsStr = Lexer::getSourceText(
105 Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO);
106 const llvm::StringRef CondRhsStr = Lexer::getSourceText(
107 Source.getExpansionRange(CondRhs->getSourceRange()), Source, LO);
108 const llvm::StringRef AssignLhsStr = Lexer::getSourceText(
109 Source.getExpansionRange(AssignLhs->getSourceRange()), Source, LO);
110
111 const QualType GlobalImplicitCastType =
112 getReplacementCastType(CondLhs, CondRhs, BO->getLHS()->getType());
113
114 return (AssignLhsStr + " = " + FunctionName +
115 (!GlobalImplicitCastType.isNull()
116 ? "<" + GlobalImplicitCastType.getAsString() + ">("
117 : "(") +
118 CondLhsStr + ", " + CondRhsStr + ");" + (Comment.empty() ? "" : " ") +
119 Comment)
120 .str();
121}
122
124 : ClangTidyCheck(Name, Context),
125 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
126 utils::IncludeSorter::IS_LLVM),
127 areDiagsSelfContained()) {}
128
130 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
131}
132
133void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) {
134 auto AssignOperator =
135 binaryOperator(hasOperatorName("="),
136 hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")),
137 hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs")));
138 auto BinaryOperator =
139 binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="),
140 hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")),
141 hasRHS(expr(unless(isTypeDependent())).bind("CondRhs")))
142 .bind("binaryOp");
143 Finder->addMatcher(
144 ifStmt(stmt().bind("if"), unless(isIfInMacro()),
145 unless(hasElse(stmt())), // Ensure `if` has no `else`
146 hasCondition(BinaryOperator),
147 hasThen(
148 anyOf(stmt(AssignOperator),
149 compoundStmt(statementCountIs(1), has(AssignOperator)))),
150 hasParent(stmt(unless(ifStmt(hasElse(
151 equalsBoundNode("if"))))))), // Ensure `if` has no `else if`
152 this);
153}
154
155void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM,
156 Preprocessor *PP,
157 Preprocessor *ModuleExpanderPP) {
158 IncludeInserter.registerPreprocessor(PP);
159}
160
161void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) {
162 const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
163 const clang::LangOptions &LO = Result.Context->getLangOpts();
164 const auto *CondLhs = Result.Nodes.getNodeAs<Expr>("CondLhs");
165 const auto *CondRhs = Result.Nodes.getNodeAs<Expr>("CondRhs");
166 const auto *AssignLhs = Result.Nodes.getNodeAs<Expr>("AssignLhs");
167 const auto *AssignRhs = Result.Nodes.getNodeAs<Expr>("AssignRhs");
168 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("binaryOp");
169 const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode();
170 const SourceLocation IfLocation = If->getIfLoc();
171 const SourceLocation ThenLocation = If->getEndLoc();
172
173 auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) {
174 const SourceManager &Source = *Result.SourceManager;
175 llvm::SmallString<64> Comment;
176
177 const auto AppendNormalized = [&](llvm::StringRef Text) {
178 Text = Text.ltrim();
179 if (!Text.empty()) {
180 if (!Comment.empty())
181 Comment += " ";
182 Comment += Text;
183 }
184 };
185
186 const auto GetSourceText = [&](SourceLocation StartLoc,
187 SourceLocation EndLoc) {
188 return Lexer::getSourceText(
189 CharSourceRange::getCharRange(
190 Lexer::getLocForEndOfToken(StartLoc, 0, Source, LO), EndLoc),
191 Source, LO);
192 };
193
194 // Captures:
195 // if (cond) // Comment A
196 // if (cond) /* Comment A */ { ... }
197 // if (cond) /* Comment A */ x = y;
198 AppendNormalized(
199 GetSourceText(If->getRParenLoc(), If->getThen()->getBeginLoc()));
200
201 if (const auto *CS = dyn_cast<CompoundStmt>(If->getThen())) {
202 const Stmt *Inner = CS->body_front();
203
204 // Captures:
205 // if (cond) { // Comment B
206 // ...
207 // }
208 // if (cond) { /* Comment B */ x = y; }
209 AppendNormalized(GetSourceText(CS->getBeginLoc(), Inner->getBeginLoc()));
210
211 // Captures:
212 // if (cond) { x = y; // Comment C }
213 // if (cond) { x = y; /* Comment C */ }
214 llvm::StringRef PostInner =
215 GetSourceText(Inner->getEndLoc(), CS->getEndLoc());
216
217 // Strip the trailing semicolon to avoid fixes like:
218 // x = std::min(x, y);; // comment
219 const size_t Semi = PostInner.find(';');
220 if (Semi != llvm::StringRef::npos &&
221 PostInner.take_front(Semi).trim().empty()) {
222 PostInner = PostInner.drop_front(Semi + 1);
223 }
224 AppendNormalized(PostInner);
225 }
226
227 diag(IfLocation, "use `%0` instead of `%1`")
228 << FunctionName << BinaryOp->getOpcodeStr()
229 << FixItHint::CreateReplacement(
230 SourceRange(IfLocation, Lexer::getLocForEndOfToken(
231 ThenLocation, 0, Source, LO)),
232 createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO,
233 FunctionName, BinaryOp, Comment))
234 << IncludeInserter.createIncludeInsertion(
235 Source.getFileID(If->getBeginLoc()), AlgorithmHeader);
236 };
237
238 if (minCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
239 (*Result.Context))) {
240 ReplaceAndDiagnose("std::min");
241 } else if (maxCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
242 (*Result.Context))) {
243 ReplaceAndDiagnose("std::max");
244 }
245}
246
247} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context)
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER(BinaryOperator, isRelationalOperator)
static QualType getNonTemplateAlias(QualType QT)
static bool minCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const Expr *AssignRhs, const ASTContext &Context)
static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const SourceManager &Source, const LangOptions &LO, StringRef FunctionName, const BinaryOperator *BO, StringRef Comment="")
static bool maxCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const Expr *AssignRhs, const ASTContext &Context)
static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs, QualType ComparedType)
static const llvm::StringRef AlgorithmHeader("<algorithm>")
bool areStatementsIdentical(const Stmt *FirstStmt, const Stmt *SecondStmt, const ASTContext &Context, bool Canonical)
Definition ASTUtils.cpp:91
llvm::StringMap< ClangTidyValue > OptionMap