annotate clang-tools-extra/clang-tidy/readability/IsolateDeclarationCheck.cpp @ 236:c4bab56944e8 llvm-original

LLVM 16
author kono
date Wed, 09 Nov 2022 17:45:10 +0900
parents 0572611fdcc8
children 1f2b6ac9f198
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 //===--- IsolateDeclarationCheck.cpp - clang-tidy -------------------------===//
anatofuz
parents:
diff changeset
2 //
anatofuz
parents:
diff changeset
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
anatofuz
parents:
diff changeset
4 // See https://llvm.org/LICENSE.txt for license information.
anatofuz
parents:
diff changeset
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
anatofuz
parents:
diff changeset
6 //
anatofuz
parents:
diff changeset
7 //===----------------------------------------------------------------------===//
anatofuz
parents:
diff changeset
8
anatofuz
parents:
diff changeset
9 #include "IsolateDeclarationCheck.h"
anatofuz
parents:
diff changeset
10 #include "../utils/LexerUtils.h"
anatofuz
parents:
diff changeset
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
anatofuz
parents:
diff changeset
12
anatofuz
parents:
diff changeset
13 using namespace clang::ast_matchers;
anatofuz
parents:
diff changeset
14 using namespace clang::tidy::utils::lexer;
anatofuz
parents:
diff changeset
15
anatofuz
parents:
diff changeset
16 namespace clang {
anatofuz
parents:
diff changeset
17 namespace tidy {
anatofuz
parents:
diff changeset
18 namespace readability {
anatofuz
parents:
diff changeset
19
anatofuz
parents:
diff changeset
20 namespace {
anatofuz
parents:
diff changeset
21 AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); }
anatofuz
parents:
diff changeset
22 AST_MATCHER(DeclStmt, onlyDeclaresVariables) {
anatofuz
parents:
diff changeset
23 return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); });
anatofuz
parents:
diff changeset
24 }
anatofuz
parents:
diff changeset
25 } // namespace
anatofuz
parents:
diff changeset
26
anatofuz
parents:
diff changeset
27 void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) {
anatofuz
parents:
diff changeset
28 Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()),
anatofuz
parents:
diff changeset
29 hasParent(compoundStmt()))
anatofuz
parents:
diff changeset
30 .bind("decl_stmt"),
anatofuz
parents:
diff changeset
31 this);
anatofuz
parents:
diff changeset
32 }
anatofuz
parents:
diff changeset
33
anatofuz
parents:
diff changeset
34 static SourceLocation findStartOfIndirection(SourceLocation Start,
anatofuz
parents:
diff changeset
35 int Indirections,
anatofuz
parents:
diff changeset
36 const SourceManager &SM,
anatofuz
parents:
diff changeset
37 const LangOptions &LangOpts) {
anatofuz
parents:
diff changeset
38 assert(Indirections >= 0 && "Indirections must be non-negative");
anatofuz
parents:
diff changeset
39 if (Indirections == 0)
anatofuz
parents:
diff changeset
40 return Start;
anatofuz
parents:
diff changeset
41
anatofuz
parents:
diff changeset
42 // Note that the post-fix decrement is necessary to perform the correct
anatofuz
parents:
diff changeset
43 // number of transformations.
anatofuz
parents:
diff changeset
44 while (Indirections-- != 0) {
anatofuz
parents:
diff changeset
45 Start = findPreviousAnyTokenKind(Start, SM, LangOpts, tok::star, tok::amp);
anatofuz
parents:
diff changeset
46 if (Start.isInvalid() || Start.isMacroID())
anatofuz
parents:
diff changeset
47 return SourceLocation();
anatofuz
parents:
diff changeset
48 }
anatofuz
parents:
diff changeset
49 return Start;
anatofuz
parents:
diff changeset
50 }
anatofuz
parents:
diff changeset
51
anatofuz
parents:
diff changeset
52 static bool isMacroID(SourceRange R) {
anatofuz
parents:
diff changeset
53 return R.getBegin().isMacroID() || R.getEnd().isMacroID();
anatofuz
parents:
diff changeset
54 }
anatofuz
parents:
diff changeset
55
anatofuz
parents:
diff changeset
56 /// This function counts the number of written indirections for the given
anatofuz
parents:
diff changeset
57 /// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing
anatofuz
parents:
diff changeset
58 /// the source code.
anatofuz
parents:
diff changeset
59 /// \see declRanges
anatofuz
parents:
diff changeset
60 static int countIndirections(const Type *T, int Indirections = 0) {
anatofuz
parents:
diff changeset
61 if (T->isFunctionPointerType()) {
anatofuz
parents:
diff changeset
62 const auto *Pointee = T->getPointeeType()->castAs<FunctionType>();
anatofuz
parents:
diff changeset
63 return countIndirections(
anatofuz
parents:
diff changeset
64 Pointee->getReturnType().IgnoreParens().getTypePtr(), ++Indirections);
anatofuz
parents:
diff changeset
65 }
anatofuz
parents:
diff changeset
66
anatofuz
parents:
diff changeset
67 // Note: Do not increment the 'Indirections' because it is not yet clear
anatofuz
parents:
diff changeset
68 // if there is an indirection added in the source code of the array
anatofuz
parents:
diff changeset
69 // declaration.
anatofuz
parents:
diff changeset
70 if (const auto *AT = dyn_cast<ArrayType>(T))
anatofuz
parents:
diff changeset
71 return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(),
anatofuz
parents:
diff changeset
72 Indirections);
anatofuz
parents:
diff changeset
73
anatofuz
parents:
diff changeset
74 if (isa<PointerType>(T) || isa<ReferenceType>(T))
anatofuz
parents:
diff changeset
75 return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(),
anatofuz
parents:
diff changeset
76 ++Indirections);
anatofuz
parents:
diff changeset
77
anatofuz
parents:
diff changeset
78 return Indirections;
anatofuz
parents:
diff changeset
79 }
anatofuz
parents:
diff changeset
80
anatofuz
parents:
diff changeset
81 static bool typeIsMemberPointer(const Type *T) {
anatofuz
parents:
diff changeset
82 if (isa<ArrayType>(T))
anatofuz
parents:
diff changeset
83 return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual());
anatofuz
parents:
diff changeset
84
anatofuz
parents:
diff changeset
85 if ((isa<PointerType>(T) || isa<ReferenceType>(T)) &&
anatofuz
parents:
diff changeset
86 isa<PointerType>(T->getPointeeType()))
anatofuz
parents:
diff changeset
87 return typeIsMemberPointer(T->getPointeeType().getTypePtr());
anatofuz
parents:
diff changeset
88
anatofuz
parents:
diff changeset
89 return isa<MemberPointerType>(T);
anatofuz
parents:
diff changeset
90 }
anatofuz
parents:
diff changeset
91
anatofuz
parents:
diff changeset
92 /// This function tries to extract the SourceRanges that make up all
anatofuz
parents:
diff changeset
93 /// declarations in this \c DeclStmt.
anatofuz
parents:
diff changeset
94 ///
anatofuz
parents:
diff changeset
95 /// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}.
anatofuz
parents:
diff changeset
96 /// Each \c SourceRange is of the form [Begin, End).
anatofuz
parents:
diff changeset
97 /// If any of the create ranges is invalid or in a macro the result will be
anatofuz
parents:
diff changeset
98 /// \c None.
anatofuz
parents:
diff changeset
99 /// If the \c DeclStmt contains only one declaration, the result is \c None.
anatofuz
parents:
diff changeset
100 /// If the \c DeclStmt contains declarations other than \c VarDecl the result
anatofuz
parents:
diff changeset
101 /// is \c None.
anatofuz
parents:
diff changeset
102 ///
anatofuz
parents:
diff changeset
103 /// \code
anatofuz
parents:
diff changeset
104 /// int * ptr1 = nullptr, value = 42;
anatofuz
parents:
diff changeset
105 /// // [ ][ ] [ ] - The ranges here are inclusive
anatofuz
parents:
diff changeset
106 /// \endcode
anatofuz
parents:
diff changeset
107 /// \todo Generalize this function to take other declarations than \c VarDecl.
anatofuz
parents:
diff changeset
108 static Optional<std::vector<SourceRange>>
anatofuz
parents:
diff changeset
109 declRanges(const DeclStmt *DS, const SourceManager &SM,
anatofuz
parents:
diff changeset
110 const LangOptions &LangOpts) {
anatofuz
parents:
diff changeset
111 std::size_t DeclCount = std::distance(DS->decl_begin(), DS->decl_end());
anatofuz
parents:
diff changeset
112 if (DeclCount < 2)
anatofuz
parents:
diff changeset
113 return None;
anatofuz
parents:
diff changeset
114
anatofuz
parents:
diff changeset
115 if (rangeContainsExpansionsOrDirectives(DS->getSourceRange(), SM, LangOpts))
anatofuz
parents:
diff changeset
116 return None;
anatofuz
parents:
diff changeset
117
anatofuz
parents:
diff changeset
118 // The initial type of the declaration and each declaration has it's own
anatofuz
parents:
diff changeset
119 // slice. This is necessary, because pointers and references bind only
anatofuz
parents:
diff changeset
120 // to the local variable and not to all variables in the declaration.
anatofuz
parents:
diff changeset
121 // Example: 'int *pointer, value = 42;'
anatofuz
parents:
diff changeset
122 std::vector<SourceRange> Slices;
anatofuz
parents:
diff changeset
123 Slices.reserve(DeclCount + 1);
anatofuz
parents:
diff changeset
124
anatofuz
parents:
diff changeset
125 // Calculate the first slice, for now only variables are handled but in the
anatofuz
parents:
diff changeset
126 // future this should be relaxed and support various kinds of declarations.
anatofuz
parents:
diff changeset
127 const auto *FirstDecl = dyn_cast<VarDecl>(*DS->decl_begin());
anatofuz
parents:
diff changeset
128
anatofuz
parents:
diff changeset
129 if (FirstDecl == nullptr)
anatofuz
parents:
diff changeset
130 return None;
anatofuz
parents:
diff changeset
131
anatofuz
parents:
diff changeset
132 // FIXME: Member pointers are not transformed correctly right now, that's
anatofuz
parents:
diff changeset
133 // why they are treated as problematic here.
anatofuz
parents:
diff changeset
134 if (typeIsMemberPointer(FirstDecl->getType().IgnoreParens().getTypePtr()))
anatofuz
parents:
diff changeset
135 return None;
anatofuz
parents:
diff changeset
136
anatofuz
parents:
diff changeset
137 // Consider the following case: 'int * pointer, value = 42;'
anatofuz
parents:
diff changeset
138 // Created slices (inclusive) [ ][ ] [ ]
anatofuz
parents:
diff changeset
139 // Because 'getBeginLoc' points to the start of the variable *name*, the
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
140 // location of the pointer must be determined separately.
150
anatofuz
parents:
diff changeset
141 SourceLocation Start = findStartOfIndirection(
anatofuz
parents:
diff changeset
142 FirstDecl->getLocation(),
anatofuz
parents:
diff changeset
143 countIndirections(FirstDecl->getType().IgnoreParens().getTypePtr()), SM,
anatofuz
parents:
diff changeset
144 LangOpts);
anatofuz
parents:
diff changeset
145
anatofuz
parents:
diff changeset
146 // Fix function-pointer declarations that have a '(' in front of the
anatofuz
parents:
diff changeset
147 // pointer.
anatofuz
parents:
diff changeset
148 // Example: 'void (*f2)(int), (*g2)(int, float) = gg;'
anatofuz
parents:
diff changeset
149 // Slices: [ ][ ] [ ]
anatofuz
parents:
diff changeset
150 if (FirstDecl->getType()->isFunctionPointerType())
anatofuz
parents:
diff changeset
151 Start = findPreviousTokenKind(Start, SM, LangOpts, tok::l_paren);
anatofuz
parents:
diff changeset
152
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
153 // It is possible that a declarator is wrapped with parens.
150
anatofuz
parents:
diff changeset
154 // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;'
anatofuz
parents:
diff changeset
155 // The slice for the type-part must not contain these parens. Consequently
anatofuz
parents:
diff changeset
156 // 'Start' is moved to the most left paren if there are parens.
anatofuz
parents:
diff changeset
157 while (true) {
anatofuz
parents:
diff changeset
158 if (Start.isInvalid() || Start.isMacroID())
anatofuz
parents:
diff changeset
159 break;
anatofuz
parents:
diff changeset
160
anatofuz
parents:
diff changeset
161 Token T = getPreviousToken(Start, SM, LangOpts);
anatofuz
parents:
diff changeset
162 if (T.is(tok::l_paren)) {
anatofuz
parents:
diff changeset
163 Start = findPreviousTokenStart(Start, SM, LangOpts);
anatofuz
parents:
diff changeset
164 continue;
anatofuz
parents:
diff changeset
165 }
anatofuz
parents:
diff changeset
166 break;
anatofuz
parents:
diff changeset
167 }
anatofuz
parents:
diff changeset
168
anatofuz
parents:
diff changeset
169 SourceRange DeclRange(DS->getBeginLoc(), Start);
anatofuz
parents:
diff changeset
170 if (DeclRange.isInvalid() || isMacroID(DeclRange))
anatofuz
parents:
diff changeset
171 return None;
anatofuz
parents:
diff changeset
172
anatofuz
parents:
diff changeset
173 // The first slice, that is prepended to every isolated declaration, is
anatofuz
parents:
diff changeset
174 // created.
anatofuz
parents:
diff changeset
175 Slices.emplace_back(DeclRange);
anatofuz
parents:
diff changeset
176
anatofuz
parents:
diff changeset
177 // Create all following slices that each declare a variable.
anatofuz
parents:
diff changeset
178 SourceLocation DeclBegin = Start;
anatofuz
parents:
diff changeset
179 for (const auto &Decl : DS->decls()) {
anatofuz
parents:
diff changeset
180 const auto *CurrentDecl = cast<VarDecl>(Decl);
anatofuz
parents:
diff changeset
181
anatofuz
parents:
diff changeset
182 // FIXME: Member pointers are not transformed correctly right now, that's
anatofuz
parents:
diff changeset
183 // why they are treated as problematic here.
anatofuz
parents:
diff changeset
184 if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr()))
anatofuz
parents:
diff changeset
185 return None;
anatofuz
parents:
diff changeset
186
anatofuz
parents:
diff changeset
187 SourceLocation DeclEnd =
anatofuz
parents:
diff changeset
188 CurrentDecl->hasInit()
anatofuz
parents:
diff changeset
189 ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM,
anatofuz
parents:
diff changeset
190 LangOpts)
anatofuz
parents:
diff changeset
191 : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts);
anatofuz
parents:
diff changeset
192
anatofuz
parents:
diff changeset
193 SourceRange VarNameRange(DeclBegin, DeclEnd);
anatofuz
parents:
diff changeset
194 if (VarNameRange.isInvalid() || isMacroID(VarNameRange))
anatofuz
parents:
diff changeset
195 return None;
anatofuz
parents:
diff changeset
196
anatofuz
parents:
diff changeset
197 Slices.emplace_back(VarNameRange);
anatofuz
parents:
diff changeset
198 DeclBegin = DeclEnd.getLocWithOffset(1);
anatofuz
parents:
diff changeset
199 }
anatofuz
parents:
diff changeset
200 return Slices;
anatofuz
parents:
diff changeset
201 }
anatofuz
parents:
diff changeset
202
anatofuz
parents:
diff changeset
203 static Optional<std::vector<StringRef>>
anatofuz
parents:
diff changeset
204 collectSourceRanges(llvm::ArrayRef<SourceRange> Ranges, const SourceManager &SM,
anatofuz
parents:
diff changeset
205 const LangOptions &LangOpts) {
anatofuz
parents:
diff changeset
206 std::vector<StringRef> Snippets;
anatofuz
parents:
diff changeset
207 Snippets.reserve(Ranges.size());
anatofuz
parents:
diff changeset
208
anatofuz
parents:
diff changeset
209 for (const auto &Range : Ranges) {
anatofuz
parents:
diff changeset
210 CharSourceRange CharRange = Lexer::getAsCharRange(
anatofuz
parents:
diff changeset
211 CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), SM,
anatofuz
parents:
diff changeset
212 LangOpts);
anatofuz
parents:
diff changeset
213
anatofuz
parents:
diff changeset
214 if (CharRange.isInvalid())
anatofuz
parents:
diff changeset
215 return None;
anatofuz
parents:
diff changeset
216
anatofuz
parents:
diff changeset
217 bool InvalidText = false;
anatofuz
parents:
diff changeset
218 StringRef Snippet =
anatofuz
parents:
diff changeset
219 Lexer::getSourceText(CharRange, SM, LangOpts, &InvalidText);
anatofuz
parents:
diff changeset
220
anatofuz
parents:
diff changeset
221 if (InvalidText)
anatofuz
parents:
diff changeset
222 return None;
anatofuz
parents:
diff changeset
223
anatofuz
parents:
diff changeset
224 Snippets.emplace_back(Snippet);
anatofuz
parents:
diff changeset
225 }
anatofuz
parents:
diff changeset
226
anatofuz
parents:
diff changeset
227 return Snippets;
anatofuz
parents:
diff changeset
228 }
anatofuz
parents:
diff changeset
229
anatofuz
parents:
diff changeset
230 /// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
anatofuz
parents:
diff changeset
231 static std::vector<std::string>
anatofuz
parents:
diff changeset
232 createIsolatedDecls(llvm::ArrayRef<StringRef> Snippets) {
anatofuz
parents:
diff changeset
233 // The first section is the type snippet, which does not make a decl itself.
anatofuz
parents:
diff changeset
234 assert(Snippets.size() > 2 && "Not enough snippets to create isolated decls");
anatofuz
parents:
diff changeset
235 std::vector<std::string> Decls(Snippets.size() - 1);
anatofuz
parents:
diff changeset
236
anatofuz
parents:
diff changeset
237 for (std::size_t I = 1; I < Snippets.size(); ++I)
anatofuz
parents:
diff changeset
238 Decls[I - 1] = Twine(Snippets[0])
anatofuz
parents:
diff changeset
239 .concat(Snippets[0].endswith(" ") ? "" : " ")
anatofuz
parents:
diff changeset
240 .concat(Snippets[I].ltrim())
anatofuz
parents:
diff changeset
241 .concat(";")
anatofuz
parents:
diff changeset
242 .str();
anatofuz
parents:
diff changeset
243
anatofuz
parents:
diff changeset
244 return Decls;
anatofuz
parents:
diff changeset
245 }
anatofuz
parents:
diff changeset
246
anatofuz
parents:
diff changeset
247 void IsolateDeclarationCheck::check(const MatchFinder::MatchResult &Result) {
anatofuz
parents:
diff changeset
248 const auto *WholeDecl = Result.Nodes.getNodeAs<DeclStmt>("decl_stmt");
anatofuz
parents:
diff changeset
249
anatofuz
parents:
diff changeset
250 auto Diag =
anatofuz
parents:
diff changeset
251 diag(WholeDecl->getBeginLoc(),
anatofuz
parents:
diff changeset
252 "multiple declarations in a single statement reduces readability");
anatofuz
parents:
diff changeset
253
anatofuz
parents:
diff changeset
254 Optional<std::vector<SourceRange>> PotentialRanges =
anatofuz
parents:
diff changeset
255 declRanges(WholeDecl, *Result.SourceManager, getLangOpts());
anatofuz
parents:
diff changeset
256 if (!PotentialRanges)
anatofuz
parents:
diff changeset
257 return;
anatofuz
parents:
diff changeset
258
anatofuz
parents:
diff changeset
259 Optional<std::vector<StringRef>> PotentialSnippets = collectSourceRanges(
anatofuz
parents:
diff changeset
260 *PotentialRanges, *Result.SourceManager, getLangOpts());
anatofuz
parents:
diff changeset
261
anatofuz
parents:
diff changeset
262 if (!PotentialSnippets)
anatofuz
parents:
diff changeset
263 return;
anatofuz
parents:
diff changeset
264
anatofuz
parents:
diff changeset
265 std::vector<std::string> NewDecls = createIsolatedDecls(*PotentialSnippets);
anatofuz
parents:
diff changeset
266 std::string Replacement = llvm::join(
anatofuz
parents:
diff changeset
267 NewDecls,
anatofuz
parents:
diff changeset
268 (Twine("\n") + Lexer::getIndentationForLine(WholeDecl->getBeginLoc(),
anatofuz
parents:
diff changeset
269 *Result.SourceManager))
anatofuz
parents:
diff changeset
270 .str());
anatofuz
parents:
diff changeset
271
anatofuz
parents:
diff changeset
272 Diag << FixItHint::CreateReplacement(WholeDecl->getSourceRange(),
anatofuz
parents:
diff changeset
273 Replacement);
anatofuz
parents:
diff changeset
274 }
anatofuz
parents:
diff changeset
275 } // namespace readability
anatofuz
parents:
diff changeset
276 } // namespace tidy
anatofuz
parents:
diff changeset
277 } // namespace clang