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