1 //===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
10 /// This file contains the definition of the
11 /// ReorderFieldsAction::newASTConsumer method
13 //===----------------------------------------------------------------------===//
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"
29 namespace reorder_fields
{
30 using namespace clang::ast_matchers
;
31 using llvm::SmallSetVector
;
33 /// Finds the definition of a record by name.
35 /// \returns nullptr if the name is ambiguous or not found.
36 static const RecordDecl
*findDefinition(StringRef RecordName
,
37 ASTContext
&Context
) {
39 match(recordDecl(hasName(RecordName
), isDefinition()).bind("recordDecl"),
41 if (Results
.empty()) {
42 llvm::errs() << "Definition of " << RecordName
<< " not found\n";
45 if (Results
.size() > 1) {
46 llvm::errs() << "The name " << RecordName
47 << " is ambiguous, several definitions found\n";
50 return selectFirst
<RecordDecl
>("recordDecl", Results
);
53 /// Calculates the new order of fields.
55 /// \returns empty vector if the list of fields doesn't match the definition.
56 static SmallVector
<unsigned, 4>
57 getNewFieldsOrder(const RecordDecl
*Definition
,
58 ArrayRef
<std::string
> DesiredFieldsOrder
) {
59 assert(Definition
&& "Definition is null");
61 llvm::StringMap
<unsigned> NameToIndex
;
62 for (const auto *Field
: Definition
->fields())
63 NameToIndex
[Field
->getName()] = Field
->getFieldIndex();
65 if (DesiredFieldsOrder
.size() != NameToIndex
.size()) {
66 llvm::errs() << "Number of provided fields doesn't match definition.\n";
69 SmallVector
<unsigned, 4> NewFieldsOrder
;
70 for (const auto &Name
: DesiredFieldsOrder
) {
71 if (!NameToIndex
.count(Name
)) {
72 llvm::errs() << "Field " << Name
<< " not found in definition.\n";
75 NewFieldsOrder
.push_back(NameToIndex
[Name
]);
77 assert(NewFieldsOrder
.size() == NameToIndex
.size());
78 return NewFieldsOrder
;
81 // FIXME: error-handling
82 /// Replaces one range of source code by another.
84 addReplacement(SourceRange Old
, SourceRange New
, const ASTContext
&Context
,
85 std::map
<std::string
, tooling::Replacements
> &Replacements
) {
87 Lexer::getSourceText(CharSourceRange::getTokenRange(New
),
88 Context
.getSourceManager(), Context
.getLangOpts());
89 tooling::Replacement
R(Context
.getSourceManager(),
90 CharSourceRange::getTokenRange(Old
), NewText
,
91 Context
.getLangOpts());
92 consumeError(Replacements
[std::string(R
.getFilePath())].add(R
));
95 /// Find all member fields used in the given init-list initializer expr
96 /// that belong to the same record
98 /// \returns a set of field declarations, empty if none were present
99 static SmallSetVector
<FieldDecl
*, 1>
100 findMembersUsedInInitExpr(const CXXCtorInitializer
*Initializer
,
101 ASTContext
&Context
) {
102 SmallSetVector
<FieldDecl
*, 1> Results
;
103 // Note that this does not pick up member fields of base classes since
104 // for those accesses Sema::PerformObjectMemberConversion always inserts an
105 // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the
107 auto FoundExprs
= match(
110 findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME"))),
111 *Initializer
->getInit(), Context
);
112 for (BoundNodes
&BN
: FoundExprs
)
113 if (auto *MemExpr
= BN
.getNodeAs
<MemberExpr
>("ME"))
114 if (auto *FD
= dyn_cast
<FieldDecl
>(MemExpr
->getMemberDecl()))
119 /// Reorders fields in the definition of a struct/class.
121 /// At the moment reordering of fields with
122 /// different accesses (public/protected/private) is not supported.
123 /// \returns true on success.
124 static bool reorderFieldsInDefinition(
125 const RecordDecl
*Definition
, ArrayRef
<unsigned> NewFieldsOrder
,
126 const ASTContext
&Context
,
127 std::map
<std::string
, tooling::Replacements
> &Replacements
) {
128 assert(Definition
&& "Definition is null");
130 SmallVector
<const FieldDecl
*, 10> Fields
;
131 for (const auto *Field
: Definition
->fields())
132 Fields
.push_back(Field
);
134 // Check that the permutation of the fields doesn't change the accesses
135 for (const auto *Field
: Definition
->fields()) {
136 const auto FieldIndex
= Field
->getFieldIndex();
137 if (Field
->getAccess() != Fields
[NewFieldsOrder
[FieldIndex
]]->getAccess()) {
138 llvm::errs() << "Currently reordering of fields with different accesses "
139 "is not supported\n";
144 for (const auto *Field
: Definition
->fields()) {
145 const auto FieldIndex
= Field
->getFieldIndex();
146 if (FieldIndex
== NewFieldsOrder
[FieldIndex
])
148 addReplacement(Field
->getSourceRange(),
149 Fields
[NewFieldsOrder
[FieldIndex
]]->getSourceRange(),
150 Context
, Replacements
);
155 /// Reorders initializers in a C++ struct/class constructor.
157 /// A constructor can have initializers for an arbitrary subset of the class's
158 /// fields. Thus, we need to ensure that we reorder just the initializers that
160 static void reorderFieldsInConstructor(
161 const CXXConstructorDecl
*CtorDecl
, ArrayRef
<unsigned> NewFieldsOrder
,
163 std::map
<std::string
, tooling::Replacements
> &Replacements
) {
164 assert(CtorDecl
&& "Constructor declaration is null");
165 if (CtorDecl
->isImplicit() || CtorDecl
->getNumCtorInitializers() <= 1)
168 // The method FunctionDecl::isThisDeclarationADefinition returns false
169 // for a defaulted function unless that function has been implicitly defined.
170 // Thus this assert needs to be after the previous checks.
171 assert(CtorDecl
->isThisDeclarationADefinition() && "Not a definition");
173 SmallVector
<unsigned, 10> NewFieldsPositions(NewFieldsOrder
.size());
174 for (unsigned i
= 0, e
= NewFieldsOrder
.size(); i
< e
; ++i
)
175 NewFieldsPositions
[NewFieldsOrder
[i
]] = i
;
177 SmallVector
<const CXXCtorInitializer
*, 10> OldWrittenInitializersOrder
;
178 SmallVector
<const CXXCtorInitializer
*, 10> NewWrittenInitializersOrder
;
179 for (const auto *Initializer
: CtorDecl
->inits()) {
180 if (!Initializer
->isMemberInitializer() || !Initializer
->isWritten())
183 // Warn if this reordering violates initialization expr dependencies.
184 const FieldDecl
*ThisM
= Initializer
->getMember();
185 const auto UsedMembers
= findMembersUsedInInitExpr(Initializer
, Context
);
186 for (const FieldDecl
*UM
: UsedMembers
) {
187 if (NewFieldsPositions
[UM
->getFieldIndex()] >
188 NewFieldsPositions
[ThisM
->getFieldIndex()]) {
189 DiagnosticsEngine
&DiagEngine
= Context
.getDiagnostics();
190 auto Description
= ("reordering field " + UM
->getName() + " after " +
191 ThisM
->getName() + " makes " + UM
->getName() +
192 " uninitialized when used in init expression")
194 unsigned ID
= DiagEngine
.getDiagnosticIDs()->getCustomDiagID(
195 DiagnosticIDs::Warning
, Description
);
196 DiagEngine
.Report(Initializer
->getSourceLocation(), ID
);
200 OldWrittenInitializersOrder
.push_back(Initializer
);
201 NewWrittenInitializersOrder
.push_back(Initializer
);
203 auto ByFieldNewPosition
= [&](const CXXCtorInitializer
*LHS
,
204 const CXXCtorInitializer
*RHS
) {
206 return NewFieldsPositions
[LHS
->getMember()->getFieldIndex()] <
207 NewFieldsPositions
[RHS
->getMember()->getFieldIndex()];
209 llvm::sort(NewWrittenInitializersOrder
, ByFieldNewPosition
);
210 assert(OldWrittenInitializersOrder
.size() ==
211 NewWrittenInitializersOrder
.size());
212 for (unsigned i
= 0, e
= NewWrittenInitializersOrder
.size(); i
< e
; ++i
)
213 if (OldWrittenInitializersOrder
[i
] != NewWrittenInitializersOrder
[i
])
214 addReplacement(OldWrittenInitializersOrder
[i
]->getSourceRange(),
215 NewWrittenInitializersOrder
[i
]->getSourceRange(), Context
,
219 /// Reorders initializers in the brace initialization of an aggregate.
221 /// At the moment partial initialization is not supported.
222 /// \returns true on success
223 static bool reorderFieldsInInitListExpr(
224 const InitListExpr
*InitListEx
, ArrayRef
<unsigned> NewFieldsOrder
,
225 const ASTContext
&Context
,
226 std::map
<std::string
, tooling::Replacements
> &Replacements
) {
227 assert(InitListEx
&& "Init list expression is null");
228 // We care only about InitListExprs which originate from source code.
229 // Implicit InitListExprs are created by the semantic analyzer.
230 if (!InitListEx
->isExplicit())
232 // The method InitListExpr::getSyntacticForm may return nullptr indicating
233 // that the current initializer list also serves as its syntactic form.
234 if (const auto *SyntacticForm
= InitListEx
->getSyntacticForm())
235 InitListEx
= SyntacticForm
;
236 // If there are no initializers we do not need to change anything.
237 if (!InitListEx
->getNumInits())
239 if (InitListEx
->getNumInits() != NewFieldsOrder
.size()) {
240 llvm::errs() << "Currently only full initialization is supported\n";
243 for (unsigned i
= 0, e
= InitListEx
->getNumInits(); i
< e
; ++i
)
244 if (i
!= NewFieldsOrder
[i
])
245 addReplacement(InitListEx
->getInit(i
)->getSourceRange(),
246 InitListEx
->getInit(NewFieldsOrder
[i
])->getSourceRange(),
247 Context
, Replacements
);
252 class ReorderingConsumer
: public ASTConsumer
{
253 StringRef RecordName
;
254 ArrayRef
<std::string
> DesiredFieldsOrder
;
255 std::map
<std::string
, tooling::Replacements
> &Replacements
;
258 ReorderingConsumer(StringRef RecordName
,
259 ArrayRef
<std::string
> DesiredFieldsOrder
,
260 std::map
<std::string
, tooling::Replacements
> &Replacements
)
261 : RecordName(RecordName
), DesiredFieldsOrder(DesiredFieldsOrder
),
262 Replacements(Replacements
) {}
264 ReorderingConsumer(const ReorderingConsumer
&) = delete;
265 ReorderingConsumer
&operator=(const ReorderingConsumer
&) = delete;
267 void HandleTranslationUnit(ASTContext
&Context
) override
{
268 const RecordDecl
*RD
= findDefinition(RecordName
, Context
);
271 SmallVector
<unsigned, 4> NewFieldsOrder
=
272 getNewFieldsOrder(RD
, DesiredFieldsOrder
);
273 if (NewFieldsOrder
.empty())
275 if (!reorderFieldsInDefinition(RD
, NewFieldsOrder
, Context
, Replacements
))
278 // CXXRD will be nullptr if C code (not C++) is being processed.
279 const CXXRecordDecl
*CXXRD
= dyn_cast
<CXXRecordDecl
>(RD
);
281 for (const auto *C
: CXXRD
->ctors())
282 if (const auto *D
= dyn_cast
<CXXConstructorDecl
>(C
->getDefinition()))
283 reorderFieldsInConstructor(cast
<const CXXConstructorDecl
>(D
),
284 NewFieldsOrder
, Context
, Replacements
);
286 // We only need to reorder init list expressions for
287 // plain C structs or C++ aggregate types.
288 // For other types the order of constructor parameters is used,
289 // which we don't change at the moment.
290 // Now (v0) partial initialization is not supported.
291 if (!CXXRD
|| CXXRD
->isAggregate())
293 match(initListExpr(hasType(equalsNode(RD
))).bind("initListExpr"),
295 if (!reorderFieldsInInitListExpr(
296 Result
.getNodeAs
<InitListExpr
>("initListExpr"), NewFieldsOrder
,
297 Context
, Replacements
)) {
298 Replacements
.clear();
303 } // end anonymous namespace
305 std::unique_ptr
<ASTConsumer
> ReorderFieldsAction::newASTConsumer() {
306 return std::make_unique
<ReorderingConsumer
>(RecordName
, DesiredFieldsOrder
,
310 } // namespace reorder_fields