1 //===--- 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 
14 using namespace clang::ast_matchers;
15 using namespace clang::tidy::utils::lexer;
16 
17 namespace clang {
18 namespace tidy {
19 namespace readability {
20 
21 namespace {
22 AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); }
23 AST_MATCHER(DeclStmt, onlyDeclaresVariables) {
24   return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); });
25 }
26 } // namespace
27 
28 void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) {
29   Finder->addMatcher(
30       declStmt(allOf(onlyDeclaresVariables(), unless(isSingleDecl()),
31                      hasParent(compoundStmt())))
32           .bind("decl_stmt"),
33       this);
34 }
35 
36 static 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 
54 static 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
62 static 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 
83 static 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.
110 static Optional<std::vector<SourceRange>>
111 declRanges(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 
205 static Optional<std::vector<StringRef>>
206 collectSourceRanges(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, ...}.
233 static std::vector<std::string>
234 createIsolatedDecls(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 
249 void 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
280