blob: c17a43c73f95100eade4e65389babf96ad5b6388 [file] [log] [blame]
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +00001//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +00006//
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"
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +000024#include "llvm/ADT/SetVector.h"
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000025#include <algorithm>
26#include <string>
27
28namespace clang {
29namespace reorder_fields {
30using namespace clang::ast_matchers;
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +000031using llvm::SmallSetVector;
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000032
Dmitri Gribenko282dc722019-08-22 11:32:57 +000033/// Finds the definition of a record by name.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000034///
35/// \returns nullptr if the name is ambiguous or not found.
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000036static const RecordDecl *findDefinition(StringRef RecordName,
37 ASTContext &Context) {
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +000038 auto Results =
Benjamin Kramer4e3f4f02020-01-29 10:52:25 +010039 match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"),
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +000040 Context);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000041 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 }
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000050 return selectFirst<RecordDecl>("recordDecl", Results);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000051}
52
Dmitri Gribenko282dc722019-08-22 11:32:57 +000053/// Calculates the new order of fields.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000054///
55/// \returns empty vector if the list of fields doesn't match the definition.
56static SmallVector<unsigned, 4>
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000057getNewFieldsOrder(const RecordDecl *Definition,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000058 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 doesn't match definition.\n";
67 return {};
68 }
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";
73 return {};
74 }
75 NewFieldsOrder.push_back(NameToIndex[Name]);
76 }
77 assert(NewFieldsOrder.size() == NameToIndex.size());
78 return NewFieldsOrder;
79}
80
81// FIXME: error-handling
Dmitri Gribenko282dc722019-08-22 11:32:57 +000082/// Replaces one range of source code by another.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000083static void
84addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
85 std::map<std::string, tooling::Replacements> &Replacements) {
86 StringRef NewText =
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());
Benjamin Krameradcd0262020-01-28 20:23:46 +010092 consumeError(Replacements[std::string(R.getFilePath())].add(R));
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000093}
94
Dmitri Gribenko282dc722019-08-22 11:32:57 +000095/// Find all member fields used in the given init-list initializer expr
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +000096/// that belong to the same record
97///
98/// \returns a set of field declarations, empty if none were present
99static SmallSetVector<FieldDecl *, 1>
100findMembersUsedInInitExpr(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
106 // object expression
107 auto FoundExprs =
108 match(findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME")),
109 *Initializer->getInit(), Context);
110 for (BoundNodes &BN : FoundExprs)
111 if (auto *MemExpr = BN.getNodeAs<MemberExpr>("ME"))
112 if (auto *FD = dyn_cast<FieldDecl>(MemExpr->getMemberDecl()))
113 Results.insert(FD);
114 return Results;
115}
116
Dmitri Gribenko282dc722019-08-22 11:32:57 +0000117/// Reorders fields in the definition of a struct/class.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000118///
Kazuaki Ishizakidd5571d2020-04-05 15:28:11 +0900119/// At the moment reordering of fields with
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000120/// different accesses (public/protected/private) is not supported.
121/// \returns true on success.
122static bool reorderFieldsInDefinition(
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000123 const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000124 const ASTContext &Context,
125 std::map<std::string, tooling::Replacements> &Replacements) {
126 assert(Definition && "Definition is null");
127
128 SmallVector<const FieldDecl *, 10> Fields;
129 for (const auto *Field : Definition->fields())
130 Fields.push_back(Field);
131
132 // Check that the permutation of the fields doesn't change the accesses
133 for (const auto *Field : Definition->fields()) {
134 const auto FieldIndex = Field->getFieldIndex();
135 if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
Kazuaki Ishizakidd5571d2020-04-05 15:28:11 +0900136 llvm::errs() << "Currently reordering of fields with different accesses "
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000137 "is not supported\n";
138 return false;
139 }
140 }
141
142 for (const auto *Field : Definition->fields()) {
143 const auto FieldIndex = Field->getFieldIndex();
144 if (FieldIndex == NewFieldsOrder[FieldIndex])
145 continue;
146 addReplacement(Field->getSourceRange(),
147 Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
148 Context, Replacements);
149 }
150 return true;
151}
152
Dmitri Gribenko282dc722019-08-22 11:32:57 +0000153/// Reorders initializers in a C++ struct/class constructor.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000154///
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000155/// A constructor can have initializers for an arbitrary subset of the class's
156/// fields. Thus, we need to ensure that we reorder just the initializers that
157/// are present.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000158static void reorderFieldsInConstructor(
159 const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000160 ASTContext &Context,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000161 std::map<std::string, tooling::Replacements> &Replacements) {
162 assert(CtorDecl && "Constructor declaration is null");
163 if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
164 return;
165
166 // The method FunctionDecl::isThisDeclarationADefinition returns false
167 // for a defaulted function unless that function has been implicitly defined.
168 // Thus this assert needs to be after the previous checks.
169 assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
170
171 SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
172 for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
173 NewFieldsPositions[NewFieldsOrder[i]] = i;
174
175 SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
176 SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
177 for (const auto *Initializer : CtorDecl->inits()) {
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000178 if (!Initializer->isMemberInitializer() || !Initializer->isWritten())
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000179 continue;
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000180
181 // Warn if this reordering violates initialization expr dependencies.
182 const FieldDecl *ThisM = Initializer->getMember();
183 const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context);
184 for (const FieldDecl *UM : UsedMembers) {
185 if (NewFieldsPositions[UM->getFieldIndex()] >
186 NewFieldsPositions[ThisM->getFieldIndex()]) {
187 DiagnosticsEngine &DiagEngine = Context.getDiagnostics();
188 auto Description = ("reordering field " + UM->getName() + " after " +
189 ThisM->getName() + " makes " + UM->getName() +
190 " uninitialized when used in init expression")
191 .str();
192 unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID(
193 DiagnosticIDs::Warning, Description);
194 DiagEngine.Report(Initializer->getSourceLocation(), ID);
195 }
196 }
197
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000198 OldWrittenInitializersOrder.push_back(Initializer);
199 NewWrittenInitializersOrder.push_back(Initializer);
200 }
201 auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
202 const CXXCtorInitializer *RHS) {
203 assert(LHS && RHS);
204 return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
205 NewFieldsPositions[RHS->getMember()->getFieldIndex()];
206 };
207 std::sort(std::begin(NewWrittenInitializersOrder),
208 std::end(NewWrittenInitializersOrder), ByFieldNewPosition);
209 assert(OldWrittenInitializersOrder.size() ==
210 NewWrittenInitializersOrder.size());
211 for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i)
212 if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i])
213 addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(),
214 NewWrittenInitializersOrder[i]->getSourceRange(), Context,
215 Replacements);
216}
217
Dmitri Gribenko282dc722019-08-22 11:32:57 +0000218/// Reorders initializers in the brace initialization of an aggregate.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000219///
220/// At the moment partial initialization is not supported.
221/// \returns true on success
222static bool reorderFieldsInInitListExpr(
223 const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
224 const ASTContext &Context,
225 std::map<std::string, tooling::Replacements> &Replacements) {
226 assert(InitListEx && "Init list expression is null");
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000227 // We care only about InitListExprs which originate from source code.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000228 // Implicit InitListExprs are created by the semantic analyzer.
229 if (!InitListEx->isExplicit())
230 return true;
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000231 // The method InitListExpr::getSyntacticForm may return nullptr indicating
232 // that the current initializer list also serves as its syntactic form.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000233 if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
234 InitListEx = SyntacticForm;
235 // If there are no initializers we do not need to change anything.
236 if (!InitListEx->getNumInits())
237 return true;
238 if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
239 llvm::errs() << "Currently only full initialization is supported\n";
240 return false;
241 }
242 for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
243 if (i != NewFieldsOrder[i])
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000244 addReplacement(InitListEx->getInit(i)->getSourceRange(),
245 InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(),
246 Context, Replacements);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000247 return true;
248}
249
250namespace {
251class ReorderingConsumer : public ASTConsumer {
252 StringRef RecordName;
253 ArrayRef<std::string> DesiredFieldsOrder;
254 std::map<std::string, tooling::Replacements> &Replacements;
255
256public:
257 ReorderingConsumer(StringRef RecordName,
258 ArrayRef<std::string> DesiredFieldsOrder,
259 std::map<std::string, tooling::Replacements> &Replacements)
260 : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder),
261 Replacements(Replacements) {}
262
263 ReorderingConsumer(const ReorderingConsumer &) = delete;
264 ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
265
266 void HandleTranslationUnit(ASTContext &Context) override {
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000267 const RecordDecl *RD = findDefinition(RecordName, Context);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000268 if (!RD)
269 return;
270 SmallVector<unsigned, 4> NewFieldsOrder =
271 getNewFieldsOrder(RD, DesiredFieldsOrder);
272 if (NewFieldsOrder.empty())
273 return;
274 if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
275 return;
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000276
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000277 // CXXRD will be nullptr if C code (not C++) is being processed.
278 const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
279 if (CXXRD)
280 for (const auto *C : CXXRD->ctors())
281 if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition()))
282 reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D),
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000283 NewFieldsOrder, Context, Replacements);
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000284
Alexander Shaposhnikovb687fdd2017-07-30 06:43:03 +0000285 // We only need to reorder init list expressions for
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000286 // plain C structs or C++ aggregate types.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000287 // For other types the order of constructor parameters is used,
288 // which we don't change at the moment.
289 // Now (v0) partial initialization is not supported.
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000290 if (!CXXRD || CXXRD->isAggregate())
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000291 for (auto Result :
292 match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
293 Context))
294 if (!reorderFieldsInInitListExpr(
295 Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
296 Context, Replacements)) {
297 Replacements.clear();
298 return;
299 }
300 }
301};
302} // end anonymous namespace
303
304std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
Jonas Devlieghere1c705d92019-08-14 23:52:23 +0000305 return std::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000306 Replacements);
307}
308
309} // namespace reorder_fields
310} // namespace clang