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
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(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
clang::tidy::utils::lexer::rangeContainsExpansionsOrDirectives
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
clang::ast_matchers
Definition: AbseilMatcher.h:15
clang::tidy::matchers::AST_MATCHER
AST_MATCHER(BinaryOperator, isAssignmentOperator)
Definition: Matchers.h:20
clang::tidy::utils::lexer::getPreviousToken
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
clang::tidy::readability::isMacroID
static bool isMacroID(SourceRange R)
Definition: IsolateDeclarationCheck.cpp:53
clang::tidy::utils::lexer::findNextTerminator
SourceLocation findNextTerminator(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:67
clang::tidy::readability::findStartOfIndirection
static SourceLocation findStartOfIndirection(SourceLocation Start, int Indirections, const SourceManager &SM, const LangOptions &LangOpts)
Definition: IsolateDeclarationCheck.cpp:35
clang::tidy::readability::createIsolatedDecls
static std::vector< std::string > createIsolatedDecls(llvm::ArrayRef< StringRef > Snippets)
Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
Definition: IsolateDeclarationCheck.cpp:233
clang::tidy::utils::lexer
Definition: LexerUtils.cpp:15
clang::tidy::readability::collectSourceRanges
static Optional< std::vector< StringRef > > collectSourceRanges(llvm::ArrayRef< SourceRange > Ranges, const SourceManager &SM, const LangOptions &LangOpts)
Definition: IsolateDeclarationCheck.cpp:205
clang::tidy::readability::countIndirections
static int countIndirections(const Type *T, int Indirections=0)
This function counts the number of written indirections for the given Type T.
Definition: IsolateDeclarationCheck.cpp:61
clang::tidy::readability::declRanges
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...
Definition: IsolateDeclarationCheck.cpp:110
D
const Decl * D
Definition: XRefs.cpp:79
Result
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
Definition: ClangdServer.cpp:60
clang::tidy::utils::lexer::findPreviousTokenStart
SourceLocation findPreviousTokenStart(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:34
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ChangeNamespace.cpp:18
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
IsolateDeclarationCheck.h
clang::tidy::utils::lexer::findPreviousAnyTokenKind
SourceLocation findPreviousAnyTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs)
Definition: LexerUtils.h:38
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:82
clang::tidy::utils::lexer::findPreviousTokenKind
SourceLocation findPreviousTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK)
Definition: LexerUtils.cpp:47
clang::tidy::readability::typeIsMemberPointer
static bool typeIsMemberPointer(const Type *T)
Definition: IsolateDeclarationCheck.cpp:82