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
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::readability {
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
62QualType 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
82static 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
116UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context)
117 : ClangTidyCheck(Name, Context),
118 IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle",
119 Default: utils::IncludeSorter::IS_LLVM),
120 areDiagsSelfContained()) {}
121
122void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
123 Options.store(Options&: Opts, LocalName: "IncludeStyle", Value: IncludeInserter.getStyle());
124}
125
126void 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
148void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM,
149 Preprocessor *PP,
150 Preprocessor *ModuleExpanderPP) {
151 IncludeInserter.registerPreprocessor(PP);
152}
153
154void 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

source code of clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp