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/Basic/LangOptions.h"
23#include "clang/Basic/SourceLocation.h"
24#include "clang/Lex/Lexer.h"
25#include "clang/Tooling/Refactoring.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/ADT/SetVector.h"
28#include <string>
29
30namespace clang {
31namespace reorder_fields {
32using namespace clang::ast_matchers;
33using llvm::SmallSetVector;
34
35/// Finds the definition of a record by name.
36///
37/// \returns nullptr if the name is ambiguous or not found.
38static const RecordDecl *findDefinition(StringRef RecordName,
39 ASTContext &Context) {
40 auto Results =
41 match(Matcher: recordDecl(hasName(Name: RecordName), isDefinition()).bind(ID: "recordDecl"),
42 Context);
43 if (Results.empty()) {
44 llvm::errs() << "Definition of " << RecordName << " not found\n";
45 return nullptr;
46 }
47 if (Results.size() > 1) {
48 llvm::errs() << "The name " << RecordName
49 << " is ambiguous, several definitions found\n";
50 return nullptr;
51 }
52 return selectFirst<RecordDecl>(BoundTo: "recordDecl", Results);
53}
54
55static bool declaresMultipleFieldsInStatement(const RecordDecl *Decl) {
56 SourceLocation LastTypeLoc;
57 for (const auto &Field : Decl->fields()) {
58 SourceLocation TypeLoc =
59 Field->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
60 if (LastTypeLoc.isValid() && TypeLoc == LastTypeLoc)
61 return true;
62 LastTypeLoc = TypeLoc;
63 }
64 return false;
65}
66
67static bool declaresMultipleFieldsInMacro(const RecordDecl *Decl,
68 const SourceManager &SrcMgr) {
69 SourceLocation LastMacroLoc;
70 for (const auto &Field : Decl->fields()) {
71 if (!Field->getLocation().isMacroID())
72 continue;
73 SourceLocation MacroLoc = SrcMgr.getExpansionLoc(Loc: Field->getLocation());
74 if (LastMacroLoc.isValid() && MacroLoc == LastMacroLoc)
75 return true;
76 LastMacroLoc = MacroLoc;
77 }
78 return false;
79}
80
81static bool containsPreprocessorDirectives(const RecordDecl *Decl,
82 const SourceManager &SrcMgr,
83 const LangOptions &LangOpts) {
84 std::pair<FileID, unsigned> FileAndOffset =
85 SrcMgr.getDecomposedLoc(Loc: Decl->field_begin()->getBeginLoc());
86 assert(!Decl->field_empty());
87 auto LastField = Decl->field_begin();
88 while (std::next(x: LastField) != Decl->field_end())
89 ++LastField;
90 unsigned EndOffset = SrcMgr.getFileOffset(SpellingLoc: LastField->getEndLoc());
91 StringRef SrcBuffer = SrcMgr.getBufferData(FID: FileAndOffset.first);
92 Lexer L(SrcMgr.getLocForStartOfFile(FID: FileAndOffset.first), LangOpts,
93 SrcBuffer.data(), SrcBuffer.data() + FileAndOffset.second,
94 SrcBuffer.data() + SrcBuffer.size());
95 IdentifierTable Identifiers(LangOpts);
96 clang::Token T;
97 while (!L.LexFromRawLexer(Result&: T) && L.getCurrentBufferOffset() < EndOffset) {
98 if (T.getKind() == tok::hash) {
99 L.LexFromRawLexer(Result&: T);
100 if (T.getKind() == tok::raw_identifier) {
101 clang::IdentifierInfo &II = Identifiers.get(Name: T.getRawIdentifier());
102 if (II.getPPKeywordID() != clang::tok::pp_not_keyword)
103 return true;
104 }
105 }
106 }
107 return false;
108}
109
110static bool isSafeToRewrite(const RecordDecl *Decl, const ASTContext &Context) {
111 // All following checks expect at least one field declaration.
112 if (Decl->field_empty())
113 return true;
114
115 // Don't attempt to rewrite if there is a declaration like 'int a, b;'.
116 if (declaresMultipleFieldsInStatement(Decl))
117 return false;
118
119 const SourceManager &SrcMgr = Context.getSourceManager();
120
121 // Don't attempt to rewrite if a single macro expansion creates multiple
122 // fields.
123 if (declaresMultipleFieldsInMacro(Decl, SrcMgr))
124 return false;
125
126 // Prevent rewriting if there are preprocessor directives present between the
127 // start of the first field and the end of last field.
128 if (containsPreprocessorDirectives(Decl, SrcMgr, LangOpts: Context.getLangOpts()))
129 return false;
130
131 return true;
132}
133
134/// Calculates the new order of fields.
135///
136/// \returns empty vector if the list of fields doesn't match the definition.
137static SmallVector<unsigned, 4>
138getNewFieldsOrder(const RecordDecl *Definition,
139 ArrayRef<std::string> DesiredFieldsOrder) {
140 assert(Definition && "Definition is null");
141
142 llvm::StringMap<unsigned> NameToIndex;
143 for (const auto *Field : Definition->fields())
144 NameToIndex[Field->getName()] = Field->getFieldIndex();
145
146 if (DesiredFieldsOrder.size() != NameToIndex.size()) {
147 llvm::errs() << "Number of provided fields (" << DesiredFieldsOrder.size()
148 << ") doesn't match definition (" << NameToIndex.size()
149 << ").\n";
150 return {};
151 }
152 SmallVector<unsigned, 4> NewFieldsOrder;
153 for (const auto &Name : DesiredFieldsOrder) {
154 auto It = NameToIndex.find(Key: Name);
155 if (It == NameToIndex.end()) {
156 llvm::errs() << "Field " << Name << " not found in definition.\n";
157 return {};
158 }
159 NewFieldsOrder.push_back(Elt: It->second);
160 }
161 assert(NewFieldsOrder.size() == NameToIndex.size());
162 return NewFieldsOrder;
163}
164
165// FIXME: error-handling
166/// Replaces one range of source code by another.
167static void
168addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
169 std::map<std::string, tooling::Replacements> &Replacements) {
170 if (Old.getBegin().isMacroID())
171 Old = Context.getSourceManager().getExpansionRange(Range: Old).getAsRange();
172 if (New.getBegin().isMacroID())
173 New = Context.getSourceManager().getExpansionRange(Range: New).getAsRange();
174 StringRef NewText =
175 Lexer::getSourceText(Range: CharSourceRange::getTokenRange(R: New),
176 SM: Context.getSourceManager(), LangOpts: Context.getLangOpts());
177 tooling::Replacement R(Context.getSourceManager(),
178 CharSourceRange::getTokenRange(R: Old), NewText,
179 Context.getLangOpts());
180 consumeError(Err: Replacements[std::string(R.getFilePath())].add(R));
181}
182
183/// Find all member fields used in the given init-list initializer expr
184/// that belong to the same record
185///
186/// \returns a set of field declarations, empty if none were present
187static SmallSetVector<FieldDecl *, 1>
188findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer,
189 ASTContext &Context) {
190 SmallSetVector<FieldDecl *, 1> Results;
191 // Note that this does not pick up member fields of base classes since
192 // for those accesses Sema::PerformObjectMemberConversion always inserts an
193 // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the
194 // object expression
195 auto FoundExprs = match(
196 Matcher: traverse(
197 TK: TK_AsIs,
198 InnerMatcher: findAll(Matcher: memberExpr(hasObjectExpression(InnerMatcher: cxxThisExpr())).bind(ID: "ME"))),
199 Node: *Initializer->getInit(), Context);
200 for (BoundNodes &BN : FoundExprs)
201 if (auto *MemExpr = BN.getNodeAs<MemberExpr>(ID: "ME"))
202 if (auto *FD = dyn_cast<FieldDecl>(Val: MemExpr->getMemberDecl()))
203 Results.insert(X: FD);
204 return Results;
205}
206
207/// Returns the start of the leading comments before `Loc`.
208static SourceLocation getStartOfLeadingComment(SourceLocation Loc,
209 const SourceManager &SM,
210 const LangOptions &LangOpts) {
211 // We consider any leading comment token that is on the same line or
212 // indented similarly to the first comment to be part of the leading comment.
213 const unsigned Line = SM.getPresumedLineNumber(Loc);
214 const unsigned Column = SM.getPresumedColumnNumber(Loc);
215 std::optional<Token> Tok =
216 Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
217 while (Tok && Tok->is(K: tok::comment)) {
218 const SourceLocation CommentLoc =
219 Lexer::GetBeginningOfToken(Loc: Tok->getLocation(), SM, LangOpts);
220 if (SM.getPresumedLineNumber(Loc: CommentLoc) != Line &&
221 SM.getPresumedColumnNumber(Loc: CommentLoc) != Column) {
222 break;
223 }
224 Loc = CommentLoc;
225 Tok = Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
226 }
227 return Loc;
228}
229
230/// Returns the end of the trailing comments after `Loc`.
231static SourceLocation getEndOfTrailingComment(SourceLocation Loc,
232 const SourceManager &SM,
233 const LangOptions &LangOpts) {
234 // We consider any following comment token that is indented more than the
235 // first comment to be part of the trailing comment.
236 const unsigned Column = SM.getPresumedColumnNumber(Loc);
237 std::optional<Token> Tok =
238 Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
239 while (Tok && Tok->is(K: tok::comment) &&
240 SM.getPresumedColumnNumber(Loc: Tok->getLocation()) > Column) {
241 Loc = Tok->getEndLoc();
242 Tok = Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true);
243 }
244 return Loc;
245}
246
247/// Returns the full source range for the field declaration up to (including)
248/// the trailing semicolumn, including potential macro invocations,
249/// e.g. `int a GUARDED_BY(mu);`. If there is a trailing comment, include it.
250static SourceRange getFullFieldSourceRange(const FieldDecl &Field,
251 const ASTContext &Context) {
252 const SourceRange Range = Field.getSourceRange();
253 SourceLocation Begin = Range.getBegin();
254 SourceLocation End = Range.getEnd();
255 const SourceManager &SM = Context.getSourceManager();
256 const LangOptions &LangOpts = Context.getLangOpts();
257 while (true) {
258 std::optional<Token> CurrentToken = Lexer::findNextToken(Loc: End, SM, LangOpts);
259
260 if (!CurrentToken)
261 return SourceRange(Begin, End);
262
263 if (CurrentToken->is(K: tok::eof))
264 return Range; // Something is wrong, return the original range.
265
266 End = CurrentToken->getLastLoc();
267
268 if (CurrentToken->is(K: tok::semi))
269 break;
270 }
271 Begin = getStartOfLeadingComment(Loc: Begin, SM, LangOpts);
272 End = getEndOfTrailingComment(Loc: End, SM, LangOpts);
273 return SourceRange(Begin, End);
274}
275
276/// Reorders fields in the definition of a struct/class.
277///
278/// At the moment reordering of fields with
279/// different accesses (public/protected/private) is not supported.
280/// \returns true on success.
281static bool reorderFieldsInDefinition(
282 const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
283 const ASTContext &Context,
284 std::map<std::string, tooling::Replacements> &Replacements) {
285 assert(Definition && "Definition is null");
286
287 SmallVector<const FieldDecl *, 10> Fields;
288 for (const auto *Field : Definition->fields())
289 Fields.push_back(Elt: Field);
290
291 // Check that the permutation of the fields doesn't change the accesses
292 for (const auto *Field : Definition->fields()) {
293 const auto FieldIndex = Field->getFieldIndex();
294 if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
295 llvm::errs() << "Currently reordering of fields with different accesses "
296 "is not supported\n";
297 return false;
298 }
299 }
300
301 for (const auto *Field : Definition->fields()) {
302 const auto FieldIndex = Field->getFieldIndex();
303 if (FieldIndex == NewFieldsOrder[FieldIndex])
304 continue;
305 addReplacement(
306 Old: getFullFieldSourceRange(Field: *Field, Context),
307 New: getFullFieldSourceRange(Field: *Fields[NewFieldsOrder[FieldIndex]], Context),
308 Context, Replacements);
309 }
310 return true;
311}
312
313/// Reorders initializers in a C++ struct/class constructor.
314///
315/// A constructor can have initializers for an arbitrary subset of the class's
316/// fields. Thus, we need to ensure that we reorder just the initializers that
317/// are present.
318static void reorderFieldsInConstructor(
319 const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
320 ASTContext &Context,
321 std::map<std::string, tooling::Replacements> &Replacements) {
322 assert(CtorDecl && "Constructor declaration is null");
323 if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
324 return;
325
326 // The method FunctionDecl::isThisDeclarationADefinition returns false
327 // for a defaulted function unless that function has been implicitly defined.
328 // Thus this assert needs to be after the previous checks.
329 assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
330
331 SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
332 for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
333 NewFieldsPositions[NewFieldsOrder[i]] = i;
334
335 SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
336 SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
337 for (const auto *Initializer : CtorDecl->inits()) {
338 if (!Initializer->isMemberInitializer() || !Initializer->isWritten())
339 continue;
340
341 // Warn if this reordering violates initialization expr dependencies.
342 const FieldDecl *ThisM = Initializer->getMember();
343 const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context);
344 for (const FieldDecl *UM : UsedMembers) {
345 if (NewFieldsPositions[UM->getFieldIndex()] >
346 NewFieldsPositions[ThisM->getFieldIndex()]) {
347 DiagnosticsEngine &DiagEngine = Context.getDiagnostics();
348 auto Description = ("reordering field " + UM->getName() + " after " +
349 ThisM->getName() + " makes " + UM->getName() +
350 " uninitialized when used in init expression")
351 .str();
352 unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID(
353 Level: DiagnosticIDs::Warning, Message: Description);
354 DiagEngine.Report(Loc: Initializer->getSourceLocation(), DiagID: ID);
355 }
356 }
357
358 OldWrittenInitializersOrder.push_back(Elt: Initializer);
359 NewWrittenInitializersOrder.push_back(Elt: Initializer);
360 }
361 auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
362 const CXXCtorInitializer *RHS) {
363 assert(LHS && RHS);
364 return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
365 NewFieldsPositions[RHS->getMember()->getFieldIndex()];
366 };
367 llvm::sort(C&: NewWrittenInitializersOrder, Comp: ByFieldNewPosition);
368 assert(OldWrittenInitializersOrder.size() ==
369 NewWrittenInitializersOrder.size());
370 for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i)
371 if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i])
372 addReplacement(Old: OldWrittenInitializersOrder[i]->getSourceRange(),
373 New: NewWrittenInitializersOrder[i]->getSourceRange(), Context,
374 Replacements);
375}
376
377/// Reorders initializers in the brace initialization of an aggregate.
378///
379/// At the moment partial initialization is not supported.
380/// \returns true on success
381static bool reorderFieldsInInitListExpr(
382 const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
383 const ASTContext &Context,
384 std::map<std::string, tooling::Replacements> &Replacements) {
385 assert(InitListEx && "Init list expression is null");
386 // We care only about InitListExprs which originate from source code.
387 // Implicit InitListExprs are created by the semantic analyzer.
388 if (!InitListEx->isExplicit())
389 return true;
390 // The method InitListExpr::getSyntacticForm may return nullptr indicating
391 // that the current initializer list also serves as its syntactic form.
392 if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
393 InitListEx = SyntacticForm;
394 // If there are no initializers we do not need to change anything.
395 if (!InitListEx->getNumInits())
396 return true;
397 if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
398 llvm::errs() << "Currently only full initialization is supported\n";
399 return false;
400 }
401 for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
402 if (i != NewFieldsOrder[i])
403 addReplacement(Old: InitListEx->getInit(Init: i)->getSourceRange(),
404 New: InitListEx->getInit(Init: NewFieldsOrder[i])->getSourceRange(),
405 Context, Replacements);
406 return true;
407}
408
409namespace {
410class ReorderingConsumer : public ASTConsumer {
411 StringRef RecordName;
412 ArrayRef<std::string> DesiredFieldsOrder;
413 std::map<std::string, tooling::Replacements> &Replacements;
414
415public:
416 ReorderingConsumer(StringRef RecordName,
417 ArrayRef<std::string> DesiredFieldsOrder,
418 std::map<std::string, tooling::Replacements> &Replacements)
419 : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder),
420 Replacements(Replacements) {}
421
422 ReorderingConsumer(const ReorderingConsumer &) = delete;
423 ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
424
425 void HandleTranslationUnit(ASTContext &Context) override {
426 const RecordDecl *RD = findDefinition(RecordName, Context);
427 if (!RD)
428 return;
429 if (!isSafeToRewrite(Decl: RD, Context))
430 return;
431 SmallVector<unsigned, 4> NewFieldsOrder =
432 getNewFieldsOrder(Definition: RD, DesiredFieldsOrder);
433 if (NewFieldsOrder.empty())
434 return;
435 if (!reorderFieldsInDefinition(Definition: RD, NewFieldsOrder, Context, Replacements))
436 return;
437
438 // CXXRD will be nullptr if C code (not C++) is being processed.
439 const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(Val: RD);
440 if (CXXRD)
441 for (const auto *C : CXXRD->ctors())
442 if (const auto *D = dyn_cast<CXXConstructorDecl>(Val: C->getDefinition()))
443 reorderFieldsInConstructor(CtorDecl: cast<const CXXConstructorDecl>(Val: D),
444 NewFieldsOrder, Context, Replacements);
445
446 // We only need to reorder init list expressions for
447 // plain C structs or C++ aggregate types.
448 // For other types the order of constructor parameters is used,
449 // which we don't change at the moment.
450 // Now (v0) partial initialization is not supported.
451 if (!CXXRD || CXXRD->isAggregate())
452 for (auto Result :
453 match(Matcher: initListExpr(hasType(InnerMatcher: equalsNode(Other: RD))).bind(ID: "initListExpr"),
454 Context))
455 if (!reorderFieldsInInitListExpr(
456 InitListEx: Result.getNodeAs<InitListExpr>(ID: "initListExpr"), NewFieldsOrder,
457 Context, Replacements)) {
458 Replacements.clear();
459 return;
460 }
461 }
462};
463} // end anonymous namespace
464
465std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
466 return std::make_unique<ReorderingConsumer>(args&: RecordName, args&: DesiredFieldsOrder,
467 args&: Replacements);
468}
469
470} // namespace reorder_fields
471} // namespace clang
472

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