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 (" << DesiredFieldsOrder.size() |
67 | << ") doesn't match definition (" << NameToIndex.size() |
68 | << ").\n" ; |
69 | return {}; |
70 | } |
71 | SmallVector<unsigned, 4> NewFieldsOrder; |
72 | for (const auto &Name : DesiredFieldsOrder) { |
73 | auto It = NameToIndex.find(Key: Name); |
74 | if (It == NameToIndex.end()) { |
75 | llvm::errs() << "Field " << Name << " not found in definition.\n" ; |
76 | return {}; |
77 | } |
78 | NewFieldsOrder.push_back(Elt: It->second); |
79 | } |
80 | assert(NewFieldsOrder.size() == NameToIndex.size()); |
81 | return NewFieldsOrder; |
82 | } |
83 | |
84 | // FIXME: error-handling |
85 | /// Replaces one range of source code by another. |
86 | static void |
87 | addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, |
88 | std::map<std::string, tooling::Replacements> &Replacements) { |
89 | StringRef NewText = |
90 | Lexer::getSourceText(Range: CharSourceRange::getTokenRange(R: New), |
91 | SM: Context.getSourceManager(), LangOpts: Context.getLangOpts()); |
92 | tooling::Replacement R(Context.getSourceManager(), |
93 | CharSourceRange::getTokenRange(R: Old), NewText, |
94 | Context.getLangOpts()); |
95 | consumeError(Err: Replacements[std::string(R.getFilePath())].add(R)); |
96 | } |
97 | |
98 | /// Find all member fields used in the given init-list initializer expr |
99 | /// that belong to the same record |
100 | /// |
101 | /// \returns a set of field declarations, empty if none were present |
102 | static SmallSetVector<FieldDecl *, 1> |
103 | findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer, |
104 | ASTContext &Context) { |
105 | SmallSetVector<FieldDecl *, 1> Results; |
106 | // Note that this does not pick up member fields of base classes since |
107 | // for those accesses Sema::PerformObjectMemberConversion always inserts an |
108 | // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the |
109 | // object expression |
110 | auto FoundExprs = match( |
111 | Matcher: traverse( |
112 | TK: TK_AsIs, |
113 | InnerMatcher: findAll(Matcher: memberExpr(hasObjectExpression(InnerMatcher: cxxThisExpr())).bind(ID: "ME" ))), |
114 | Node: *Initializer->getInit(), Context); |
115 | for (BoundNodes &BN : FoundExprs) |
116 | if (auto *MemExpr = BN.getNodeAs<MemberExpr>(ID: "ME" )) |
117 | if (auto *FD = dyn_cast<FieldDecl>(Val: MemExpr->getMemberDecl())) |
118 | Results.insert(X: FD); |
119 | return Results; |
120 | } |
121 | |
122 | /// Returns the start of the leading comments before `Loc`. |
123 | static SourceLocation (SourceLocation Loc, |
124 | const SourceManager &SM, |
125 | const LangOptions &LangOpts) { |
126 | // We consider any leading comment token that is on the same line or |
127 | // indented similarly to the first comment to be part of the leading comment. |
128 | const unsigned Line = SM.getPresumedLineNumber(Loc); |
129 | const unsigned Column = SM.getPresumedColumnNumber(Loc); |
130 | std::optional<Token> Tok = |
131 | Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true); |
132 | while (Tok && Tok->is(K: tok::comment)) { |
133 | const SourceLocation = |
134 | Lexer::GetBeginningOfToken(Loc: Tok->getLocation(), SM, LangOpts); |
135 | if (SM.getPresumedLineNumber(Loc: CommentLoc) != Line && |
136 | SM.getPresumedColumnNumber(Loc: CommentLoc) != Column) { |
137 | break; |
138 | } |
139 | Loc = CommentLoc; |
140 | Tok = Lexer::findPreviousToken(Loc, SM, LangOpts, /*IncludeComments=*/true); |
141 | } |
142 | return Loc; |
143 | } |
144 | |
145 | /// Returns the end of the trailing comments after `Loc`. |
146 | static SourceLocation (SourceLocation Loc, |
147 | const SourceManager &SM, |
148 | const LangOptions &LangOpts) { |
149 | // We consider any following comment token that is indented more than the |
150 | // first comment to be part of the trailing comment. |
151 | const unsigned Column = SM.getPresumedColumnNumber(Loc); |
152 | std::optional<Token> Tok = |
153 | Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true); |
154 | while (Tok && Tok->is(K: tok::comment) && |
155 | SM.getPresumedColumnNumber(Loc: Tok->getLocation()) > Column) { |
156 | Loc = Tok->getEndLoc(); |
157 | Tok = Lexer::findNextToken(Loc, SM, LangOpts, /*IncludeComments=*/true); |
158 | } |
159 | return Loc; |
160 | } |
161 | |
162 | /// Returns the full source range for the field declaration up to (including) |
163 | /// the trailing semicolumn, including potential macro invocations, |
164 | /// e.g. `int a GUARDED_BY(mu);`. If there is a trailing comment, include it. |
165 | static SourceRange getFullFieldSourceRange(const FieldDecl &Field, |
166 | const ASTContext &Context) { |
167 | const SourceRange Range = Field.getSourceRange(); |
168 | SourceLocation Begin = Range.getBegin(); |
169 | SourceLocation End = Range.getEnd(); |
170 | const SourceManager &SM = Context.getSourceManager(); |
171 | const LangOptions &LangOpts = Context.getLangOpts(); |
172 | while (true) { |
173 | std::optional<Token> CurrentToken = Lexer::findNextToken(Loc: End, SM, LangOpts); |
174 | |
175 | if (!CurrentToken) |
176 | return SourceRange(Begin, End); |
177 | |
178 | if (CurrentToken->is(K: tok::eof)) |
179 | return Range; // Something is wrong, return the original range. |
180 | |
181 | End = CurrentToken->getLastLoc(); |
182 | |
183 | if (CurrentToken->is(K: tok::semi)) |
184 | break; |
185 | } |
186 | Begin = getStartOfLeadingComment(Loc: Begin, SM, LangOpts); |
187 | End = getEndOfTrailingComment(Loc: End, SM, LangOpts); |
188 | return SourceRange(Begin, End); |
189 | } |
190 | |
191 | /// Reorders fields in the definition of a struct/class. |
192 | /// |
193 | /// At the moment reordering of fields with |
194 | /// different accesses (public/protected/private) is not supported. |
195 | /// \returns true on success. |
196 | static bool reorderFieldsInDefinition( |
197 | const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder, |
198 | const ASTContext &Context, |
199 | std::map<std::string, tooling::Replacements> &Replacements) { |
200 | assert(Definition && "Definition is null" ); |
201 | |
202 | SmallVector<const FieldDecl *, 10> Fields; |
203 | for (const auto *Field : Definition->fields()) |
204 | Fields.push_back(Elt: Field); |
205 | |
206 | // Check that the permutation of the fields doesn't change the accesses |
207 | for (const auto *Field : Definition->fields()) { |
208 | const auto FieldIndex = Field->getFieldIndex(); |
209 | if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) { |
210 | llvm::errs() << "Currently reordering of fields with different accesses " |
211 | "is not supported\n" ; |
212 | return false; |
213 | } |
214 | } |
215 | |
216 | for (const auto *Field : Definition->fields()) { |
217 | const auto FieldIndex = Field->getFieldIndex(); |
218 | if (FieldIndex == NewFieldsOrder[FieldIndex]) |
219 | continue; |
220 | addReplacement( |
221 | Old: getFullFieldSourceRange(Field: *Field, Context), |
222 | New: getFullFieldSourceRange(Field: *Fields[NewFieldsOrder[FieldIndex]], Context), |
223 | Context, Replacements); |
224 | } |
225 | return true; |
226 | } |
227 | |
228 | /// Reorders initializers in a C++ struct/class constructor. |
229 | /// |
230 | /// A constructor can have initializers for an arbitrary subset of the class's |
231 | /// fields. Thus, we need to ensure that we reorder just the initializers that |
232 | /// are present. |
233 | static void reorderFieldsInConstructor( |
234 | const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder, |
235 | ASTContext &Context, |
236 | std::map<std::string, tooling::Replacements> &Replacements) { |
237 | assert(CtorDecl && "Constructor declaration is null" ); |
238 | if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1) |
239 | return; |
240 | |
241 | // The method FunctionDecl::isThisDeclarationADefinition returns false |
242 | // for a defaulted function unless that function has been implicitly defined. |
243 | // Thus this assert needs to be after the previous checks. |
244 | assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition" ); |
245 | |
246 | SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size()); |
247 | for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i) |
248 | NewFieldsPositions[NewFieldsOrder[i]] = i; |
249 | |
250 | SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder; |
251 | SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder; |
252 | for (const auto *Initializer : CtorDecl->inits()) { |
253 | if (!Initializer->isMemberInitializer() || !Initializer->isWritten()) |
254 | continue; |
255 | |
256 | // Warn if this reordering violates initialization expr dependencies. |
257 | const FieldDecl *ThisM = Initializer->getMember(); |
258 | const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context); |
259 | for (const FieldDecl *UM : UsedMembers) { |
260 | if (NewFieldsPositions[UM->getFieldIndex()] > |
261 | NewFieldsPositions[ThisM->getFieldIndex()]) { |
262 | DiagnosticsEngine &DiagEngine = Context.getDiagnostics(); |
263 | auto Description = ("reordering field " + UM->getName() + " after " + |
264 | ThisM->getName() + " makes " + UM->getName() + |
265 | " uninitialized when used in init expression" ) |
266 | .str(); |
267 | unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID( |
268 | DiagnosticIDs::Warning, Description); |
269 | DiagEngine.Report(Loc: Initializer->getSourceLocation(), DiagID: ID); |
270 | } |
271 | } |
272 | |
273 | OldWrittenInitializersOrder.push_back(Elt: Initializer); |
274 | NewWrittenInitializersOrder.push_back(Elt: Initializer); |
275 | } |
276 | auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS, |
277 | const CXXCtorInitializer *RHS) { |
278 | assert(LHS && RHS); |
279 | return NewFieldsPositions[LHS->getMember()->getFieldIndex()] < |
280 | NewFieldsPositions[RHS->getMember()->getFieldIndex()]; |
281 | }; |
282 | llvm::sort(C&: NewWrittenInitializersOrder, Comp: ByFieldNewPosition); |
283 | assert(OldWrittenInitializersOrder.size() == |
284 | NewWrittenInitializersOrder.size()); |
285 | for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i) |
286 | if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i]) |
287 | addReplacement(Old: OldWrittenInitializersOrder[i]->getSourceRange(), |
288 | New: NewWrittenInitializersOrder[i]->getSourceRange(), Context, |
289 | Replacements); |
290 | } |
291 | |
292 | /// Reorders initializers in the brace initialization of an aggregate. |
293 | /// |
294 | /// At the moment partial initialization is not supported. |
295 | /// \returns true on success |
296 | static bool reorderFieldsInInitListExpr( |
297 | const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder, |
298 | const ASTContext &Context, |
299 | std::map<std::string, tooling::Replacements> &Replacements) { |
300 | assert(InitListEx && "Init list expression is null" ); |
301 | // We care only about InitListExprs which originate from source code. |
302 | // Implicit InitListExprs are created by the semantic analyzer. |
303 | if (!InitListEx->isExplicit()) |
304 | return true; |
305 | // The method InitListExpr::getSyntacticForm may return nullptr indicating |
306 | // that the current initializer list also serves as its syntactic form. |
307 | if (const auto *SyntacticForm = InitListEx->getSyntacticForm()) |
308 | InitListEx = SyntacticForm; |
309 | // If there are no initializers we do not need to change anything. |
310 | if (!InitListEx->getNumInits()) |
311 | return true; |
312 | if (InitListEx->getNumInits() != NewFieldsOrder.size()) { |
313 | llvm::errs() << "Currently only full initialization is supported\n" ; |
314 | return false; |
315 | } |
316 | for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i) |
317 | if (i != NewFieldsOrder[i]) |
318 | addReplacement(InitListEx->getInit(Init: i)->getSourceRange(), |
319 | InitListEx->getInit(Init: NewFieldsOrder[i])->getSourceRange(), |
320 | Context, Replacements); |
321 | return true; |
322 | } |
323 | |
324 | namespace { |
325 | class ReorderingConsumer : public ASTConsumer { |
326 | StringRef RecordName; |
327 | ArrayRef<std::string> DesiredFieldsOrder; |
328 | std::map<std::string, tooling::Replacements> &Replacements; |
329 | |
330 | public: |
331 | ReorderingConsumer(StringRef RecordName, |
332 | ArrayRef<std::string> DesiredFieldsOrder, |
333 | std::map<std::string, tooling::Replacements> &Replacements) |
334 | : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), |
335 | Replacements(Replacements) {} |
336 | |
337 | ReorderingConsumer(const ReorderingConsumer &) = delete; |
338 | ReorderingConsumer &operator=(const ReorderingConsumer &) = delete; |
339 | |
340 | void HandleTranslationUnit(ASTContext &Context) override { |
341 | const RecordDecl *RD = findDefinition(RecordName, Context); |
342 | if (!RD) |
343 | return; |
344 | SmallVector<unsigned, 4> NewFieldsOrder = |
345 | getNewFieldsOrder(Definition: RD, DesiredFieldsOrder); |
346 | if (NewFieldsOrder.empty()) |
347 | return; |
348 | if (!reorderFieldsInDefinition(Definition: RD, NewFieldsOrder, Context, Replacements)) |
349 | return; |
350 | |
351 | // CXXRD will be nullptr if C code (not C++) is being processed. |
352 | const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(Val: RD); |
353 | if (CXXRD) |
354 | for (const auto *C : CXXRD->ctors()) |
355 | if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition())) |
356 | reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D), |
357 | NewFieldsOrder, Context, Replacements); |
358 | |
359 | // We only need to reorder init list expressions for |
360 | // plain C structs or C++ aggregate types. |
361 | // For other types the order of constructor parameters is used, |
362 | // which we don't change at the moment. |
363 | // Now (v0) partial initialization is not supported. |
364 | if (!CXXRD || CXXRD->isAggregate()) |
365 | for (auto Result : |
366 | match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr" ), |
367 | Context)) |
368 | if (!reorderFieldsInInitListExpr( |
369 | Result.getNodeAs<InitListExpr>("initListExpr" ), NewFieldsOrder, |
370 | Context, Replacements)) { |
371 | Replacements.clear(); |
372 | return; |
373 | } |
374 | } |
375 | }; |
376 | } // end anonymous namespace |
377 | |
378 | std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() { |
379 | return std::make_unique<ReorderingConsumer>(args&: RecordName, args&: DesiredFieldsOrder, |
380 | args&: Replacements); |
381 | } |
382 | |
383 | } // namespace reorder_fields |
384 | } // namespace clang |
385 | |