clang-tools  14.0.0git
ReorderFieldsAction.cpp
Go to the documentation of this file.
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/Lex/Lexer.h"
23 #include "clang/Tooling/Refactoring.h"
24 #include "llvm/ADT/SetVector.h"
25 #include <algorithm>
26 #include <string>
27 
28 namespace clang {
29 namespace reorder_fields {
30 using namespace clang::ast_matchers;
31 using llvm::SmallSetVector;
32 
33 /// Finds the definition of a record by name.
34 ///
35 /// \returns nullptr if the name is ambiguous or not found.
36 static const RecordDecl *findDefinition(StringRef RecordName,
37  ASTContext &Context) {
38  auto Results =
39  match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"),
40  Context);
41  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  }
50  return selectFirst<RecordDecl>("recordDecl", Results);
51 }
52 
53 /// Calculates the new order of fields.
54 ///
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");
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
82 /// Replaces one range of source code by another.
83 static void
84 addReplacement(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());
92  consumeError(Replacements[std::string(R.getFilePath())].add(R));
93 }
94 
95 /// Find all member fields used in the given init-list initializer expr
96 /// that belong to the same record
97 ///
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
106  // object expression
107  auto FoundExprs = match(
108  traverse(
109  TK_AsIs,
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()))
115  Results.insert(FD);
116  return Results;
117 }
118 
119 /// Reorders fields in the definition of a struct/class.
120 ///
121 /// At the moment reordering of fields with
122 /// different accesses (public/protected/private) is not supported.
123 /// \returns true on success.
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");
129 
130  SmallVector<const FieldDecl *, 10> Fields;
131  for (const auto *Field : Definition->fields())
132  Fields.push_back(Field);
133 
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";
140  return false;
141  }
142  }
143 
144  for (const auto *Field : Definition->fields()) {
145  const auto FieldIndex = Field->getFieldIndex();
146  if (FieldIndex == NewFieldsOrder[FieldIndex])
147  continue;
148  addReplacement(Field->getSourceRange(),
149  Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
150  Context, Replacements);
151  }
152  return true;
153 }
154 
155 /// Reorders initializers in a C++ struct/class constructor.
156 ///
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
159 /// are present.
161  const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder,
162  ASTContext &Context,
163  std::map<std::string, tooling::Replacements> &Replacements) {
164  assert(CtorDecl && "Constructor declaration is null");
165  if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1)
166  return;
167 
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");
172 
173  SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size());
174  for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i)
175  NewFieldsPositions[NewFieldsOrder[i]] = i;
176 
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())
181  continue;
182 
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")
193  .str();
194  unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID(
196  DiagEngine.Report(Initializer->getSourceLocation(), ID);
197  }
198  }
199 
200  OldWrittenInitializersOrder.push_back(Initializer);
201  NewWrittenInitializersOrder.push_back(Initializer);
202  }
203  auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS,
204  const CXXCtorInitializer *RHS) {
205  assert(LHS && RHS);
206  return NewFieldsPositions[LHS->getMember()->getFieldIndex()] <
207  NewFieldsPositions[RHS->getMember()->getFieldIndex()];
208  };
209  std::sort(std::begin(NewWrittenInitializersOrder),
210  std::end(NewWrittenInitializersOrder), ByFieldNewPosition);
211  assert(OldWrittenInitializersOrder.size() ==
212  NewWrittenInitializersOrder.size());
213  for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i)
214  if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i])
215  addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(),
216  NewWrittenInitializersOrder[i]->getSourceRange(), Context,
217  Replacements);
218 }
219 
220 /// Reorders initializers in the brace initialization of an aggregate.
221 ///
222 /// At the moment partial initialization is not supported.
223 /// \returns true on success
225  const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder,
226  const ASTContext &Context,
227  std::map<std::string, tooling::Replacements> &Replacements) {
228  assert(InitListEx && "Init list expression is null");
229  // We care only about InitListExprs which originate from source code.
230  // Implicit InitListExprs are created by the semantic analyzer.
231  if (!InitListEx->isExplicit())
232  return true;
233  // The method InitListExpr::getSyntacticForm may return nullptr indicating
234  // that the current initializer list also serves as its syntactic form.
235  if (const auto *SyntacticForm = InitListEx->getSyntacticForm())
236  InitListEx = SyntacticForm;
237  // If there are no initializers we do not need to change anything.
238  if (!InitListEx->getNumInits())
239  return true;
240  if (InitListEx->getNumInits() != NewFieldsOrder.size()) {
241  llvm::errs() << "Currently only full initialization is supported\n";
242  return false;
243  }
244  for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i)
245  if (i != NewFieldsOrder[i])
246  addReplacement(InitListEx->getInit(i)->getSourceRange(),
247  InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(),
248  Context, Replacements);
249  return true;
250 }
251 
252 namespace {
253 class ReorderingConsumer : public ASTConsumer {
254  StringRef RecordName;
255  ArrayRef<std::string> DesiredFieldsOrder;
256  std::map<std::string, tooling::Replacements> &Replacements;
257 
258 public:
259  ReorderingConsumer(StringRef RecordName,
260  ArrayRef<std::string> DesiredFieldsOrder,
261  std::map<std::string, tooling::Replacements> &Replacements)
262  : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder),
263  Replacements(Replacements) {}
264 
265  ReorderingConsumer(const ReorderingConsumer &) = delete;
266  ReorderingConsumer &operator=(const ReorderingConsumer &) = delete;
267 
268  void HandleTranslationUnit(ASTContext &Context) override {
269  const RecordDecl *RD = findDefinition(RecordName, Context);
270  if (!RD)
271  return;
272  SmallVector<unsigned, 4> NewFieldsOrder =
273  getNewFieldsOrder(RD, DesiredFieldsOrder);
274  if (NewFieldsOrder.empty())
275  return;
276  if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements))
277  return;
278 
279  // CXXRD will be nullptr if C code (not C++) is being processed.
280  const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD);
281  if (CXXRD)
282  for (const auto *C : CXXRD->ctors())
283  if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition()))
284  reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D),
285  NewFieldsOrder, Context, Replacements);
286 
287  // We only need to reorder init list expressions for
288  // plain C structs or C++ aggregate types.
289  // For other types the order of constructor parameters is used,
290  // which we don't change at the moment.
291  // Now (v0) partial initialization is not supported.
292  if (!CXXRD || CXXRD->isAggregate())
293  for (auto Result :
294  match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"),
295  Context))
297  Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder,
298  Context, Replacements)) {
299  Replacements.clear();
300  return;
301  }
302  }
303 };
304 } // end anonymous namespace
305 
306 std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() {
307  return std::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder,
308  Replacements);
309 }
310 
311 } // namespace reorder_fields
312 } // namespace clang
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:109
clang::reorder_fields::reorderFieldsInDefinition
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.
Definition: ReorderFieldsAction.cpp:124
ReorderFieldsAction.h
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::reorder_fields::getNewFieldsOrder
static SmallVector< unsigned, 4 > getNewFieldsOrder(const RecordDecl *Definition, ArrayRef< std::string > DesiredFieldsOrder)
Calculates the new order of fields.
Definition: ReorderFieldsAction.cpp:57
clang::clangd::match
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:94
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
RecordName
static cl::opt< std::string > RecordName("record-name", cl::Required, cl::desc("The name of the struct/class."), cl::cat(ClangReorderFieldsCategory))
Description
const char * Description
Definition: Dexp.cpp:361
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::reorder_fields::findMembersUsedInInitExpr
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.
Definition: ReorderFieldsAction.cpp:100
Results
std::vector< CodeCompletionResult > Results
Definition: CodeComplete.cpp:771
ID
static char ID
Definition: Logger.cpp:74
ASTConsumer
clang::reorder_fields::reorderFieldsInConstructor
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.
Definition: ReorderFieldsAction.cpp:160
C
const Criteria C
Definition: FunctionCognitiveComplexityCheck.cpp:93
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::reorder_fields::ReorderFieldsAction::newASTConsumer
std::unique_ptr< ASTConsumer > newASTConsumer()
Definition: ReorderFieldsAction.cpp:306
clang::reorder_fields::reorderFieldsInInitListExpr
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.
Definition: ReorderFieldsAction.cpp:224
Warning
constexpr static llvm::SourceMgr::DiagKind Warning
Definition: ConfigCompile.cpp:501
clang::reorder_fields::findDefinition
static const RecordDecl * findDefinition(StringRef RecordName, ASTContext &Context)
Finds the definition of a record by name.
Definition: ReorderFieldsAction.cpp:36
clang::reorder_fields::addReplacement
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.
Definition: ReorderFieldsAction.cpp:84