| //===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file contains the definition of the |
| /// ReorderFieldsAction::newASTConsumer method |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "ReorderFieldsAction.h" |
| #include "clang/AST/AST.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Lex/Lexer.h" |
| #include "clang/Tooling/Refactoring.h" |
| #include "llvm/ADT/SetVector.h" |
| #include <algorithm> |
| #include <string> |
| |
| namespace clang { |
| namespace reorder_fields { |
| using namespace clang::ast_matchers; |
| using llvm::SmallSetVector; |
| |
| /// \brief Finds the definition of a record by name. |
| /// |
| /// \returns nullptr if the name is ambiguous or not found. |
| static const RecordDecl *findDefinition(StringRef RecordName, |
| ASTContext &Context) { |
| auto Results = |
| match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"), |
| Context); |
| if (Results.empty()) { |
| llvm::errs() << "Definition of " << RecordName << " not found\n"; |
| return nullptr; |
| } |
| if (Results.size() > 1) { |
| llvm::errs() << "The name " << RecordName |
| << " is ambiguous, several definitions found\n"; |
| return nullptr; |
| } |
| return selectFirst<RecordDecl>("recordDecl", Results); |
| } |
| |
| /// \brief Calculates the new order of fields. |
| /// |
| /// \returns empty vector if the list of fields doesn't match the definition. |
| static SmallVector<unsigned, 4> |
| getNewFieldsOrder(const RecordDecl *Definition, |
| ArrayRef<std::string> DesiredFieldsOrder) { |
| assert(Definition && "Definition is null"); |
| |
| llvm::StringMap<unsigned> NameToIndex; |
| for (const auto *Field : Definition->fields()) |
| NameToIndex[Field->getName()] = Field->getFieldIndex(); |
| |
| if (DesiredFieldsOrder.size() != NameToIndex.size()) { |
| llvm::errs() << "Number of provided fields doesn't match definition.\n"; |
| return {}; |
| } |
| SmallVector<unsigned, 4> NewFieldsOrder; |
| for (const auto &Name : DesiredFieldsOrder) { |
| if (!NameToIndex.count(Name)) { |
| llvm::errs() << "Field " << Name << " not found in definition.\n"; |
| return {}; |
| } |
| NewFieldsOrder.push_back(NameToIndex[Name]); |
| } |
| assert(NewFieldsOrder.size() == NameToIndex.size()); |
| return NewFieldsOrder; |
| } |
| |
| // FIXME: error-handling |
| /// \brief Replaces one range of source code by another. |
| static void |
| addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, |
| std::map<std::string, tooling::Replacements> &Replacements) { |
| StringRef NewText = |
| Lexer::getSourceText(CharSourceRange::getTokenRange(New), |
| Context.getSourceManager(), Context.getLangOpts()); |
| tooling::Replacement R(Context.getSourceManager(), |
| CharSourceRange::getTokenRange(Old), NewText, |
| Context.getLangOpts()); |
| consumeError(Replacements[R.getFilePath()].add(R)); |
| } |
| |
| /// \brief Find all member fields used in the given init-list initializer expr |
| /// that belong to the same record |
| /// |
| /// \returns a set of field declarations, empty if none were present |
| static SmallSetVector<FieldDecl *, 1> |
| findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer, |
| ASTContext &Context) { |
| SmallSetVector<FieldDecl *, 1> Results; |
| // Note that this does not pick up member fields of base classes since |
| // for those accesses Sema::PerformObjectMemberConversion always inserts an |
| // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the |
| // object expression |
| auto FoundExprs = |
| match(findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME")), |
| *Initializer->getInit(), Context); |
| for (BoundNodes &BN : FoundExprs) |
| if (auto *MemExpr = BN.getNodeAs<MemberExpr>("ME")) |
| if (auto *FD = dyn_cast<FieldDecl>(MemExpr->getMemberDecl())) |
| Results.insert(FD); |
| return Results; |
| } |
| |
| /// \brief Reorders fields in the definition of a struct/class. |
| /// |
| /// At the moment reodering of fields with |
| /// different accesses (public/protected/private) is not supported. |
| /// \returns true on success. |
| static bool reorderFieldsInDefinition( |
| const RecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder, |
| const ASTContext &Context, |
| std::map<std::string, tooling::Replacements> &Replacements) { |
| assert(Definition && "Definition is null"); |
| |
| SmallVector<const FieldDecl *, 10> Fields; |
| for (const auto *Field : Definition->fields()) |
| Fields.push_back(Field); |
| |
| // Check that the permutation of the fields doesn't change the accesses |
| for (const auto *Field : Definition->fields()) { |
| const auto FieldIndex = Field->getFieldIndex(); |
| if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) { |
| llvm::errs() << "Currently reodering of fields with different accesses " |
| "is not supported\n"; |
| return false; |
| } |
| } |
| |
| for (const auto *Field : Definition->fields()) { |
| const auto FieldIndex = Field->getFieldIndex(); |
| if (FieldIndex == NewFieldsOrder[FieldIndex]) |
| continue; |
| addReplacement(Field->getSourceRange(), |
| Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(), |
| Context, Replacements); |
| } |
| return true; |
| } |
| |
| /// \brief Reorders initializers in a C++ struct/class constructor. |
| /// |
| /// A constructor can have initializers for an arbitrary subset of the class's |
| /// fields. Thus, we need to ensure that we reorder just the initializers that |
| /// are present. |
| static void reorderFieldsInConstructor( |
| const CXXConstructorDecl *CtorDecl, ArrayRef<unsigned> NewFieldsOrder, |
| ASTContext &Context, |
| std::map<std::string, tooling::Replacements> &Replacements) { |
| assert(CtorDecl && "Constructor declaration is null"); |
| if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1) |
| return; |
| |
| // The method FunctionDecl::isThisDeclarationADefinition returns false |
| // for a defaulted function unless that function has been implicitly defined. |
| // Thus this assert needs to be after the previous checks. |
| assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition"); |
| |
| SmallVector<unsigned, 10> NewFieldsPositions(NewFieldsOrder.size()); |
| for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i) |
| NewFieldsPositions[NewFieldsOrder[i]] = i; |
| |
| SmallVector<const CXXCtorInitializer *, 10> OldWrittenInitializersOrder; |
| SmallVector<const CXXCtorInitializer *, 10> NewWrittenInitializersOrder; |
| for (const auto *Initializer : CtorDecl->inits()) { |
| if (!Initializer->isMemberInitializer() || !Initializer->isWritten()) |
| continue; |
| |
| // Warn if this reordering violates initialization expr dependencies. |
| const FieldDecl *ThisM = Initializer->getMember(); |
| const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context); |
| for (const FieldDecl *UM : UsedMembers) { |
| if (NewFieldsPositions[UM->getFieldIndex()] > |
| NewFieldsPositions[ThisM->getFieldIndex()]) { |
| DiagnosticsEngine &DiagEngine = Context.getDiagnostics(); |
| auto Description = ("reordering field " + UM->getName() + " after " + |
| ThisM->getName() + " makes " + UM->getName() + |
| " uninitialized when used in init expression") |
| .str(); |
| unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID( |
| DiagnosticIDs::Warning, Description); |
| DiagEngine.Report(Initializer->getSourceLocation(), ID); |
| } |
| } |
| |
| OldWrittenInitializersOrder.push_back(Initializer); |
| NewWrittenInitializersOrder.push_back(Initializer); |
| } |
| auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS, |
| const CXXCtorInitializer *RHS) { |
| assert(LHS && RHS); |
| return NewFieldsPositions[LHS->getMember()->getFieldIndex()] < |
| NewFieldsPositions[RHS->getMember()->getFieldIndex()]; |
| }; |
| std::sort(std::begin(NewWrittenInitializersOrder), |
| std::end(NewWrittenInitializersOrder), ByFieldNewPosition); |
| assert(OldWrittenInitializersOrder.size() == |
| NewWrittenInitializersOrder.size()); |
| for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i) |
| if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i]) |
| addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(), |
| NewWrittenInitializersOrder[i]->getSourceRange(), Context, |
| Replacements); |
| } |
| |
| /// \brief Reorders initializers in the brace initialization of an aggregate. |
| /// |
| /// At the moment partial initialization is not supported. |
| /// \returns true on success |
| static bool reorderFieldsInInitListExpr( |
| const InitListExpr *InitListEx, ArrayRef<unsigned> NewFieldsOrder, |
| const ASTContext &Context, |
| std::map<std::string, tooling::Replacements> &Replacements) { |
| assert(InitListEx && "Init list expression is null"); |
| // We care only about InitListExprs which originate from source code. |
| // Implicit InitListExprs are created by the semantic analyzer. |
| if (!InitListEx->isExplicit()) |
| return true; |
| // The method InitListExpr::getSyntacticForm may return nullptr indicating |
| // that the current initializer list also serves as its syntactic form. |
| if (const auto *SyntacticForm = InitListEx->getSyntacticForm()) |
| InitListEx = SyntacticForm; |
| // If there are no initializers we do not need to change anything. |
| if (!InitListEx->getNumInits()) |
| return true; |
| if (InitListEx->getNumInits() != NewFieldsOrder.size()) { |
| llvm::errs() << "Currently only full initialization is supported\n"; |
| return false; |
| } |
| for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i) |
| if (i != NewFieldsOrder[i]) |
| addReplacement(InitListEx->getInit(i)->getSourceRange(), |
| InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(), |
| Context, Replacements); |
| return true; |
| } |
| |
| namespace { |
| class ReorderingConsumer : public ASTConsumer { |
| StringRef RecordName; |
| ArrayRef<std::string> DesiredFieldsOrder; |
| std::map<std::string, tooling::Replacements> &Replacements; |
| |
| public: |
| ReorderingConsumer(StringRef RecordName, |
| ArrayRef<std::string> DesiredFieldsOrder, |
| std::map<std::string, tooling::Replacements> &Replacements) |
| : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), |
| Replacements(Replacements) {} |
| |
| ReorderingConsumer(const ReorderingConsumer &) = delete; |
| ReorderingConsumer &operator=(const ReorderingConsumer &) = delete; |
| |
| void HandleTranslationUnit(ASTContext &Context) override { |
| const RecordDecl *RD = findDefinition(RecordName, Context); |
| if (!RD) |
| return; |
| SmallVector<unsigned, 4> NewFieldsOrder = |
| getNewFieldsOrder(RD, DesiredFieldsOrder); |
| if (NewFieldsOrder.empty()) |
| return; |
| if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements)) |
| return; |
| |
| // CXXRD will be nullptr if C code (not C++) is being processed. |
| const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD); |
| if (CXXRD) |
| for (const auto *C : CXXRD->ctors()) |
| if (const auto *D = dyn_cast<CXXConstructorDecl>(C->getDefinition())) |
| reorderFieldsInConstructor(cast<const CXXConstructorDecl>(D), |
| NewFieldsOrder, Context, Replacements); |
| |
| // We only need to reorder init list expressions for |
| // plain C structs or C++ aggregate types. |
| // For other types the order of constructor parameters is used, |
| // which we don't change at the moment. |
| // Now (v0) partial initialization is not supported. |
| if (!CXXRD || CXXRD->isAggregate()) |
| for (auto Result : |
| match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"), |
| Context)) |
| if (!reorderFieldsInInitListExpr( |
| Result.getNodeAs<InitListExpr>("initListExpr"), NewFieldsOrder, |
| Context, Replacements)) { |
| Replacements.clear(); |
| return; |
| } |
| } |
| }; |
| } // end anonymous namespace |
| |
| std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() { |
| return llvm::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder, |
| Replacements); |
| } |
| |
| } // namespace reorder_fields |
| } // namespace clang |