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