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