clang-tools 20.0.0git
UseStdMinMaxCheck.cpp
Go to the documentation of this file.
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
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 TypedefType *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->getDecl()->getUnderlyingType();
71 }
72 // cast to elaborated type
73 else if (const ElaboratedType *ET = dyn_cast<ElaboratedType>(QT)) {
74 QT = ET->getNamedType();
75 } else {
76 break;
77 }
78 }
79 return QT;
80}
81
82static 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>(CondRhs)) {
93 GlobalImplicitCastType = getNonTemplateAlias(LhsType);
94 } else if (llvm::isa<IntegerLiteral>(CondLhs)) {
95 GlobalImplicitCastType = getNonTemplateAlias(RhsType);
96 } else {
97 GlobalImplicitCastType = getNonTemplateAlias(ComparedType);
98 }
99 }
100 return GlobalImplicitCastType;
101}
102
103static 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 Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO);
111 const llvm::StringRef CondRhsStr = Lexer::getSourceText(
112 Source.getExpansionRange(CondRhs->getSourceRange()), Source, LO);
113 const llvm::StringRef AssignLhsStr = Lexer::getSourceText(
114 Source.getExpansionRange(AssignLhs->getSourceRange()), Source, LO);
115
116 QualType GlobalImplicitCastType =
117 getReplacementCastType(CondLhs, CondRhs, BO->getLHS()->getType());
118
119 return (AssignLhsStr + " = " + FunctionName +
120 (!GlobalImplicitCastType.isNull()
121 ? "<" + GlobalImplicitCastType.getAsString() + ">("
122 : "(") +
123 CondLhsStr + ", " + CondRhsStr + ");")
124 .str();
125}
126
128 : ClangTidyCheck(Name, Context),
129 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
130 utils::IncludeSorter::IS_LLVM),
131 areDiagsSelfContained()) {}
132
134 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
135}
136
137void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) {
138 auto AssignOperator =
139 binaryOperator(hasOperatorName("="),
140 hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")),
141 hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs")));
142 auto BinaryOperator =
143 binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="),
144 hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")),
145 hasRHS(expr(unless(isTypeDependent())).bind("CondRhs")))
146 .bind("binaryOp");
147 Finder->addMatcher(
148 ifStmt(stmt().bind("if"), unless(isIfInMacro()),
149 unless(hasElse(stmt())), // Ensure `if` has no `else`
150 hasCondition(BinaryOperator),
151 hasThen(
152 anyOf(stmt(AssignOperator),
153 compoundStmt(statementCountIs(1), has(AssignOperator)))),
154 hasParent(stmt(unless(ifStmt(hasElse(
155 equalsBoundNode("if"))))))), // Ensure `if` has no `else if`
156 this);
157}
158
159void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM,
160 Preprocessor *PP,
161 Preprocessor *ModuleExpanderPP) {
162 IncludeInserter.registerPreprocessor(PP);
163}
164
165void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) {
166 const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
167 const clang::LangOptions &LO = Result.Context->getLangOpts();
168 const auto *CondLhs = Result.Nodes.getNodeAs<Expr>("CondLhs");
169 const auto *CondRhs = Result.Nodes.getNodeAs<Expr>("CondRhs");
170 const auto *AssignLhs = Result.Nodes.getNodeAs<Expr>("AssignLhs");
171 const auto *AssignRhs = Result.Nodes.getNodeAs<Expr>("AssignRhs");
172 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("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(IfLocation, "use `%0` instead of `%1`")
180 << FunctionName << BinaryOp->getOpcodeStr()
181 << FixItHint::CreateReplacement(
182 SourceRange(IfLocation, Lexer::getLocForEndOfToken(
183 ThenLocation, 0, Source, LO)),
184 createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO,
185 FunctionName, BinaryOp))
186 << IncludeInserter.createIncludeInsertion(
187 Source.getFileID(If->getBeginLoc()), AlgorithmHeader);
188 };
189
190 if (minCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
191 (*Result.Context))) {
192 ReplaceAndDiagnose("std::min");
193 } else if (maxCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs,
194 (*Result.Context))) {
195 ReplaceAndDiagnose("std::max");
196 }
197}
198
199} // namespace clang::tidy::readability
llvm::SmallString< 256U > Name
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
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
Override this to register PPCallbacks in the preprocessor.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerPreprocessor(Preprocessor *PP)
Registers this with the Preprocessor PP, must be called before this class is used.
std::optional< FixItHint > createIncludeInsertion(FileID FileID, llvm::StringRef Header)
Creates a Header inclusion directive fixit in the File FileID.
IncludeSorter::IncludeStyle getStyle() const
static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, const Expr *AssignLhs, const SourceManager &Source, const LangOptions &LO, StringRef FunctionName, const BinaryOperator *BO)
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 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