clang-tools 20.0.0git
IsolateDeclarationCheck.cpp
Go to the documentation of this file.
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
10#include "../utils/LexerUtils.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include <optional>
13
14using namespace clang::ast_matchers;
15using namespace clang::tidy::utils::lexer;
16
18
19namespace {
20AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); }
21AST_MATCHER(DeclStmt, onlyDeclaresVariables) {
22 return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); });
23}
24} // namespace
25
27 Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()),
28 hasParent(compoundStmt()))
29 .bind("decl_stmt"),
30 this);
31}
32
33static 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
51static 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
59static 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
80static 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.
107static std::optional<std::vector<SourceRange>>
108declRanges(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
202static std::optional<std::vector<StringRef>>
203collectSourceRanges(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, ...}.
230static std::vector<std::string>
231createIsolatedDecls(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
246void 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
const FunctionDecl * Decl
std::string Snippet
NodeType Type
CharSourceRange Range
SourceRange for the file name.
::clang::DynTypedNode Node
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static std::optional< std::vector< StringRef > > collectSourceRanges(llvm::ArrayRef< SourceRange > Ranges, const SourceManager &SM, const LangOptions &LangOpts)
static SourceLocation findStartOfIndirection(SourceLocation Start, int Indirections, const SourceManager &SM, const LangOptions &LangOpts)
static std::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.
static std::vector< std::string > createIsolatedDecls(llvm::ArrayRef< StringRef > Snippets)
Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}.
static int countIndirections(const Type *T, int Indirections=0)
This function counts the number of written indirections for the given Type T.
static bool isMacroID(SourceRange R)
static bool typeIsMemberPointer(const Type *T)
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,...
Definition: LexerUtils.cpp:127
SourceLocation findNextTerminator(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:84
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
Definition: LexerUtils.cpp:41
SourceLocation findPreviousTokenStart(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition: LexerUtils.cpp:48
SourceLocation findPreviousTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK)
Definition: LexerUtils.cpp:61
SourceLocation findPreviousAnyTokenKind(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs)
Definition: LexerUtils.h:44