clang-tools 19.0.0git
MinMaxUseInitializerListCheck.cpp
Go to the documentation of this file.
1//===--- MinMaxUseInitializerListCheck.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
10#include "../utils/ASTUtils.h"
11#include "../utils/LexerUtils.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Frontend/CompilerInstance.h"
14#include "clang/Lex/Lexer.h"
15
16using namespace clang;
17
18namespace {
19
20struct FindArgsResult {
21 const Expr *First;
22 const Expr *Last;
23 const Expr *Compare;
24 SmallVector<const clang::Expr *, 2> Args;
25};
26
27} // anonymous namespace
28
29using namespace clang::ast_matchers;
30
31namespace clang::tidy::modernize {
32
33static FindArgsResult findArgs(const CallExpr *Call) {
34 FindArgsResult Result;
35 Result.First = nullptr;
36 Result.Last = nullptr;
37 Result.Compare = nullptr;
38
39 // check if the function has initializer list argument
40 if (Call->getNumArgs() < 3) {
41 auto ArgIterator = Call->arguments().begin();
42
43 const auto *InitListExpr =
44 dyn_cast<CXXStdInitializerListExpr>(*ArgIterator);
45 const auto *InitList =
46 InitListExpr != nullptr
47 ? dyn_cast<clang::InitListExpr>(
48 InitListExpr->getSubExpr()->IgnoreImplicit())
49 : nullptr;
50
51 if (InitList) {
52 Result.Args.append(InitList->inits().begin(), InitList->inits().end());
53 Result.First = *ArgIterator;
54 Result.Last = *ArgIterator;
55
56 // check if there is a comparison argument
57 std::advance(ArgIterator, 1);
58 if (ArgIterator != Call->arguments().end())
59 Result.Compare = *ArgIterator;
60
61 return Result;
62 }
63 Result.Args = SmallVector<const Expr *>(Call->arguments());
64 } else {
65 // if it has 3 arguments then the last will be the comparison
66 Result.Compare = *(std::next(Call->arguments().begin(), 2));
67 Result.Args = SmallVector<const Expr *>(llvm::drop_end(Call->arguments()));
68 }
69 Result.First = Result.Args.front();
70 Result.Last = Result.Args.back();
71
72 return Result;
73}
74
75static SmallVector<FixItHint>
76generateReplacements(const MatchFinder::MatchResult &Match,
77 const CallExpr *TopCall, const FindArgsResult &Result,
78 const bool IgnoreNonTrivialTypes,
79 const std::uint64_t IgnoreTrivialTypesOfSizeAbove) {
80 SmallVector<FixItHint> FixItHints;
81 const SourceManager &SourceMngr = *Match.SourceManager;
82 const LangOptions &LanguageOpts = Match.Context->getLangOpts();
83
84 const QualType ResultType = TopCall->getDirectCallee()
85 ->getReturnType()
86 .getCanonicalType()
87 .getNonReferenceType()
88 .getUnqualifiedType();
89
90 // check if the type is trivial
91 const bool IsResultTypeTrivial = ResultType.isTrivialType(*Match.Context);
92
93 if ((!IsResultTypeTrivial && IgnoreNonTrivialTypes))
94 return FixItHints;
95
96 if (IsResultTypeTrivial &&
97 static_cast<std::uint64_t>(
98 Match.Context->getTypeSizeInChars(ResultType).getQuantity()) >
99 IgnoreTrivialTypesOfSizeAbove)
100 return FixItHints;
101
102 for (const Expr *Arg : Result.Args) {
103 const auto *InnerCall = dyn_cast<CallExpr>(Arg->IgnoreParenImpCasts());
104
105 // If the argument is not a nested call
106 if (!InnerCall) {
107 // check if typecast is required
108 const QualType ArgType = Arg->IgnoreParenImpCasts()
109 ->getType()
110 .getCanonicalType()
111 .getUnqualifiedType();
112
113 if (ArgType == ResultType)
114 continue;
115
116 const StringRef ArgText = Lexer::getSourceText(
117 CharSourceRange::getTokenRange(Arg->getSourceRange()), SourceMngr,
118 LanguageOpts);
119
120 const auto Replacement = Twine("static_cast<")
121 .concat(ResultType.getAsString(LanguageOpts))
122 .concat(">(")
123 .concat(ArgText)
124 .concat(")")
125 .str();
126
127 FixItHints.push_back(
128 FixItHint::CreateReplacement(Arg->getSourceRange(), Replacement));
129 continue;
130 }
131
132 // if the nested call is not the same as the top call
133 if (InnerCall->getDirectCallee()->getQualifiedNameAsString() !=
134 TopCall->getDirectCallee()->getQualifiedNameAsString())
135 continue;
136
137 const FindArgsResult InnerResult = findArgs(InnerCall);
138
139 // if the nested call doesn't have arguments skip it
140 if (!InnerResult.First || !InnerResult.Last)
141 continue;
142
143 // if the nested call doesn't have the same compare function
144 if ((Result.Compare || InnerResult.Compare) &&
145 !utils::areStatementsIdentical(Result.Compare, InnerResult.Compare,
146 *Match.Context))
147 continue;
148
149 // remove the function call
150 FixItHints.push_back(
151 FixItHint::CreateRemoval(InnerCall->getCallee()->getSourceRange()));
152
153 // remove the parentheses
155 InnerCall->getCallee()->getEndLoc(), SourceMngr, LanguageOpts);
156 if (LParen.has_value() && LParen->is(tok::l_paren))
157 FixItHints.push_back(
158 FixItHint::CreateRemoval(SourceRange(LParen->getLocation())));
159 FixItHints.push_back(
160 FixItHint::CreateRemoval(SourceRange(InnerCall->getRParenLoc())));
161
162 // if the inner call has an initializer list arg
163 if (InnerResult.First == InnerResult.Last) {
164 // remove the initializer list braces
165 FixItHints.push_back(FixItHint::CreateRemoval(
166 CharSourceRange::getTokenRange(InnerResult.First->getBeginLoc())));
167 FixItHints.push_back(FixItHint::CreateRemoval(
168 CharSourceRange::getTokenRange(InnerResult.First->getEndLoc())));
169 }
170
171 const SmallVector<FixItHint> InnerReplacements = generateReplacements(
172 Match, InnerCall, InnerResult, IgnoreNonTrivialTypes,
173 IgnoreTrivialTypesOfSizeAbove);
174
175 FixItHints.append(InnerReplacements);
176
177 if (InnerResult.Compare) {
178 // find the comma after the value arguments
180 InnerResult.Last->getEndLoc(), SourceMngr, LanguageOpts);
181
182 // remove the comma and the comparison
183 if (Comma.has_value() && Comma->is(tok::comma))
184 FixItHints.push_back(
185 FixItHint::CreateRemoval(SourceRange(Comma->getLocation())));
186
187 FixItHints.push_back(
188 FixItHint::CreateRemoval(InnerResult.Compare->getSourceRange()));
189 }
190 }
191
192 return FixItHints;
193}
194
196 StringRef Name, ClangTidyContext *Context)
197 : ClangTidyCheck(Name, Context),
198 IgnoreNonTrivialTypes(Options.get("IgnoreNonTrivialTypes", true)),
199 IgnoreTrivialTypesOfSizeAbove(
200 Options.get("IgnoreTrivialTypesOfSizeAbove", 32L)),
201 Inserter(Options.getLocalOrGlobal("IncludeStyle",
202 utils::IncludeSorter::IS_LLVM),
203 areDiagsSelfContained()) {}
204
207 Options.store(Opts, "IgnoreNonTrivialTypes", IgnoreNonTrivialTypes);
208 Options.store(Opts, "IgnoreTrivialTypesOfSizeAbove",
209 IgnoreTrivialTypesOfSizeAbove);
210 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
211}
212
214 auto CreateMatcher = [](const StringRef FunctionName) {
215 auto FuncDecl = functionDecl(hasName(FunctionName));
216 auto Expression = callExpr(callee(FuncDecl));
217
218 return callExpr(callee(FuncDecl),
219 anyOf(hasArgument(0, Expression),
220 hasArgument(1, Expression),
221 hasArgument(0, cxxStdInitializerListExpr())),
222 unless(hasParent(Expression)))
223 .bind("topCall");
224 };
225
226 Finder->addMatcher(CreateMatcher("::std::max"), this);
227 Finder->addMatcher(CreateMatcher("::std::min"), this);
228}
229
231 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
232 Inserter.registerPreprocessor(PP);
233}
234
236 const MatchFinder::MatchResult &Match) {
237
238 const auto *TopCall = Match.Nodes.getNodeAs<CallExpr>("topCall");
239
240 const FindArgsResult Result = findArgs(TopCall);
241 const SmallVector<FixItHint> Replacements =
242 generateReplacements(Match, TopCall, Result, IgnoreNonTrivialTypes,
243 IgnoreTrivialTypesOfSizeAbove);
244
245 if (Replacements.empty())
246 return;
247
248 const DiagnosticBuilder Diagnostic =
249 diag(TopCall->getBeginLoc(),
250 "do not use nested 'std::%0' calls, use an initializer list instead")
251 << TopCall->getDirectCallee()->getName()
252 << Inserter.createIncludeInsertion(
253 Match.SourceManager->getFileID(TopCall->getBeginLoc()),
254 "<algorithm>");
255
256 // if the top call doesn't have an initializer list argument
257 if (Result.First != Result.Last) {
258 // add { and } insertions
259 Diagnostic << FixItHint::CreateInsertion(Result.First->getBeginLoc(), "{");
260
261 Diagnostic << FixItHint::CreateInsertion(
262 Lexer::getLocForEndOfToken(Result.Last->getEndLoc(), 0,
263 *Match.SourceManager,
264 Match.Context->getLangOpts()),
265 "}");
266 }
267
268 Diagnostic << Replacements;
269}
270
271} // namespace clang::tidy::modernize
llvm::SmallString< 256U > Name
DiagnosticCallback Diagnostic
llvm::json::Object Args
Definition: Trace.cpp:138
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.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
MinMaxUseInitializerListCheck(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 &Match) 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 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 FindArgsResult findArgs(const CallExpr *Call)
static SmallVector< FixItHint > generateReplacements(const MatchFinder::MatchResult &Match, const CallExpr *TopCall, const FindArgsResult &Result, const bool IgnoreNonTrivialTypes, const std::uint64_t IgnoreTrivialTypesOfSizeAbove)
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:111
bool areStatementsIdentical(const Stmt *FirstStmt, const Stmt *SecondStmt, const ASTContext &Context, bool Canonical)
Definition: ASTUtils.cpp:91
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::StringMap< ClangTidyValue > OptionMap