clang-tools  8.0.0
ReorderFieldsAction.cpp
Go to the documentation of this file.
1 //===-- 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 "llvm/ADT/SetVector.h"
26 #include <algorithm>
27 #include <string>
28 
29 namespace clang {
30 namespace reorder_fields {
31 using namespace clang::ast_matchers;
32 using llvm::SmallSetVector;
33 
34 /// \brief Finds the definition of a record by name.
35 ///
36 /// \returns nullptr if the name is ambiguous or not found.
37 static const RecordDecl *findDefinition(StringRef RecordName,
38  ASTContext &Context) {
39  auto Results =
40  match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"),
41  Context);
42  if (Results.empty()) {
43  llvm::errs() << "Definition of " << RecordName << " not found\n";
44  return nullptr;
45  }
46  if (Results.size() > 1) {
47  llvm::errs() << "The name " << RecordName
48  << " is ambiguous, several definitions found\n";
49  return nullptr;
50  }
51  return selectFirst<RecordDecl>("recordDecl", Results);
52 }
53 
54 /// \brief Calculates the new order of fields.
55 ///
56 /// \returns empty vector if the list of fields doesn't match the definition.
57 static SmallVector<unsigned, 4>
58 getNewFieldsOrder(const RecordDecl *Definition,
59  ArrayRef<std::string> DesiredFieldsOrder) {
60  assert(Definition && "Definition is null");
61 
62  llvm::StringMap<unsigned> NameToIndex;
63  for (const auto *Field : Definition->fields())
64  NameToIndex[Field->getName()] = Field->getFieldIndex();
65 
66  if (DesiredFieldsOrder.size() != NameToIndex.size()) {
67  llvm::errs() << "Number of provided fields doesn't match definition.\n";
68  return {};
69  }
70  SmallVector<unsigned, 4> NewFieldsOrder;
71  for (const auto &Name : DesiredFieldsOrder) {
72  if (!NameToIndex.count(Name)) {
73  llvm::errs() << "Field " << Name << " not found in definition.\n";
74  return {};
75  }
76  NewFieldsOrder.push_back(NameToIndex[Name]);
77  }
78  assert(NewFieldsOrder.size() == NameToIndex.size());
79  return NewFieldsOrder;
80 }
81 
82 // FIXME: error-handling
83 /// \brief Replaces one range of source code by another.
84 static void
85 addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context,
86  std::map<std::string, tooling::Replacements> &Replacements) {
87  StringRef NewText =
88  Lexer::getSourceText(CharSourceRange::getTokenRange(New),
89  Context.getSourceManager(), Context.getLangOpts());
90  tooling::Replacement R(Context.getSourceManager(),
91  CharSourceRange::getTokenRange(Old), NewText,
92  Context.getLangOpts());
93  consumeError(Replacements[R.getFilePath()].add(R));
94 }
95 
96 /// \brief Find all member fields used in the given init-list initializer expr
97 /// that belong to the same record
98 ///
99 /// \returns a set of field declarations, empty if none were present
100 static SmallSetVector<FieldDecl *, 1>
101 findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer,
102  ASTContext &Context) {
103  SmallSetVector<FieldDecl *, 1> Results;
104  // Note that this does not pick up member fields of base classes since
105  // for those accesses Sema::PerformObjectMemberConversion always inserts an
106  // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the
107  // object expression
108  auto FoundExprs =
109  match(findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME")),
110  *Initializer->getInit(), Context);
111  for (BoundNodes &BN : FoundExprs)
112  if (auto *MemExpr = BN.getNodeAs<MemberExpr>("ME"))
113  if (auto *FD = dyn_cast<FieldDecl>(MemExpr->getMemberDecl()))
114  Results.insert(FD);
115  return Results;
116 }
117 
118 /// \brief Reorders fields in the definition of a struct/class.
119 ///
120 /// At the moment reodering of fields with
121 /// different accesses (public/protected/private) is not supported.
122 /// \returns true on success.
124  const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder,
125  const ASTContext &Context,
126  std::map<std::string, tooling::Replacements> &Replacements) {
127  assert(Definition && "Definition is null");
128 
129  SmallVector<const FieldDecl *, 10> Fields;
130  for (const auto *Field : Definition->fields())
131  Fields.push_back(Field);
132 
133  // Check that the permutation of the fields doesn't change the accesses
134  for (const auto *Field : Definition->fields()) {
135  const auto FieldIndex = Field->getFieldIndex();
136  if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) {
137  llvm::errs() << "Currently reodering of fields with different accesses "
138  "is not supported\n";
139  return false;
140  }
141  }
142 
143  for (const auto *Field : Definition->fields()) {
144  const auto FieldIndex = Field->getFieldIndex();
145  if (FieldIndex == NewFieldsOrder[FieldIndex])
146  continue;
147  addReplacement(Field->getSourceRange(),
148  Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
149  Context, Replacements);
150  }
151  return true;
152 }
153 
154 /// \brief Reorders initializers in a C++ struct/class constructor.
155 ///
156 /// A constructor can have initializers for an arbitrary subset of the class's
157 /// fields. Thus, we need to ensure that we reorder just the initializers that
158 /// are present.
160  const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
161  ASTContext &Context,
162  std::map<std::string, tooling::Replacements> &Replacements) {
163  assert(CtorDecl && "Constructor declaration is null");
164  if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
165  return;
166 
167  // The method FunctionDecl::isThisDeclarationADefinition returns false
168  // for a defaulted function unless that function has been implicitly defined.
169  // Thus this assert needs to be after the previous checks.
170  assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition");
171 
172  SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
173  for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
174  NewFieldsPositions[NewFieldsOrder[i]] = i;
175 
176  SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder;
177  SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder;
178  for (const auto *Initializer : CtorDecl->inits()) {
179  if (!Initializer->isMemberInitializer() || !Initializer->isWritten())
180  continue;
181 
182  // Warn if this reordering violates initialization expr dependencies.
183  const FieldDecl *ThisM = Initializer->getMember();
184  const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context);
185  for (const FieldDecl *UM : UsedMembers) {
186  if (NewFieldsPositions[UM->getFieldIndex()] >
187  NewFieldsPositions[ThisM->getFieldIndex()]) {
188  DiagnosticsEngine &DiagEngine = Context.getDiagnostics();
189  auto Description = ("reordering field " + UM->getName() + " after " +
190  ThisM->getName() + " makes " + UM->getName() +
191  " uninitialized when used in init expression")
192  .str();
193  unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID(
194  DiagnosticIDs::Warning, Description);
195  DiagEngine.Report(Initializer->getSourceLocation(), ID);
196  }
197  }
198 
199  OldWrittenInitializersOrder.push_back(Initializer);
200  NewWrittenInitializersOrder.push_back(Initializer);
201  }
202  auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
203  const CXXCtorInitializer *RHS) {
204  assert(LHS && RHS);
205  return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
206  NewFieldsPositions[RHS->getMember()->getFieldIndex()];
207  };
208  std::sort(std::begin(NewWrittenInitializersOrder),
209  std::end(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,
216  Replacements);
217 }
218 
219 /// \brief Reorders initializers in the brace initialization of an aggregate.
220 ///
221 /// At the moment partial initialization is not supported.
222 /// \returns true on success
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())
231  return true;
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())
238  return true;
239  if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
240  llvm::errs() << "Currently only full initialization is supported\n";
241  return false;
242  }
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);
248  return true;
249 }
250 
251 namespace {
252 class ReorderingConsumer : public ASTConsumer {
253  StringRef RecordName;
254  ArrayRef<std::string> DesiredFieldsOrder;
255  std::map<std::string, tooling::Replacements> &Replacements;
256 
257 public:
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) {}
263 
264  ReorderingConsumer(const ReorderingConsumer &) = delete;
265  ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
266 
267  void HandleTranslationUnit(ASTContext &Context) override {
268  const RecordDecl *RD = findDefinition(RecordName, Context);
269  if (!RD)
270  return;
271  SmallVector<unsigned, 4> NewFieldsOrder =
272  getNewFieldsOrder(RD, DesiredFieldsOrder);
273  if (NewFieldsOrder.empty())
274  return;
275  if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
276  return;
277 
278  // CXXRD will be nullptr if C code (not C++) is being processed.
279  const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
280  if (CXXRD)
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);
285 
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())
292  for (auto Result :
293  match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
294  Context))
296  Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
297  Context, Replacements)) {
298  Replacements.clear();
299  return;
300  }
301  }
302 };
303 } // end anonymous namespace
304 
305 std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
306  return llvm::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder,
307  Replacements);
308 }
309 
310 } // namespace reorder_fields
311 } // namespace clang
static cl::opt< std::string > RecordName("record-name", cl::Required, cl::desc("The name of the struct/class."), cl::cat(ClangReorderFieldsCategory))
static void addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Replaces one range of source code by another.
static bool reorderFieldsInDefinition(const RecordDecl *Definition, ArrayRef< unsigned > NewFieldsOrder, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders fields in the definition of a struct/class.
static const RecordDecl * findDefinition(StringRef RecordName, ASTContext &Context)
Finds the definition of a record by name.
std::unique_ptr< ASTConsumer > newASTConsumer()
std::vector< CodeCompletionResult > Results
This file contains the declarations of the ReorderFieldsAction class and the FieldPosition struct...
static SmallVector< unsigned, 4 > getNewFieldsOrder(const RecordDecl *Definition, ArrayRef< std::string > DesiredFieldsOrder)
Calculates the new order of fields.
static SmallSetVector< FieldDecl *, 1 > findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer, ASTContext &Context)
Find all member fields used in the given init-list initializer expr that belong to the same record...
static constexpr llvm::StringLiteral Name
const Decl * D
Definition: XRefs.cpp:79
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
static bool reorderFieldsInInitListExpr(const InitListExpr *InitListEx, ArrayRef< unsigned > NewFieldsOrder, const ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders initializers in the brace initialization of an aggregate.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
const char * Description
Definition: Dexp.cpp:258
static void reorderFieldsInConstructor(const CXXConstructorDecl *CtorDecl, ArrayRef< unsigned > NewFieldsOrder, ASTContext &Context, std::map< std::string, tooling::Replacements > &Replacements)
Reorders initializers in a C++ struct/class constructor.