1 | //===--- UseRangesCheck.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 "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 | |
33 | using namespace clang::ast_matchers; |
34 | |
35 | static constexpr const char BoundCall[] = "CallExpr" ; |
36 | static constexpr const char FuncDecl[] = "FuncDecl" ; |
37 | static constexpr const char ArgName[] = "ArgName" ; |
38 | |
39 | namespace clang::tidy::utils { |
40 | |
41 | static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) { |
42 | std::string Output; |
43 | llvm::raw_string_ostream OS(Output); |
44 | for (const UseRangesCheck::Indexes &Item : Signature) |
45 | OS << Item.BeginArg << ":" << Item.EndArg << ":" |
46 | << (Item.ReplaceArg == Item.First ? '0' : '1'); |
47 | return Output; |
48 | } |
49 | |
50 | namespace { |
51 | |
52 | AST_MATCHER(Expr, hasSideEffects) { |
53 | return Node.HasSideEffects(Ctx: Finder->getASTContext()); |
54 | } |
55 | } // namespace |
56 | |
57 | static auto |
58 | makeExprMatcher(ast_matchers::internal::Matcher<Expr> ArgumentMatcher, |
59 | ArrayRef<StringRef> MethodNames, |
60 | ArrayRef<StringRef> FreeNames) { |
61 | return expr( |
62 | anyOf(cxxMemberCallExpr(argumentCountIs(N: 0), |
63 | callee(InnerMatcher: cxxMethodDecl(hasAnyName(MethodNames))), |
64 | on(InnerMatcher: ArgumentMatcher)), |
65 | callExpr(argumentCountIs(N: 1), hasArgument(N: 0, InnerMatcher: ArgumentMatcher), |
66 | hasDeclaration(InnerMatcher: functionDecl(hasAnyName(FreeNames)))))); |
67 | } |
68 | |
69 | static ast_matchers::internal::Matcher<CallExpr> |
70 | makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes, |
71 | ArrayRef<StringRef> BeginFreeNames, |
72 | ArrayRef<StringRef> EndFreeNames, |
73 | const std::optional<UseRangesCheck::ReverseIteratorDescriptor> |
74 | &ReverseDescriptor) { |
75 | std::string ArgBound = (ArgName + llvm::Twine(Indexes.BeginArg)).str(); |
76 | SmallString<64> ID = {BoundCall, State}; |
77 | ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf( |
78 | hasArgument(N: Indexes.BeginArg, |
79 | InnerMatcher: makeExprMatcher(ArgumentMatcher: expr(unless(hasSideEffects())).bind(ID: ArgBound), |
80 | MethodNames: {"begin" , "cbegin" }, FreeNames: BeginFreeNames)), |
81 | hasArgument(N: Indexes.EndArg, |
82 | InnerMatcher: makeExprMatcher( |
83 | ArgumentMatcher: expr(matchers::isStatementIdenticalToBoundNode(ID: ArgBound)), |
84 | MethodNames: {"end" , "cend" }, FreeNames: EndFreeNames))); |
85 | if (ReverseDescriptor) { |
86 | ArgBound.push_back(c: 'R'); |
87 | SmallVector<StringRef> RBegin{ |
88 | llvm::make_first_range(c: ReverseDescriptor->FreeReverseNames)}; |
89 | SmallVector<StringRef> REnd{ |
90 | llvm::make_second_range(c: ReverseDescriptor->FreeReverseNames)}; |
91 | ArgumentMatcher = anyOf( |
92 | ArgumentMatcher, |
93 | allOf(hasArgument( |
94 | N: Indexes.BeginArg, |
95 | InnerMatcher: makeExprMatcher(ArgumentMatcher: expr(unless(hasSideEffects())).bind(ID: ArgBound), |
96 | MethodNames: {"rbegin" , "crbegin" }, FreeNames: RBegin)), |
97 | hasArgument( |
98 | N: Indexes.EndArg, |
99 | InnerMatcher: makeExprMatcher( |
100 | ArgumentMatcher: expr(matchers::isStatementIdenticalToBoundNode(ID: ArgBound)), |
101 | MethodNames: {"rend" , "crend" }, FreeNames: REnd)))); |
102 | } |
103 | return callExpr(argumentCountAtLeast( |
104 | N: std::max(a: Indexes.BeginArg, b: Indexes.EndArg) + 1), |
105 | ArgumentMatcher) |
106 | .bind(ID); |
107 | } |
108 | |
109 | void UseRangesCheck::registerMatchers(MatchFinder *Finder) { |
110 | auto Replaces = getReplacerMap(); |
111 | ReverseDescriptor = getReverseDescriptor(); |
112 | auto BeginEndNames = getFreeBeginEndMethods(); |
113 | llvm::SmallVector<StringRef, 4> BeginNames{ |
114 | llvm::make_first_range(c&: BeginEndNames)}; |
115 | llvm::SmallVector<StringRef, 4> EndNames{ |
116 | llvm::make_second_range(c&: BeginEndNames)}; |
117 | Replacers.clear(); |
118 | llvm::DenseSet<Replacer *> SeenRepl; |
119 | for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { |
120 | auto Replacer = I->getValue(); |
121 | if (!SeenRepl.insert(V: Replacer.get()).second) |
122 | continue; |
123 | Replacers.push_back(x: Replacer); |
124 | assert(!Replacer->getReplacementSignatures().empty() && |
125 | llvm::all_of(Replacer->getReplacementSignatures(), |
126 | [](auto Index) { return !Index.empty(); })); |
127 | std::vector<StringRef> Names(1, I->getKey()); |
128 | for (auto J = std::next(x: I); J != E; ++J) |
129 | if (J->getValue() == Replacer) |
130 | Names.push_back(x: J->getKey()); |
131 | |
132 | std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers; |
133 | // As we match on the first matched signature, we need to sort the |
134 | // signatures in order of length(longest to shortest). This way any |
135 | // signature that is a subset of another signature will be matched after the |
136 | // other. |
137 | SmallVector<Signature> SigVec(Replacer->getReplacementSignatures()); |
138 | llvm::sort(C&: SigVec, Comp: [](auto &L, auto &R) { return R.size() < L.size(); }); |
139 | for (const auto &Signature : SigVec) { |
140 | std::vector<ast_matchers::internal::DynTypedMatcher> Matchers; |
141 | for (const auto &ArgPair : Signature) |
142 | Matchers.push_back(x: makeMatcherPair(State: getFullPrefix(Signature), Indexes: ArgPair, |
143 | BeginFreeNames: BeginNames, EndFreeNames: EndNames, |
144 | ReverseDescriptor)); |
145 | TotalMatchers.push_back( |
146 | x: ast_matchers::internal::DynTypedMatcher::constructVariadic( |
147 | Op: ast_matchers::internal::DynTypedMatcher::VO_AllOf, |
148 | SupportedKind: ASTNodeKind::getFromNodeKind<CallExpr>(), InnerMatchers: std::move(Matchers))); |
149 | } |
150 | Finder->addMatcher( |
151 | NodeMatch: callExpr( |
152 | callee(InnerMatcher: functionDecl(hasAnyName(std::move(Names))) |
153 | .bind(ID: (FuncDecl + Twine(Replacers.size() - 1).str()))), |
154 | ast_matchers::internal::DynTypedMatcher::constructVariadic( |
155 | Op: ast_matchers::internal::DynTypedMatcher::VO_AnyOf, |
156 | SupportedKind: ASTNodeKind::getFromNodeKind<CallExpr>(), |
157 | InnerMatchers: std::move(TotalMatchers)) |
158 | .convertTo<CallExpr>()), |
159 | Action: this); |
160 | } |
161 | } |
162 | |
163 | static void removeFunctionArgs(DiagnosticBuilder &Diag, const CallExpr &Call, |
164 | ArrayRef<unsigned> Indexes, |
165 | const ASTContext &Ctx) { |
166 | llvm::SmallVector<unsigned> Sorted(Indexes); |
167 | llvm::sort(C&: Sorted); |
168 | // Keep track of commas removed |
169 | llvm::SmallBitVector Commas(Call.getNumArgs()); |
170 | // The first comma is actually the '(' which we can't remove |
171 | Commas[0] = true; |
172 | for (unsigned Index : Sorted) { |
173 | const Expr *Arg = Call.getArg(Arg: Index); |
174 | if (Commas[Index]) { |
175 | if (Index >= Commas.size()) { |
176 | Diag << FixItHint::CreateRemoval(Arg->getSourceRange()); |
177 | } else { |
178 | // Remove the next comma |
179 | Commas[Index + 1] = true; |
180 | Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( |
181 | {Arg->getBeginLoc(), |
182 | Lexer::getLocForEndOfToken( |
183 | Loc: Arg->getEndLoc(), Offset: 0, SM: Ctx.getSourceManager(), LangOpts: Ctx.getLangOpts()) |
184 | .getLocWithOffset(1)})); |
185 | } |
186 | } else { |
187 | Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( |
188 | Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc())); |
189 | Commas[Index] = true; |
190 | } |
191 | } |
192 | } |
193 | |
194 | void UseRangesCheck::check(const MatchFinder::MatchResult &Result) { |
195 | Replacer *Replacer = nullptr; |
196 | const FunctionDecl *Function = nullptr; |
197 | for (auto [Node, Value] : Result.Nodes.getMap()) { |
198 | StringRef NodeStr(Node); |
199 | if (!NodeStr.consume_front(Prefix: FuncDecl)) |
200 | continue; |
201 | Function = Value.get<FunctionDecl>(); |
202 | size_t Index; |
203 | if (NodeStr.getAsInteger(Radix: 10, Result&: Index)) { |
204 | llvm_unreachable("Unable to extract replacer index" ); |
205 | } |
206 | assert(Index < Replacers.size()); |
207 | Replacer = Replacers[Index].get(); |
208 | break; |
209 | } |
210 | assert(Replacer && Function); |
211 | SmallString<64> Buffer; |
212 | for (const Signature &Sig : Replacer->getReplacementSignatures()) { |
213 | Buffer.assign(Refs: {BoundCall, getFullPrefix(Signature: Sig)}); |
214 | const auto *Call = Result.Nodes.getNodeAs<CallExpr>(ID: Buffer); |
215 | if (!Call) |
216 | continue; |
217 | |
218 | // FIXME: This check specifically handles `CXXNullPtrLiteralExpr`, but |
219 | // a more general solution might be needed. |
220 | if (Function->getName() == "find" ) { |
221 | const unsigned ValueArgIndex = 2; |
222 | if (Call->getNumArgs() <= ValueArgIndex) |
223 | continue; |
224 | const Expr *ValueExpr = |
225 | Call->getArg(Arg: ValueArgIndex)->IgnoreParenImpCasts(); |
226 | if (isa<CXXNullPtrLiteralExpr>(Val: ValueExpr)) |
227 | return; |
228 | } |
229 | |
230 | auto Diag = createDiag(Call: *Call); |
231 | if (auto ReplaceName = Replacer->getReplaceName(*Function)) |
232 | Diag << FixItHint::CreateReplacement(Call->getCallee()->getSourceRange(), |
233 | *ReplaceName); |
234 | if (auto Include = Replacer->getHeaderInclusion(*Function)) |
235 | Diag << Inserter.createIncludeInsertion( |
236 | FileID: Result.SourceManager->getFileID(SpellingLoc: Call->getBeginLoc()), Header: *Include); |
237 | llvm::SmallVector<unsigned, 3> ToRemove; |
238 | for (const auto &[First, Second, Replace] : Sig) { |
239 | auto ArgNode = ArgName + std::to_string(val: First); |
240 | if (const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ID: ArgNode)) { |
241 | Diag << FixItHint::CreateReplacement( |
242 | Call->getArg(Arg: Replace == Indexes::Second ? Second : First) |
243 | ->getSourceRange(), |
244 | Lexer::getSourceText( |
245 | Range: CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), |
246 | SM: Result.Context->getSourceManager(), |
247 | LangOpts: Result.Context->getLangOpts())); |
248 | } else { |
249 | assert(ReverseDescriptor && "Couldn't find forward argument" ); |
250 | ArgNode.push_back(c: 'R'); |
251 | ArgExpr = Result.Nodes.getNodeAs<Expr>(ID: ArgNode); |
252 | assert(ArgExpr && "Couldn't find forward or reverse argument" ); |
253 | if (ReverseDescriptor->ReverseHeader) |
254 | Diag << Inserter.createIncludeInsertion( |
255 | FileID: Result.SourceManager->getFileID(SpellingLoc: Call->getBeginLoc()), |
256 | Header: *ReverseDescriptor->ReverseHeader); |
257 | StringRef ArgText = Lexer::getSourceText( |
258 | Range: CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), |
259 | SM: Result.Context->getSourceManager(), LangOpts: Result.Context->getLangOpts()); |
260 | SmallString<128> ReplaceText; |
261 | if (ReverseDescriptor->IsPipeSyntax) |
262 | ReplaceText.assign( |
263 | Refs: {ArgText, " | " , ReverseDescriptor->ReverseAdaptorName}); |
264 | else |
265 | ReplaceText.assign( |
266 | Refs: {ReverseDescriptor->ReverseAdaptorName, "(" , ArgText, ")" }); |
267 | Diag << FixItHint::CreateReplacement( |
268 | Call->getArg(Arg: Replace == Indexes::Second ? Second : First) |
269 | ->getSourceRange(), |
270 | ReplaceText); |
271 | } |
272 | ToRemove.push_back(Elt: Replace == Indexes::Second ? First : Second); |
273 | } |
274 | removeFunctionArgs(Diag, Call: *Call, Indexes: ToRemove, Ctx: *Result.Context); |
275 | return; |
276 | } |
277 | llvm_unreachable("No valid signature found" ); |
278 | } |
279 | |
280 | bool UseRangesCheck::isLanguageVersionSupported( |
281 | const LangOptions &LangOpts) const { |
282 | return LangOpts.CPlusPlus11; |
283 | } |
284 | |
285 | UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context) |
286 | : ClangTidyCheck(Name, Context), |
287 | Inserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
288 | Default: utils::IncludeSorter::IS_LLVM), |
289 | areDiagsSelfContained()) {} |
290 | |
291 | void UseRangesCheck::registerPPCallbacks(const SourceManager &, |
292 | Preprocessor *PP, Preprocessor *) { |
293 | Inserter.registerPreprocessor(PP); |
294 | } |
295 | |
296 | void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
297 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: Inserter.getStyle()); |
298 | } |
299 | |
300 | std::optional<std::string> |
301 | UseRangesCheck::Replacer::(const NamedDecl &) const { |
302 | return std::nullopt; |
303 | } |
304 | |
305 | DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) { |
306 | return diag(Loc: Call.getBeginLoc(), Description: "use a ranges version of this algorithm" ); |
307 | } |
308 | |
309 | std::optional<UseRangesCheck::ReverseIteratorDescriptor> |
310 | UseRangesCheck::getReverseDescriptor() const { |
311 | return std::nullopt; |
312 | } |
313 | |
314 | ArrayRef<std::pair<StringRef, StringRef>> |
315 | UseRangesCheck::getFreeBeginEndMethods() const { |
316 | return {}; |
317 | } |
318 | |
319 | std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const { |
320 | return TK_IgnoreUnlessSpelledInSource; |
321 | } |
322 | } // namespace clang::tidy::utils |
323 | |