clang-tools 24.0.0git
utils/UseRangesCheck.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
9#include "UseRangesCheck.h"
10#include "Matchers.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Decl.h"
13#include "clang/AST/Expr.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "clang/ASTMatchers/ASTMatchersInternal.h"
17#include "clang/Basic/Diagnostic.h"
18#include "clang/Basic/LLVM.h"
19#include "clang/Basic/SourceLocation.h"
20#include "clang/Basic/SourceManager.h"
21#include "clang/Lex/Lexer.h"
22#include "llvm/ADT/ArrayRef.h"
23#include "llvm/ADT/STLExtras.h"
24#include "llvm/ADT/SmallBitVector.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/ADT/StringRef.h"
27#include "llvm/ADT/Twine.h"
28#include "llvm/Support/raw_ostream.h"
29#include <cassert>
30#include <optional>
31#include <string>
32
33using namespace clang::ast_matchers;
34
35static constexpr const char BoundCall[] = "CallExpr";
36static constexpr const char FuncDecl[] = "FuncDecl";
37static constexpr const char ArgName[] = "ArgName";
38static constexpr const char StructuredBindingResult[] = "StructuredBinding";
39
40namespace clang::tidy::utils {
41
42static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) {
43 std::string Output;
44 llvm::raw_string_ostream OS(Output);
45 for (const UseRangesCheck::Indexes &Item : Signature)
46 OS << Item.BeginArg << ":" << Item.EndArg << ":"
47 << (Item.ReplaceArg == UseRangesCheck::Indexes::First ? '0' : '1');
48 return Output;
49}
50
51namespace {
52
53AST_MATCHER(Expr, hasSideEffects) {
54 return Node.HasSideEffects(Finder->getASTContext());
55}
56} // namespace
57
59 return callExpr(
60 anyOf(hasParent(decompositionDecl()),
61 hasParent(exprWithCleanups(hasParent(decompositionDecl())))));
62}
63
64static auto hasStructuredBindingResult(StringRef ID) {
65 return hasStructuredBindingResult().bind(ID);
66}
67
68static auto
69makeExprMatcher(const ast_matchers::internal::Matcher<Expr> &ArgumentMatcher,
70 ArrayRef<StringRef> MethodNames,
71 ArrayRef<StringRef> FreeNames) {
72 return expr(
73 anyOf(cxxMemberCallExpr(argumentCountIs(0),
74 callee(cxxMethodDecl(hasAnyName(MethodNames))),
75 on(ArgumentMatcher)),
76 callExpr(argumentCountIs(1), hasArgument(0, ArgumentMatcher),
77 hasDeclaration(functionDecl(hasAnyName(FreeNames))))));
78}
79
80static ast_matchers::internal::Matcher<CallExpr>
81makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes,
82 ArrayRef<StringRef> BeginFreeNames,
83 ArrayRef<StringRef> EndFreeNames,
84 const std::optional<UseRangesCheck::ReverseIteratorDescriptor>
85 &ReverseDescriptor) {
86 std::string ArgBound = (ArgName + llvm::Twine(Indexes.BeginArg)).str();
87 const SmallString<64> ID = {BoundCall, State};
88 ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf(
89 hasArgument(Indexes.BeginArg,
90 makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound),
91 {"begin", "cbegin"}, BeginFreeNames)),
92 hasArgument(Indexes.EndArg,
94 expr(matchers::isStatementIdenticalToBoundNode(ArgBound)),
95 {"end", "cend"}, EndFreeNames)));
96 if (ReverseDescriptor) {
97 ArgBound.push_back('R');
98 const SmallVector<StringRef> RBegin{
99 llvm::make_first_range(ReverseDescriptor->FreeReverseNames)};
100 const SmallVector<StringRef> REnd{
101 llvm::make_second_range(ReverseDescriptor->FreeReverseNames)};
102 ArgumentMatcher = anyOf(
103 ArgumentMatcher,
104 allOf(hasArgument(
105 Indexes.BeginArg,
106 makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound),
107 {"rbegin", "crbegin"}, RBegin)),
108 hasArgument(
109 Indexes.EndArg,
111 expr(matchers::isStatementIdenticalToBoundNode(ArgBound)),
112 {"rend", "crend"}, REnd))));
113 }
114 return callExpr(argumentCountAtLeast(
115 std::max(Indexes.BeginArg, Indexes.EndArg) + 1),
116 ArgumentMatcher)
117 .bind(ID);
118}
119
120void UseRangesCheck::registerMatchers(MatchFinder *Finder) {
121 auto Replaces = getReplacerMap();
122 ReverseDescriptor = getReverseDescriptor();
123 auto BeginEndNames = getFreeBeginEndMethods();
124 const SmallVector<StringRef, 4> BeginNames{
125 llvm::make_first_range(BeginEndNames)};
126 const SmallVector<StringRef, 4> EndNames{
127 llvm::make_second_range(BeginEndNames)};
128 Replacers.clear();
129 llvm::DenseSet<Replacer *> SeenRepl;
130 for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
131 const auto Replacer = I->getValue();
132 if (!SeenRepl.insert(Replacer.get()).second)
133 continue;
134 Replacers.push_back(Replacer);
135 assert(!Replacer->getReplacementSignatures().empty() &&
136 llvm::all_of(Replacer->getReplacementSignatures(),
137 [](const Signature &Index) { return !Index.empty(); }));
138 std::vector<StringRef> Names(1, I->getKey());
139 for (auto J = std::next(I); J != E; ++J)
140 if (J->getValue() == Replacer)
141 Names.push_back(J->getKey());
142
143 std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers;
144 // As we match on the first matched signature, we need to sort the
145 // signatures in order of length(longest to shortest). This way any
146 // signature that is a subset of another signature will be matched after the
147 // other.
149 llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); });
150 for (const auto &Signature : SigVec) {
151 std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
152 for (const auto &ArgPair : Signature)
153 Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair,
154 BeginNames, EndNames,
155 ReverseDescriptor));
156 TotalMatchers.push_back(
157 ast_matchers::internal::DynTypedMatcher::constructVariadic(
158 ast_matchers::internal::DynTypedMatcher::VO_AllOf,
159 ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers)));
160 }
161 Finder->addMatcher(
162 callExpr(
163 callee(functionDecl(hasAnyName(Names))
164 .bind((FuncDecl + Twine(Replacers.size() - 1).str()))),
165 ast_matchers::internal::DynTypedMatcher::constructVariadic(
166 ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
167 ASTNodeKind::getFromNodeKind<CallExpr>(),
168 std::move(TotalMatchers))
169 .convertTo<CallExpr>(),
171 this);
172 }
173}
174
175static void removeFunctionArgs(const DiagnosticBuilder &Diag,
176 const CallExpr &Call, ArrayRef<unsigned> Indexes,
177 const ASTContext &Ctx) {
178 SmallVector<unsigned> Sorted(Indexes);
179 llvm::sort(Sorted);
180 // Keep track of commas removed
181 llvm::SmallBitVector Commas(Call.getNumArgs());
182 // The first comma is actually the '(' which we can't remove
183 Commas[0] = true;
184 for (const unsigned Index : Sorted) {
185 const Expr *Arg = Call.getArg(Index);
186 if (Commas[Index]) {
187 if (Index >= Commas.size()) {
188 Diag << FixItHint::CreateRemoval(Arg->getSourceRange());
189 } else {
190 // Remove the next comma
191 Commas[Index + 1] = true;
192 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
193 {Arg->getBeginLoc(),
194 Lexer::getLocForEndOfToken(
195 Arg->getEndLoc(), 0, Ctx.getSourceManager(), Ctx.getLangOpts())
196 .getLocWithOffset(1)}));
197 }
198 } else {
199 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
200 Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc()));
201 Commas[Index] = true;
202 }
203 }
204}
205
206static bool isResultUsed(const DynTypedNode &Node,
207 const ast_matchers::MatchFinder::MatchResult &Result) {
208 const DynTypedNodeList Parents = Result.Context->getParents(Node);
209 assert(Parents.size() == 1 &&
210 "Expected exactly one parent for a matched algorithm call");
211 const DynTypedNode &Parent = Parents[0];
212 if (Parent.get<CompoundStmt>())
213 return false;
214 if (const auto *Cleanups = Parent.get<ExprWithCleanups>())
215 return isResultUsed(DynTypedNode::create(*Cleanups), Result);
216 if (const auto *Temporary = Parent.get<CXXBindTemporaryExpr>())
217 return isResultUsed(DynTypedNode::create(*Temporary), Result);
218 return true;
219}
220
221static bool isResultUsed(const CallExpr &Call,
222 const ast_matchers::MatchFinder::MatchResult &Result) {
223 return isResultUsed(DynTypedNode::create(Call), Result);
224}
225
227 bool ResultUsed, bool IsStructuredBinding) {
229 if (!ResultUsed)
230 return true;
231 switch (Policy.PolicyKind) {
232 case Kind::Preserve:
233 case Kind::AppendAccessorForUsedResult:
234 return true;
235 case Kind::KeepFixItOnlyForStructuredBinding:
236 return IsStructuredBinding;
237 case Kind::SuppressFixItForUsedResult:
238 return false;
239 }
240 llvm_unreachable("Unhandled result use policy");
241}
242
243static void insertAccessor(const DiagnosticBuilder &Diag, const CallExpr &Call,
244 StringRef Accessor, const ASTContext &Ctx) {
245 const SourceLocation End = Lexer::getLocForEndOfToken(
246 Call.getEndLoc(), 0, Ctx.getSourceManager(), Ctx.getLangOpts());
247 if (End.isValid())
248 Diag << FixItHint::CreateInsertion(End, Accessor);
249}
250
251void UseRangesCheck::check(const MatchFinder::MatchResult &Result) {
252 const Replacer *Replacer = nullptr;
253 const FunctionDecl *Function = nullptr;
254 for (const auto &[Node, Value] : Result.Nodes.getMap()) {
255 StringRef NodeStr(Node);
256 if (!NodeStr.consume_front(FuncDecl))
257 continue;
258 Function = Value.get<FunctionDecl>();
259 size_t Index = 0;
260 if (NodeStr.getAsInteger(10, Index))
261 llvm_unreachable("Unable to extract replacer index");
262 assert(Index < Replacers.size());
263 Replacer = Replacers[Index].get();
264 break;
265 }
266 assert(Replacer && Function);
267 SmallString<64> Buffer;
268 for (const Signature &Sig : Replacer->getReplacementSignatures()) {
269 Buffer.assign({BoundCall, getFullPrefix(Sig)});
270 const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer);
271 if (!Call)
272 continue;
273
274 // FIXME: This check specifically handles `CXXNullPtrLiteralExpr`, but
275 // a more general solution might be needed.
276 if (Function->getName() == "find") {
277 const unsigned ValueArgIndex = 2;
278 if (Call->getNumArgs() <= ValueArgIndex)
279 continue;
280 const Expr *ValueExpr =
281 Call->getArg(ValueArgIndex)->IgnoreParenImpCasts();
282 if (isa<CXXNullPtrLiteralExpr>(ValueExpr))
283 return;
284 }
285
286 const bool IsStructuredBinding =
287 Result.Nodes.getNodeAs<CallExpr>(StructuredBindingResult) != nullptr;
288 const bool ResultUsed = isResultUsed(*Call, Result);
289 const auto ResultPolicy =
290 Replacer->getResultUsePolicy(*Function, IsStructuredBinding);
291
292 const auto Diag = createDiag(*Call);
293 if (!shouldEmitFixIts(ResultPolicy, ResultUsed, IsStructuredBinding))
294 return;
295
296 if (auto ReplaceName = Replacer->getReplaceName(*Function))
297 Diag << FixItHint::CreateReplacement(Call->getCallee()->getSourceRange(),
298 *ReplaceName);
299 if (auto Include = Replacer->getHeaderInclusion(*Function))
300 Diag << Inserter.createIncludeInsertion(
301 Result.SourceManager->getFileID(Call->getBeginLoc()), *Include);
303 for (const auto &[First, Second, Replace] : Sig) {
304 auto ArgNode = ArgName + std::to_string(First);
305 if (const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode)) {
306 Diag << FixItHint::CreateReplacement(
307 Call->getArg(Replace == Indexes::Second ? Second : First)
308 ->getSourceRange(),
309 Lexer::getSourceText(
310 CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
311 Result.Context->getSourceManager(),
312 Result.Context->getLangOpts()));
313 } else {
314 assert(ReverseDescriptor && "Couldn't find forward argument");
315 ArgNode.push_back('R');
316 ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode);
317 assert(ArgExpr && "Couldn't find forward or reverse argument");
318 if (ReverseDescriptor->ReverseHeader)
319 Diag << Inserter.createIncludeInsertion(
320 Result.SourceManager->getFileID(Call->getBeginLoc()),
321 *ReverseDescriptor->ReverseHeader);
322 const StringRef ArgText = Lexer::getSourceText(
323 CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
324 Result.Context->getSourceManager(), Result.Context->getLangOpts());
325 SmallString<128> ReplaceText;
326 if (ReverseDescriptor->IsPipeSyntax)
327 ReplaceText.assign(
328 {ArgText, " | ", ReverseDescriptor->ReverseAdaptorName});
329 else
330 ReplaceText.assign(
331 {ReverseDescriptor->ReverseAdaptorName, "(", ArgText, ")"});
332 Diag << FixItHint::CreateReplacement(
333 Call->getArg(Replace == Indexes::Second ? Second : First)
334 ->getSourceRange(),
335 ReplaceText);
336 }
337 ToRemove.push_back(Replace == Indexes::Second ? First : Second);
338 }
339 removeFunctionArgs(Diag, *Call, ToRemove, *Result.Context);
340 using ResultPolicyKind = Replacer::ResultUsePolicy::Kind;
341 if (ResultUsed && ResultPolicy.PolicyKind ==
342 ResultPolicyKind::AppendAccessorForUsedResult) {
343 insertAccessor(Diag, *Call, ResultPolicy.Accessor, *Result.Context);
344 }
345 return;
346 }
347 llvm_unreachable("No valid signature found");
348}
349
351 const LangOptions &LangOpts) const {
352 return LangOpts.CPlusPlus11;
353}
354
356 : ClangTidyCheck(Name, Context),
357 Inserter(Options.getLocalOrGlobal("IncludeStyle",
358 utils::IncludeSorter::IS_LLVM),
359 areDiagsSelfContained()) {}
360
361void UseRangesCheck::registerPPCallbacks(const SourceManager &,
362 Preprocessor *PP, Preprocessor *) {
363 Inserter.registerPreprocessor(PP);
364}
365
367 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
368}
369
370std::optional<std::string>
372 return std::nullopt;
373}
374
376UseRangesCheck::Replacer::getResultUsePolicy(const NamedDecl &, bool) const {
377 return {};
378}
379
380DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
381 return diag(Call.getBeginLoc(), "use a ranges version of this algorithm");
382}
383
384std::optional<UseRangesCheck::ReverseIteratorDescriptor>
386 return std::nullopt;
387}
388
389ArrayRef<std::pair<StringRef, StringRef>>
391 return {};
392}
393
394std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const {
395 return TK_IgnoreUnlessSpelledInSource;
396}
397} // namespace clang::tidy::utils
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
virtual ArrayRef< Signature > getReplacementSignatures() const =0
Gets an array of all the possible overloads for a function with indexes where begin and end arguments...
virtual ResultUsePolicy getResultUsePolicy(const NamedDecl &OriginalName, bool IsStructuredBinding) const
Gets how to handle fix-its when the original algorithm result is used.
virtual std::optional< std::string > getHeaderInclusion(const NamedDecl &OriginalName) const
Gets the header needed to access the replaced function Return std::nullopt if no new header is needed...
virtual std::optional< std::string > getReplaceName(const NamedDecl &OriginalName) const =0
Gets the name to replace a function with, return std::nullopt for a replacement where we just call a ...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
virtual DiagnosticBuilder createDiag(const CallExpr &Call)
Create a diagnostic for the CallExpr Override this to support custom diagnostic messages.
void registerMatchers(ast_matchers::MatchFinder *Finder) final
std::optional< TraversalKind > getCheckTraversalKind() const override
void check(const ast_matchers::MatchFinder::MatchResult &Result) final
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) final
virtual ReplacerMap getReplacerMap() const =0
Gets a map of function to replace and methods to create the replacements.
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
UseRangesCheck(StringRef Name, ClangTidyContext *Context)
virtual std::optional< ReverseIteratorDescriptor > getReverseDescriptor() const
virtual ArrayRef< std::pair< StringRef, StringRef > > getFreeBeginEndMethods() const
Gets the fully qualified names of begin and end functions.
static std::string getFullPrefix(ArrayRef< UseRangesCheck::Indexes > Signature)
static ast_matchers::internal::Matcher< CallExpr > makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes, ArrayRef< StringRef > BeginFreeNames, ArrayRef< StringRef > EndFreeNames, const std::optional< UseRangesCheck::ReverseIteratorDescriptor > &ReverseDescriptor)
static auto hasStructuredBindingResult()
static bool shouldEmitFixIts(UseRangesCheck::Replacer::ResultUsePolicy Policy, bool ResultUsed, bool IsStructuredBinding)
static bool isResultUsed(const DynTypedNode &Node, const ast_matchers::MatchFinder::MatchResult &Result)
IncludeSorter(const SourceManager *SourceMgr, FileID FileID, StringRef FileName, IncludeStyle Style)
Class used by IncludeInserterCallback to record the names of the / inclusions in a given source file ...
static void insertAccessor(const DiagnosticBuilder &Diag, const CallExpr &Call, StringRef Accessor, const ASTContext &Ctx)
static auto makeExprMatcher(const ast_matchers::internal::Matcher< Expr > &ArgumentMatcher, ArrayRef< StringRef > MethodNames, ArrayRef< StringRef > FreeNames)
static void removeFunctionArgs(const DiagnosticBuilder &Diag, const CallExpr &Call, ArrayRef< unsigned > Indexes, const ASTContext &Ctx)
llvm::StringMap< ClangTidyValue > OptionMap
static constexpr const char ArgName[]
static constexpr const char BoundCall[]
static constexpr const char StructuredBindingResult[]
static constexpr const char FuncDecl[]