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