blob: ae22b346500f72aee84541563a9e39b5410d5fbf [file] [log] [blame]
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +00001//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file
11/// This file contains the definition of the
12/// ReorderFieldsAction::newASTConsumer method
13///
14//===----------------------------------------------------------------------===//
15
16#include "ReorderFieldsAction.h"
17#include "clang/AST/AST.h"
18#include "clang/AST/ASTConsumer.h"
19#include "clang/AST/ASTContext.h"
20#include "clang/AST/Decl.h"
21#include "clang/AST/RecursiveASTVisitor.h"
22#include "clang/ASTMatchers/ASTMatchFinder.h"
23#include "clang/Lex/Lexer.h"
24#include "clang/Tooling/Refactoring.h"
25#include <algorithm>
26#include <string>
27
28namespace clang {
29namespace reorder_fields {
30using namespace clang::ast_matchers;
31
32/// \brief Finds the definition of a record by name.
33///
34/// \returns nullptr if the name is ambiguous or not found.
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000035static const RecordDecl *findDefinition(StringRef RecordName,
36 ASTContext &Context) {
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000037 auto Results = match(
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000038 recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"),
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000039 Context);
40 if (Results.empty()) {
41 llvm::errs() << "Definition of " << RecordName << " not found\n";
42 return nullptr;
43 }
44 if (Results.size() > 1) {
45 llvm::errs() << "The name " << RecordName
46 << " is ambiguous, several definitions found\n";
47 return nullptr;
48 }
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000049 return selectFirst<RecordDecl>("recordDecl", Results);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000050}
51
52/// \brief Calculates the new order of fields.
53///
54/// \returns empty vector if the list of fields doesn't match the definition.
55static SmallVector<unsigned, 4>
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +000056getNewFieldsOrder(const RecordDecl *Definition,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +000057 ArrayRef<std::string> DesiredFieldsOrder) {
58 assert(Definition && "Definition is null");
59
60 llvm::StringMap<unsigned> NameToIndex;
61 for (const auto *Field : Definition->fields())
62 NameToIndex[Field->getName()] = Field->getFieldIndex();
63
64 if (DesiredFieldsOrder.size() != NameToIndex.size()) {
65 llvm::errs() << "Number of provided fields doesn't match definition.\n";
66 return {};
67 }
68 SmallVector<unsigned, 4> NewFieldsOrder;
69 for (const auto &Name : DesiredFieldsOrder) {
70 if (!NameToIndex.count(Name)) {
71 llvm::errs() << "Field " << Name << " not found in definition.\n";
72 return {};
73 }
74 NewFieldsOrder.push_back(NameToIndex[Name]);
75 }
76 assert(NewFieldsOrder.size() == NameToIndex.size());
77 return NewFieldsOrder;
78}
79
80// FIXME: error-handling
81/// \brief Replaces one range of source code by another.
82static void
83addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
84 std::map<std::string, tooling::Replacements> &Replacements) {
85 StringRef NewText =
86 Lexer::getSourceText(CharSourceRange::getTokenRange(New),
87 Context.getSourceManager(), Context.getLangOpts());
88 tooling::Replacement R(Context.getSourceManager(),
89 CharSourceRange::getTokenRange(Old), NewText,
90 Context.getLangOpts());
91 consumeError(Replacements[R.getFilePath()].add(R));
92}
93
94/// \brief Reorders fields in the definition of a struct/class.
95///
96/// At the moment reodering of fields with
97/// different accesses (public/protected/private) is not supported.
98/// \returns true on success.
99static bool reorderFieldsInDefinition(
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000100 const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000101 const ASTContext &Context,
102 std::map<std::string, tooling::Replacements> &Replacements) {
103 assert(Definition && "Definition is null");
104
105 SmallVector<const FieldDecl *, 10> Fields;
106 for (const auto *Field : Definition->fields())
107 Fields.push_back(Field);
108
109 // Check that the permutation of the fields doesn't change the accesses
110 for (const auto *Field : Definition->fields()) {
111 const auto FieldIndex = Field->getFieldIndex();
112 if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
113 llvm::errs() << "Currently reodering of fields with different accesses "
114 "is not supported\n";
115 return false;
116 }
117 }
118
119 for (const auto *Field : Definition->fields()) {
120 const auto FieldIndex = Field->getFieldIndex();
121 if (FieldIndex == NewFieldsOrder[FieldIndex])
122 continue;
123 addReplacement(Field->getSourceRange(),
124 Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
125 Context, Replacements);
126 }
127 return true;
128}
129
130/// \brief Reorders initializers in a C++ struct/class constructor.
131///
132/// A constructor can have initializers for an arbitrary subset of the class's fields.
133/// Thus, we need to ensure that we reorder just the initializers that are present.
134static void reorderFieldsInConstructor(
135 const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
136 const ASTContext &Context,
137 std::map<std::string, tooling::Replacements> &Replacements) {
138 assert(CtorDecl && "Constructor declaration is null");
139 if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
140 return;
141
142 // The method FunctionDecl::isThisDeclarationADefinition returns false
143 // for a defaulted function unless that function has been implicitly defined.
144 // Thus this assert needs to be after the previous checks.
145 assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
146
147 SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
148 for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
149 NewFieldsPositions[NewFieldsOrder[i]] = i;
150
151 SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
152 SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
153 for (const auto *Initializer : CtorDecl->inits()) {
154 if (!Initializer->isWritten())
155 continue;
156 OldWrittenInitializersOrder.push_back(Initializer);
157 NewWrittenInitializersOrder.push_back(Initializer);
158 }
159 auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
160 const CXXCtorInitializer *RHS) {
161 assert(LHS && RHS);
162 return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
163 NewFieldsPositions[RHS->getMember()->getFieldIndex()];
164 };
165 std::sort(std::begin(NewWrittenInitializersOrder),
166 std::end(NewWrittenInitializersOrder), ByFieldNewPosition);
167 assert(OldWrittenInitializersOrder.size() ==
168 NewWrittenInitializersOrder.size());
169 for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i)
170 if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i])
171 addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(),
172 NewWrittenInitializersOrder[i]->getSourceRange(), Context,
173 Replacements);
174}
175
176/// \brief Reorders initializers in the brace initialization of an aggregate.
177///
178/// At the moment partial initialization is not supported.
179/// \returns true on success
180static bool reorderFieldsInInitListExpr(
181 const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
182 const ASTContext &Context,
183 std::map<std::string, tooling::Replacements> &Replacements) {
184 assert(InitListEx && "Init list expression is null");
185 // We care only about InitListExprs which originate from source code.
186 // Implicit InitListExprs are created by the semantic analyzer.
187 if (!InitListEx->isExplicit())
188 return true;
189 // The method InitListExpr::getSyntacticForm may return nullptr indicating that
190 // the current initializer list also serves as its syntactic form.
191 if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
192 InitListEx = SyntacticForm;
193 // If there are no initializers we do not need to change anything.
194 if (!InitListEx->getNumInits())
195 return true;
196 if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
197 llvm::errs() << "Currently only full initialization is supported\n";
198 return false;
199 }
200 for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
201 if (i != NewFieldsOrder[i])
202 addReplacement(
203 InitListEx->getInit(i)->getSourceRange(),
204 InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(), Context,
205 Replacements);
206 return true;
207}
208
209namespace {
210class ReorderingConsumer : public ASTConsumer {
211 StringRef RecordName;
212 ArrayRef<std::string> DesiredFieldsOrder;
213 std::map<std::string, tooling::Replacements> &Replacements;
214
215public:
216 ReorderingConsumer(StringRef RecordName,
217 ArrayRef<std::string> DesiredFieldsOrder,
218 std::map<std::string, tooling::Replacements> &Replacements)
219 : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder),
220 Replacements(Replacements) {}
221
222 ReorderingConsumer(const ReorderingConsumer &) = delete;
223 ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
224
225 void HandleTranslationUnit(ASTContext &Context) override {
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000226 const RecordDecl *RD = findDefinition(RecordName, Context);
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000227 if (!RD)
228 return;
229 SmallVector<unsigned, 4> NewFieldsOrder =
230 getNewFieldsOrder(RD, DesiredFieldsOrder);
231 if (NewFieldsOrder.empty())
232 return;
233 if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
234 return;
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000235
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000236 // CXXRD will be nullptr if C code (not C++) is being processed.
237 const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
238 if (CXXRD)
239 for (const auto *C : CXXRD->ctors())
240 if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition()))
241 reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D),
242 NewFieldsOrder, Context, Replacements);
243
244 // We only need to reorder init list expressions for
245 // plain C structs or C++ aggregate types.
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000246 // For other types the order of constructor parameters is used,
247 // which we don't change at the moment.
248 // Now (v0) partial initialization is not supported.
Alexander Shaposhnikoveaf833c2017-07-20 21:41:20 +0000249 if (!CXXRD || CXXRD->isAggregate())
Alexander Shaposhnikovbf3c84c2016-09-02 02:56:07 +0000250 for (auto Result :
251 match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
252 Context))
253 if (!reorderFieldsInInitListExpr(
254 Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
255 Context, Replacements)) {
256 Replacements.clear();
257 return;
258 }
259 }
260};
261} // end anonymous namespace
262
263std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
264 return llvm::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder,
265 Replacements);
266}
267
268} // namespace reorder_fields
269} // namespace clang