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