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