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/STLExtras.h" |
25 | #include "llvm/ADT/SetVector.h" |
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(Matcher: recordDecl(hasName(Name: RecordName), isDefinition()).bind(ID: "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>(BoundTo: "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(Key: Name)) { |
72 | llvm::errs() << "Field " << Name << " not found in definition.\n" ; |
73 | return {}; |
74 | } |
75 | NewFieldsOrder.push_back(Elt: 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(Range: CharSourceRange::getTokenRange(R: New), |
88 | SM: Context.getSourceManager(), LangOpts: Context.getLangOpts()); |
89 | tooling::Replacement R(Context.getSourceManager(), |
90 | CharSourceRange::getTokenRange(R: Old), NewText, |
91 | Context.getLangOpts()); |
92 | consumeError(Err: 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 | Matcher: traverse( |
109 | TK: TK_AsIs, |
110 | InnerMatcher: findAll(Matcher: memberExpr(hasObjectExpression(InnerMatcher: cxxThisExpr())).bind(ID: "ME" ))), |
111 | Node: *Initializer->getInit(), Context); |
112 | for (BoundNodes &BN : FoundExprs) |
113 | if (auto *MemExpr = BN.getNodeAs<MemberExpr>(ID: "ME" )) |
114 | if (auto *FD = dyn_cast<FieldDecl>(Val: MemExpr->getMemberDecl())) |
115 | Results.insert(X: 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. |
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" ); |
129 | |
130 | SmallVector<const FieldDecl *, 10> Fields; |
131 | for (const auto *Field : Definition->fields()) |
132 | Fields.push_back(Elt: 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(Old: Field->getSourceRange(), |
149 | New: 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. |
160 | static void reorderFieldsInConstructor( |
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( |
195 | L: DiagnosticIDs::Warning, FormatString: Description); |
196 | DiagEngine.Report(Loc: Initializer->getSourceLocation(), DiagID: ID); |
197 | } |
198 | } |
199 | |
200 | OldWrittenInitializersOrder.push_back(Elt: Initializer); |
201 | NewWrittenInitializersOrder.push_back(Elt: 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 | llvm::sort(C&: NewWrittenInitializersOrder, Comp: 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(Old: OldWrittenInitializersOrder[i]->getSourceRange(), |
215 | New: NewWrittenInitializersOrder[i]->getSourceRange(), Context, |
216 | Replacements); |
217 | } |
218 | |
219 | /// Reorders initializers in the brace initialization of an aggregate. |
220 | /// |
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()) |
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(Init: i)->getSourceRange(), |
246 | InitListEx->getInit(Init: 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(Definition: RD, DesiredFieldsOrder); |
273 | if (NewFieldsOrder.empty()) |
274 | return; |
275 | if (!reorderFieldsInDefinition(Definition: 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>(Val: 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)) |
295 | if (!reorderFieldsInInitListExpr( |
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 std::make_unique<ReorderingConsumer>(args&: RecordName, args&: DesiredFieldsOrder, |
307 | args&: Replacements); |
308 | } |
309 | |
310 | } // namespace reorder_fields |
311 | } // namespace clang |
312 | |