clang-tools 20.0.0git
ReorderFieldsAction.cpp
Go to the documentation of this file.
1//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===//
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/// \file
10/// This file contains the definition of the
11/// ReorderFieldsAction::newASTConsumer method
12///
13//===----------------------------------------------------------------------===//
14
15#include "ReorderFieldsAction.h"
16#include "clang/AST/AST.h"
17#include "clang/AST/ASTConsumer.h"
18#include "clang/AST/ASTContext.h"
19#include "clang/AST/Decl.h"
20#include "clang/AST/RecursiveASTVisitor.h"
21#include "clang/ASTMatchers/ASTMatchFinder.h"
22#include "clang/Lex/Lexer.h"
23#include "clang/Tooling/Refactoring.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/SetVector.h"
26#include <string>
27
28namespace clang {
29namespace reorder_fields {
30using namespace clang::ast_matchers;
31using llvm::SmallSetVector;
32
33/// Finds the definition of a record by name.
34///
35/// \returns nullptr if the name is ambiguous or not found.
36static const RecordDecl *findDefinition(StringRef RecordName,
37 ASTContext &Context) {
38 auto Results =
39 match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"),
40 Context);
41 if (Results.empty()) {
42 llvm::errs() << "Definition of " << RecordName << " not found\n";
43 return nullptr;
44 }
45 if (Results.size() > 1) {
46 llvm::errs() << "The name " << RecordName
47 << " is ambiguous, several definitions found\n";
48 return nullptr;
49 }
50 return selectFirst<RecordDecl>("recordDecl", Results);
51}
52
53/// Calculates the new order of fields.
54///
55/// \returns empty vector if the list of fields doesn't match the definition.
56static SmallVector<unsigned, 4>
57getNewFieldsOrder(const RecordDecl *Definition,
58 ArrayRef<std::string> DesiredFieldsOrder) {
59 assert(Definition && "Definition is null");
60
61 llvm::StringMap<unsigned> NameToIndex;
62 for (const auto *Field : Definition->fields())
63 NameToIndex[Field->getName()] = Field->getFieldIndex();
64
65 if (DesiredFieldsOrder.size() != NameToIndex.size()) {
66 llvm::errs() << "Number of provided fields (" << DesiredFieldsOrder.size()
67 << ") doesn't match definition (" << NameToIndex.size()
68 << ").\n";
69 return {};
70 }
71 SmallVector<unsigned, 4> NewFieldsOrder;
72 for (const auto &Name : DesiredFieldsOrder) {
73 if (!NameToIndex.count(Name)) {
74 llvm::errs() << "Field " << Name << " not found in definition.\n";
75 return {};
76 }
77 NewFieldsOrder.push_back(NameToIndex[Name]);
78 }
79 assert(NewFieldsOrder.size() == NameToIndex.size());
80 return NewFieldsOrder;
81}
82
83// FIXME: error-handling
84/// Replaces one range of source code by another.
85static void
86addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
87 std::map<std::string, tooling::Replacements> &Replacements) {
88 StringRef NewText =
89 Lexer::getSourceText(CharSourceRange::getTokenRange(New),
90 Context.getSourceManager(), Context.getLangOpts());
91 tooling::Replacement R(Context.getSourceManager(),
92 CharSourceRange::getTokenRange(Old), NewText,
93 Context.getLangOpts());
94 consumeError(Replacements[std::string(R.getFilePath())].add(R));
95}
96
97/// Find all member fields used in the given init-list initializer expr
98/// that belong to the same record
99///
100/// \returns a set of field declarations, empty if none were present
101static SmallSetVector<FieldDecl *, 1>
102findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer,
103 ASTContext &Context) {
104 SmallSetVector<FieldDecl *, 1> Results;
105 // Note that this does not pick up member fields of base classes since
106 // for those accesses Sema::PerformObjectMemberConversion always inserts an
107 // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the
108 // object expression
109 auto FoundExprs = match(
110 traverse(
111 TK_AsIs,
112 findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME"))),
113 *Initializer->getInit(), Context);
114 for (BoundNodes &BN : FoundExprs)
115 if (auto *MemExpr = BN.getNodeAs<MemberExpr>("ME"))
116 if (auto *FD = dyn_cast<FieldDecl>(MemExpr->getMemberDecl()))
117 Results.insert(FD);
118 return Results;
119}
120
121/// Returns the start of the leading comments before `Loc`.
122static SourceLocation getStartOfLeadingComment(SourceLocation Loc,
123 const SourceManager &SM,
124 const LangOptions &LangOpts) {
125 // We consider any leading comment token that is on the same line or
126 // indented similarly to the first comment to be part of the leading comment.
127 const unsigned Line = SM.getPresumedLineNumber(Loc);
128 const unsigned Column = SM.getPresumedColumnNumber(Loc);
129 std::optional<Token> Tok =
130 Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
131 while (Tok && Tok->is(tok::comment)) {
132 const SourceLocation CommentLoc =
133 Lexer::GetBeginningOfToken(Tok->getLocation(), SM, LangOpts);
134 if (SM.getPresumedLineNumber(CommentLoc) != Line &&
135 SM.getPresumedColumnNumber(CommentLoc) != Column) {
136 break;
137 }
138 Loc = CommentLoc;
139 Tok = Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
140 }
141 return Loc;
142}
143
144/// Returns the end of the trailing comments after `Loc`.
145static SourceLocation getEndOfTrailingComment(SourceLocation Loc,
146 const SourceManager &SM,
147 const LangOptions &LangOpts) {
148 // We consider any following comment token that is indented more than the
149 // first comment to be part of the trailing comment.
150 const unsigned Column = SM.getPresumedColumnNumber(Loc);
151 std::optional<Token> Tok =
152 Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
153 while (Tok && Tok->is(tok::comment) &&
154 SM.getPresumedColumnNumber(Tok->getLocation()) > Column) {
155 Loc = Tok->getEndLoc();
156 Tok = Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
157 }
158 return Loc;
159}
160
161/// Returns the full source range for the field declaration up to (including)
162/// the trailing semicolumn, including potential macro invocations,
163/// e.g. `int a GUARDED_BY(mu);`. If there is a trailing comment, include it.
164static SourceRange getFullFieldSourceRange(const FieldDecl &Field,
165 const ASTContext &Context) {
166 const SourceRange Range = Field.getSourceRange();
167 SourceLocation Begin = Range.getBegin();
168 SourceLocation End = Range.getEnd();
169 const SourceManager &SM = Context.getSourceManager();
170 const LangOptions &LangOpts = Context.getLangOpts();
171 while (true) {
172 std::optional<Token> CurrentToken = Lexer::findNextToken(End, SM, LangOpts);
173
174 if (!CurrentToken)
175 return SourceRange(Begin, End);
176
177 if (CurrentToken->is(tok::eof))
178 return Range; // Something is wrong, return the original range.
179
180 End = CurrentToken->getLastLoc();
181
182 if (CurrentToken->is(tok::semi))
183 break;
184 }
185 Begin = getStartOfLeadingComment(Begin, SM, LangOpts);
186 End = getEndOfTrailingComment(End, SM, LangOpts);
187 return SourceRange(Begin, End);
188}
189
190/// Reorders fields in the definition of a struct/class.
191///
192/// At the moment reordering of fields with
193/// different accesses (public/protected/private) is not supported.
194/// \returns true on success.
196 const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
197 const ASTContext &Context,
198 std::map<std::string, tooling::Replacements> &Replacements) {
199 assert(Definition && "Definition is null");
200
201 SmallVector<const FieldDecl *, 10> Fields;
202 for (const auto *Field : Definition->fields())
203 Fields.push_back(Field);
204
205 // Check that the permutation of the fields doesn't change the accesses
206 for (const auto *Field : Definition->fields()) {
207 const auto FieldIndex = Field->getFieldIndex();
208 if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
209 llvm::errs() << "Currently reordering of fields with different accesses "
210 "is not supported\n";
211 return false;
212 }
213 }
214
215 for (const auto *Field : Definition->fields()) {
216 const auto FieldIndex = Field->getFieldIndex();
217 if (FieldIndex == NewFieldsOrder[FieldIndex])
218 continue;
221 getFullFieldSourceRange(*Fields[NewFieldsOrder[FieldIndex]], Context),
222 Context, Replacements);
223 }
224 return true;
225}
226
227/// Reorders initializers in a C++ struct/class constructor.
228///
229/// A constructor can have initializers for an arbitrary subset of the class's
230/// fields. Thus, we need to ensure that we reorder just the initializers that
231/// are present.
233 const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
234 ASTContext &Context,
235 std::map<std::string, tooling::Replacements> &Replacements) {
236 assert(CtorDecl && "Constructor declaration is null");
237 if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
238 return;
239
240 // The method FunctionDecl::isThisDeclarationADefinition returns false
241 // for a defaulted function unless that function has been implicitly defined.
242 // Thus this assert needs to be after the previous checks.
243 assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
244
245 SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
246 for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
247 NewFieldsPositions[NewFieldsOrder[i]] = i;
248
249 SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
250 SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
251 for (const auto *Initializer : CtorDecl->inits()) {
252 if (!Initializer->isMemberInitializer() || !Initializer->isWritten())
253 continue;
254
255 // Warn if this reordering violates initialization expr dependencies.
256 const FieldDecl *ThisM = Initializer->getMember();
257 const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context);
258 for (const FieldDecl *UM : UsedMembers) {
259 if (NewFieldsPositions[UM->getFieldIndex()] >
260 NewFieldsPositions[ThisM->getFieldIndex()]) {
261 DiagnosticsEngine &DiagEngine = Context.getDiagnostics();
262 auto Description = ("reordering field " + UM->getName() + " after " +
263 ThisM->getName() + " makes " + UM->getName() +
264 " uninitialized when used in init expression")
265 .str();
266 unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID(
267 DiagnosticIDs::Warning, Description);
268 DiagEngine.Report(Initializer->getSourceLocation(), ID);
269 }
270 }
271
272 OldWrittenInitializersOrder.push_back(Initializer);
273 NewWrittenInitializersOrder.push_back(Initializer);
274 }
275 auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
276 const CXXCtorInitializer *RHS) {
277 assert(LHS && RHS);
278 return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
279 NewFieldsPositions[RHS->getMember()->getFieldIndex()];
280 };
281 llvm::sort(NewWrittenInitializersOrder, ByFieldNewPosition);
282 assert(OldWrittenInitializersOrder.size() ==
283 NewWrittenInitializersOrder.size());
284 for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i)
285 if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i])
286 addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(),
287 NewWrittenInitializersOrder[i]->getSourceRange(), Context,
288 Replacements);
289}
290
291/// Reorders initializers in the brace initialization of an aggregate.
292///
293/// At the moment partial initialization is not supported.
294/// \returns true on success
296 const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
297 const ASTContext &Context,
298 std::map<std::string, tooling::Replacements> &Replacements) {
299 assert(InitListEx && "Init list expression is null");
300 // We care only about InitListExprs which originate from source code.
301 // Implicit InitListExprs are created by the semantic analyzer.
302 if (!InitListEx->isExplicit())
303 return true;
304 // The method InitListExpr::getSyntacticForm may return nullptr indicating
305 // that the current initializer list also serves as its syntactic form.
306 if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
307 InitListEx = SyntacticForm;
308 // If there are no initializers we do not need to change anything.
309 if (!InitListEx->getNumInits())
310 return true;
311 if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
312 llvm::errs() << "Currently only full initialization is supported\n";
313 return false;
314 }
315 for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
316 if (i != NewFieldsOrder[i])
317 addReplacement(InitListEx->getInit(i)->getSourceRange(),
318 InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(),
319 Context, Replacements);
320 return true;
321}
322
323namespace {
324class ReorderingConsumer : public ASTConsumer {
325 StringRef RecordName;
326 ArrayRef<std::string> DesiredFieldsOrder;
327 std::map<std::string, tooling::Replacements> &Replacements;
328
329public:
330 ReorderingConsumer(StringRef RecordName,
331 ArrayRef<std::string> DesiredFieldsOrder,
332 std::map<std::string, tooling::Replacements> &Replacements)
333 : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder),
334 Replacements(Replacements) {}
335
336 ReorderingConsumer(const ReorderingConsumer &) = delete;
337 ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
338
339 void HandleTranslationUnit(ASTContext &Context) override {
340 const RecordDecl *RD = findDefinition(RecordName, Context);
341 if (!RD)
342 return;
343 SmallVector<unsigned, 4> NewFieldsOrder =
344 getNewFieldsOrder(RD, DesiredFieldsOrder);
345 if (NewFieldsOrder.empty())
346 return;
347 if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
348 return;
349
350 // CXXRD will be nullptr if C code (not C++) is being processed.
351 const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
352 if (CXXRD)
353 for (const auto *C : CXXRD->ctors())
354 if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition()))
355 reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D),
356 NewFieldsOrder, Context, Replacements);
357
358 // We only need to reorder init list expressions for
359 // plain C structs or C++ aggregate types.
360 // For other types the order of constructor parameters is used,
361 // which we don't change at the moment.
362 // Now (v0) partial initialization is not supported.
363 if (!CXXRD || CXXRD->isAggregate())
364 for (auto Result :
365 match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
366 Context))
368 Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
369 Context, Replacements)) {
370 Replacements.clear();
371 return;
372 }
373 }
374};
375} // end anonymous namespace
376
377std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
378 return std::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder,
379 Replacements);
380}
381
382} // namespace reorder_fields
383} // namespace clang
unsigned Line
Definition: Bracket.cpp:77
llvm::SmallString< 256U > Name
std::vector< CodeCompletionResult > Results
const Criteria C
CharSourceRange Range
SourceRange for the file name.
SourceLocation Loc
const FieldDecl * Field
int Column
This file contains the declarations of the ReorderFieldsAction class and the FieldPosition struct.
std::unique_ptr< ASTConsumer > newASTConsumer()
static SourceLocation getStartOfLeadingComment(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Returns the start of the leading comments before Loc.
static void reorderFieldsInConstructor(const CXXConstructorDecl *CtorDecl, ArrayRef< unsigned > NewFieldsOrder, ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders initializers in a C++ struct/class constructor.
static const RecordDecl * findDefinition(StringRef RecordName, ASTContext &Context)
Finds the definition of a record by name.
static bool reorderFieldsInInitListExpr(const InitListExpr *InitListEx, ArrayRef< unsigned > NewFieldsOrder, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders initializers in the brace initialization of an aggregate.
static void addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Replaces one range of source code by another.
static SourceRange getFullFieldSourceRange(const FieldDecl &Field, const ASTContext &Context)
Returns the full source range for the field declaration up to (including) the trailing semicolumn,...
static SourceLocation getEndOfTrailingComment(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Returns the end of the trailing comments after Loc.
static SmallSetVector< FieldDecl *, 1 > findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer, ASTContext &Context)
Find all member fields used in the given init-list initializer expr that belong to the same record.
static bool reorderFieldsInDefinition(const RecordDecl *Definition, ArrayRef< unsigned > NewFieldsOrder, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders fields in the definition of a struct/class.
static SmallVector< unsigned, 4 > getNewFieldsOrder(const RecordDecl *Definition, ArrayRef< std::string > DesiredFieldsOrder)
Calculates the new order of fields.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//