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

source code of clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp