alexshap created this revision. alexshap added reviewers: klimek, compnerd. alexshap added a subscriber: cfe-commits. alexshap changed the visibility of this Differential Revision from "Public (No Login Required)" to "All Users".
This diff adds v0 of clang-reorder-fields tool to clang/tools/extra. The main idea behind this tool is to simplify and make less error-prone refactoring of large codebases when someone needs to change the order fields of a struct/class (for example to remove excess padding). https://reviews.llvm.org/D23279 Files: CMakeLists.txt clang-reorder-fields/CMakeLists.txt clang-reorder-fields/ReorderFieldsAction.cpp clang-reorder-fields/ReorderFieldsAction.h clang-reorder-fields/tool/CMakeLists.txt clang-reorder-fields/tool/ClangReorderFields.cpp test/CMakeLists.txt test/clang-reorder-fields/CStructAmbiguousName.cpp test/clang-reorder-fields/CStructFieldPos.cpp test/clang-reorder-fields/CStructFieldsOrder.cpp
Index: test/clang-reorder-fields/CStructFieldsOrder.cpp =================================================================== --- test/clang-reorder-fields/CStructFieldsOrder.cpp +++ test/clang-reorder-fields/CStructFieldsOrder.cpp @@ -0,0 +1,18 @@ +// RUN: cat %s > %t.cpp +// RUN: clang-reorder-fields -record-name ::bar::Foo -fields-order z,w,y,x %t.cpp -i -- +// RUN: sed 's,//.*,,' %t.cpp | FileCheck %s + +namespace bar { +struct Foo { + const int* x; // CHECK: double z; + int y; // CHECK: int w; + double z; // CHECK: int y; + int w; // CHECK: const int* x; +}; +} // end namespace bar + +int main() { + const int x = 13; + bar::Foo foo = { &x, 17, 1.29, 0 }; // CHECK: bar::Foo foo = { 1.29, 0, 17, &x }; + return 0; +} Index: test/clang-reorder-fields/CStructFieldPos.cpp =================================================================== --- test/clang-reorder-fields/CStructFieldPos.cpp +++ test/clang-reorder-fields/CStructFieldPos.cpp @@ -0,0 +1,16 @@ +// RUN: cat %s > %t.cpp +// RUN: clang-reorder-fields -record-name ::Foo -field-pos z:0 %t.cpp -i -- +// RUN: sed 's,//.*,,' %t.cpp | FileCheck %s + +struct Foo { + const int* x; // CHECK: double z; + int y; // CHECK: const int* x; + double z; // CHECK: int y; + int w; // CHECK: int w; +}; + +int main() { + const int x = 13; + Foo foo = { &x, 17, 1.29, 0 }; // CHECK: Foo foo = { 1.29, &x, 17, 0 }; + return 0; +} Index: test/clang-reorder-fields/CStructAmbiguousName.cpp =================================================================== --- test/clang-reorder-fields/CStructAmbiguousName.cpp +++ test/clang-reorder-fields/CStructAmbiguousName.cpp @@ -0,0 +1,20 @@ +// RUN: cat %s > %t.cpp +// RUN: clang-reorder-fields -record-name Foo -fields-order y,x %t.cpp -i -- +// RUN: sed 's,//.*,,' %t.cpp | FileCheck %s + +struct Foo { + int x; // CHECK: int x; + double y; // CHECK: double y; +}; + +namespace bar { +struct Foo { + int x; // CHECK: int x; + double y; // CHECK: double y; +}; +} // end namespace bar + +int main() { + Foo foo = { 1, 1.7 }; // CHECK: Foo foo = { 1, 1.7 }; + return 0; +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -45,6 +45,7 @@ clang-include-fixer clang-query clang-rename + clang-reorder-fields clang-tidy find-all-symbols modularize Index: clang-reorder-fields/tool/ClangReorderFields.cpp =================================================================== --- clang-reorder-fields/tool/ClangReorderFields.cpp +++ clang-reorder-fields/tool/ClangReorderFields.cpp @@ -0,0 +1,135 @@ +//===-- tools/extra/clang-reorder-fields/tool/ClangReorderFields.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 implementation of clang-reorder-fields tool +/// +//===----------------------------------------------------------------------===// + +#include "../ReorderFieldsAction.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include <cstdlib> +#include <string> +#include <system_error> +#include <tuple> + +using namespace llvm; +using namespace clang; + +cl::OptionCategory ClangReorderFieldsCategory("clang-reorder-fields options"); + +// FIXME: error-handling +struct FieldsOrderParser : cl::parser<SmallVector<std::string, 4>> { + explicit FieldsOrderParser(cl::Option &O) + : cl::parser<SmallVector<std::string, 4>>(O){}; + + bool parse(cl::Option &O, StringRef ArgName, const std::string &ArgValue, + SmallVector<std::string, 4> &Val) { + Val.clear(); + SmallVector<StringRef, 4> Parts; + StringRef(ArgValue).split(Parts, ','); + for (const auto Part : Parts) + Val.push_back(Part.str()); + return Val.empty(); + } +}; + +// FIXME: error-handling +struct FieldPositionParser : cl::parser<reorder_fields::FieldPosition> { + explicit FieldPositionParser(cl::Option &O) + : cl::parser<reorder_fields::FieldPosition>(O){}; + + bool parse(cl::Option &O, StringRef ArgName, const std::string &ArgValue, + reorder_fields::FieldPosition &Val) { + StringRef Name; + StringRef Position; + std::tie(Name, Position) = StringRef(ArgValue).split(":"); + Val.Name = Name.str(); + return Position.getAsInteger(10, Val.Position); + } +}; + +static cl::opt<std::string> RecordName("record-name", + cl::desc("The name of the struct/class."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::opt<SmallVector<std::string, 4>, false, FieldsOrderParser> + FieldsOrder("fields-order", cl::desc("The desired fields order."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::list<reorder_fields::FieldPosition, bool, FieldPositionParser> + FieldsPositions("field-pos", cl::desc("The new field position."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::opt<bool> Inplace("i", cl::desc("Overwrite edited <file>s."), + cl::cat(ClangReorderFieldsCategory)); + +const char Usage[] = "A tool to reorder fields in C/C++ structs/classes.\n"; + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangReorderFieldsCategory, + Usage); + + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + + if (!FieldsOrder.empty() && !FieldsPositions.empty()) { + errs() << "Either fields-order or field-pos should not be specified\n"; + exit(1); + } + + std::unique_ptr<reorder_fields::ReorderFieldsAction> Action; + if (!FieldsOrder.empty()) + Action.reset(new reorder_fields::ReorderFieldsAction( + RecordName, FieldsOrder, Tool.getReplacements())); + + if (!FieldsPositions.empty()) + Action.reset(new reorder_fields::ReorderFieldsAction( + RecordName, FieldsPositions, Tool.getReplacements())); + + auto Factory = tooling::newFrontendActionFactory(Action.get()); + int ExitCode; + + if (Inplace) { + ExitCode = Tool.runAndSave(Factory.get()); + } else { + ExitCode = Tool.run(Factory.get()); + + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + Tool.applyAllReplacements(Rewrite); + + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + const auto ID = Sources.translateFile(Entry); + Rewrite.getEditBuffer(ID).write(outs()); + } + } + exit(ExitCode); +} Index: clang-reorder-fields/tool/CMakeLists.txt =================================================================== --- clang-reorder-fields/tool/CMakeLists.txt +++ clang-reorder-fields/tool/CMakeLists.txt @@ -0,0 +1,12 @@ +add_clang_executable(clang-reorder-fields ClangReorderFields.cpp) + +target_link_libraries(clang-reorder-fields + clangBasic + clangFrontend + clangReorderFields + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-reorder-fields RUNTIME DESTINATION bin) Index: clang-reorder-fields/ReorderFieldsAction.h =================================================================== --- clang-reorder-fields/ReorderFieldsAction.h +++ clang-reorder-fields/ReorderFieldsAction.h @@ -0,0 +1,59 @@ +//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.h -*- 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 declarations of the ReorderFieldsAction class and +/// the FieldPosition struct. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; + +namespace reorder_fields { +struct FieldPosition { + std::string Name; + unsigned Position; +}; + +class ReorderFieldsAction { + llvm::StringRef RecordName; + llvm::ArrayRef<std::string> DesiredFieldsOrder; + llvm::ArrayRef<FieldPosition> DesiredFieldsPositions; + std::map<std::string, tooling::Replacements> &Replacements; + +public: + ReorderFieldsAction( + llvm::StringRef RecordName, + llvm::ArrayRef<std::string> DesiredFieldsOrder, + std::map<std::string, tooling::Replacements> &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderFieldsAction( + llvm::StringRef RecordName, + llvm::ArrayRef<FieldPosition> DesiredFieldsPositions, + std::map<std::string, tooling::Replacements> &Replacements) + : RecordName(RecordName), DesiredFieldsPositions(DesiredFieldsPositions), + Replacements(Replacements) {} + + ReorderFieldsAction(const ReorderFieldsAction &) = delete; + ReorderFieldsAction &operator=(const ReorderFieldsAction &) = delete; + + std::unique_ptr<ASTConsumer> newASTConsumer(); +}; +} // namespace reorder_fields +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H Index: clang-reorder-fields/ReorderFieldsAction.cpp =================================================================== --- clang-reorder-fields/ReorderFieldsAction.cpp +++ clang-reorder-fields/ReorderFieldsAction.cpp @@ -0,0 +1,271 @@ +//===-- 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 <algorithm> +#include <limits> +#include <string> +#include <unordered_map> +#include <vector> + +using namespace llvm; +using namespace clang; +using namespace clang::ast_matchers; + +static const CXXRecordDecl *findDefinition(StringRef RecordName, + ASTContext &Context) { + struct DefinitionMatchCallback : MatchFinder::MatchCallback { + DefinitionMatchCallback() : RecordDecl(nullptr) {} + virtual void run(const MatchFinder::MatchResult &Result) override { + const auto *RD = Result.Nodes.getNodeAs<CXXRecordDecl>("cxxRecordDecl"); + if (RecordDecl) { + errs() << "Ambiguous name, several matches found: " + << RecordDecl->getQualifiedNameAsString() << ", " + << RD->getQualifiedNameAsString() << "\n"; + RecordDecl = nullptr; + return; + } + RecordDecl = RD; + } + const CXXRecordDecl *RecordDecl; + }; + + MatchFinder Finder; + DefinitionMatchCallback Callback; + const auto Matcher = + recordDecl(hasName(RecordName), isDefinition()).bind("cxxRecordDecl"); + Finder.addMatcher(Matcher, &Callback); + Finder.matchAST(Context); + return Callback.RecordDecl; +} + +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()); + if (auto Err = Replacements[R.getFilePath()].add(R)) + llvm::errs() << "Failed to add replacement, file: " << R.getFilePath() + << ", error: " << llvm::toString(std::move(Err)) << "\n"; +} + +static void reorderFieldsInDefinition( + const CXXRecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder, + const ASTContext &Context, + std::map<std::string, tooling::Replacements> &Replacements) { + assert(Definition && "Definition is null"); + + std::unordered_map<size_t, SourceRange> FieldToSource; + for (const auto *Field : Definition->fields()) + FieldToSource[Field->getFieldIndex()] = Field->getSourceRange(); + + for (const auto *Field : Definition->fields()) { + const auto FieldIndex = Field->getFieldIndex(); + if (FieldIndex == NewFieldsOrder[FieldIndex]) + continue; + addReplacement(FieldToSource[FieldIndex], + FieldToSource[NewFieldsOrder[FieldIndex]], Context, + Replacements); + } +} + +namespace clang { +namespace reorder_fields { +namespace { +class ReorderingASTVisitor : public RecursiveASTVisitor<ReorderingASTVisitor> { + const CXXRecordDecl *Definition; + ArrayRef<unsigned> NewFieldsOrder; + const ASTContext &Context; + std::map<std::string, tooling::Replacements> &Replacements; + +public: + ReorderingASTVisitor( + const CXXRecordDecl *Definition, ArrayRef<unsigned> NewFieldsOrder, + const ASTContext &Context, + std::map<std::string, tooling::Replacements> &Replacements) + : Definition(Definition), NewFieldsOrder(NewFieldsOrder), + Context(Context), Replacements(Replacements) {} + + ReorderingASTVisitor(const ReorderingASTVisitor &) = delete; + ReorderingASTVisitor &operator=(const ReorderingASTVisitor &) = delete; + + bool VisitInitListExpr(InitListExpr *InitListEx) { + if (!InitListEx->isExplicit() || !InitListEx->getNumInits()) + return true; + + const auto *RD = InitListEx->getType() + .getCanonicalType() + .getTypePtr() + ->getAsCXXRecordDecl(); + assert(RD && "CXXRecordDecl is null"); + if (RD->getDefinition() != Definition) + return true; + if (InitListEx->getNumInits() != NewFieldsOrder.size()) { + errs() << "Currently support only full initialization.\n"; + return true; + } + + for (unsigned Index = 0, NumInits = InitListEx->getNumInits(); + Index < NumInits; ++Index) { + if (Index == NewFieldsOrder[Index]) + continue; + addReplacement( + InitListEx->getInit(Index)->getSourceRange(), + InitListEx->getInit(NewFieldsOrder[Index])->getSourceRange(), Context, + Replacements); + } + return true; + } +}; + +class ReorderingConsumer : public ASTConsumer { + StringRef RecordName; + ArrayRef<std::string> DesiredFieldsOrder; + ArrayRef<FieldPosition> DesiredFieldsPositions; + std::map<std::string, tooling::Replacements> &Replacements; + SmallVector<unsigned, 4> NewFieldsOrder; + + bool setNewFieldsOrder(const CXXRecordDecl *Definition, + ArrayRef<std::string> DesiredFieldsOrder) { + assert(Definition && "Definition is null"); + + StringMap<unsigned> NameToIndex; + for (const auto *Field : Definition->fields()) + NameToIndex[Field->getName()] = Field->getFieldIndex(); + + if (DesiredFieldsOrder.size() != NameToIndex.size()) { + errs() << "Number of provided fields doesn't match definition.\n"; + return false; + } + + NewFieldsOrder.clear(); + for (const auto &Name : DesiredFieldsOrder) { + if (!NameToIndex.count(Name)) { + errs() << "Field " << Name << " not found in definition.\n"; + return false; + } + NewFieldsOrder.push_back(NameToIndex[Name]); + } + assert(NewFieldsOrder.size() == NameToIndex.size()); + return true; + } + + bool setNewFieldsOrder(const CXXRecordDecl *Definition, + ArrayRef<FieldPosition> DesiredFieldsPositions) { + assert(Definition && "Definition is null"); + + StringMap<unsigned> NameToIndex; + for (const auto *Field : Definition->fields()) + NameToIndex[Field->getName()] = Field->getFieldIndex(); + + StringSet<> AllocatedFields; + LLVM_CONSTEXPR unsigned INF = std::numeric_limits<unsigned>::max(); + NewFieldsOrder.assign(NameToIndex.size(), INF); + for (const auto &FP : DesiredFieldsPositions) { + if (!NameToIndex.count(FP.Name)) { + errs() << "Field " << FP.Name << " not found in the definition\n"; + return false; + } + if (FP.Position >= NewFieldsOrder.size()) { + errs() << "Field [" << FP.Name << "]" + << " position [" << FP.Position << "] out of range\n"; + return false; + } + if (AllocatedFields.count(FP.Name)) { + errs() << "Field " << FP.Name << " specified twice\n"; + return false; + } + NewFieldsOrder[FP.Position] = NameToIndex[FP.Name]; + AllocatedFields.insert(FP.Name); + } + + unsigned NextFreePosition = 0; + for (const auto *Field : Definition->fields()) { + if (AllocatedFields.count(Field->getName())) + continue; + while (NextFreePosition < NewFieldsOrder.size() && + NewFieldsOrder[NextFreePosition] != INF) + ++NextFreePosition; + assert(NextFreePosition < NewFieldsOrder.size()); + NewFieldsOrder[NextFreePosition] = Field->getFieldIndex(); + } + return true; + } + +public: + ReorderingConsumer(StringRef RecordName, + ArrayRef<std::string> DesiredFieldsOrder, + std::map<std::string, tooling::Replacements> &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderingConsumer(StringRef RecordName, + ArrayRef<FieldPosition> DesiredFieldsPositions, + std::map<std::string, tooling::Replacements> &Replacements) + : RecordName(RecordName), DesiredFieldsPositions(DesiredFieldsPositions), + Replacements(Replacements) {} + + ReorderingConsumer(const ReorderingConsumer &) = delete; + ReorderingConsumer &operator=(const ReorderingConsumer &) = delete; + + void HandleTranslationUnit(ASTContext &Context) override { + const CXXRecordDecl *RD = findDefinition(RecordName, Context); + if (!RD) { + errs() << "Definition for name " << RecordName + << " not found or ambiguous.\n"; + return; + } + if (!RD->isCLike()) { + errs() << "Now only C-like structs are supported.\n"; + return; + } + if (!DesiredFieldsOrder.empty() && + !setNewFieldsOrder(RD, DesiredFieldsOrder)) + return; + if (!DesiredFieldsPositions.empty() && + !setNewFieldsOrder(RD, DesiredFieldsPositions)) + return; + reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements); + ReorderingASTVisitor Visitor(RD, NewFieldsOrder, Context, Replacements); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + } +}; +} // end anonymous namespace + +std::unique_ptr<ASTConsumer> ReorderFieldsAction::newASTConsumer() { + assert(!DesiredFieldsOrder.empty() || !DesiredFieldsPositions.empty()); + if (!DesiredFieldsOrder.empty()) + return llvm::make_unique<ReorderingConsumer>(RecordName, DesiredFieldsOrder, + Replacements); + if (!DesiredFieldsPositions.empty()) + return llvm::make_unique<ReorderingConsumer>( + RecordName, DesiredFieldsPositions, Replacements); + llvm_unreachable("Either list of fields or list of positions is not empty"); +} + +} // namespace reorder_fields +} // namespace clang Index: clang-reorder-fields/CMakeLists.txt =================================================================== --- clang-reorder-fields/CMakeLists.txt +++ clang-reorder-fields/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangReorderFields + ReorderFieldsAction.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangIndex + clangLex + clangToolingCore + ) + +add_subdirectory(tool) Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(clang-apply-replacements) add_subdirectory(clang-rename) +add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) if(CLANG_ENABLE_STATIC_ANALYZER) add_subdirectory(clang-tidy)
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits