clang/Makefile | 15 + clang/README | 58 +++++++ clang/bin/rename-wrapper | 21 ++ clang/find-unprefixed-members.cxx | 168 +++++++++++++++++++++ clang/rename.cxx | 302 ++++++++++++++++++++++++++++++++++++++ clang/test.cxx | 37 ++++ clang/test.hxx | 39 ++++ 7 files changed, 640 insertions(+)
New commits: commit 9107601036fc9af9e67b37f0fc26296dddfd6eb1 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Thu May 21 11:05:46 2015 +0100 clang: import two libtooling-based compiler-like tool - rename: can handle renaming of member variables - find-unprefixed-members: can find all unprefixed members of a class Both are examples, the main motivation is that if you have a complex enough transformation that sed can't handle, but it's still a one-off transformation so that you would have a throw-away tool, then writing a tool based on libtooling is more optimal than writing a compiler plugin and let everyone else run it for no reason. diff --git a/clang/Makefile b/clang/Makefile new file mode 100644 index 0000000..70004cc --- /dev/null +++ b/clang/Makefile @@ -0,0 +1,15 @@ +CLANGDEFS=-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -fno-rtti +CLANGWARNS=-Werror -Wall -Wno-missing-braces -Wnon-virtual-dtor -Wendif-labels -Wextra -Wundef -Wunused-macros -Wshadow -Woverloaded-virtual +CLANGFLAGS = $(CLANGDEFS) $(CLANGWARNS) -g -std=c++11 +CLANGLIBS = -lLLVMSupport -lclangAST -lclangBasic -lclangFrontend -lclangRewrite -lclangTooling + +bin/rename: rename.cxx Makefile + clang++ $(CLANGFLAGS) $(CLANGLIBS) -o $@ $< + +bin/find-unprefixed-members: find-unprefixed-members.cxx Makefile + clang++ $(CLANGFLAGS) $(CLANGLIBS) -o $@ $< + +test: test.cxx test.hxx Makefile + clang++ -o test test.cxx + +# vim: set noet sw=4 ts=4: diff --git a/clang/README b/clang/README new file mode 100644 index 0000000..febf50a --- /dev/null +++ b/clang/README @@ -0,0 +1,58 @@ += Clang libtooling-based rename + +== This tool vs clang-rename + +This tool is similar to clang-rename, though there are a number of differences. + +Pros: + +- old name can be a simple qualified name, no need to manually specify a byte + offset +- rename handles ctor initializer list, too +- handles macros, even nested ones +- comes with a wrapper to do fully automatic rewriting + +Cons: + +- handles only rename of class members so far +- only tested with clang-3.5.0 + +== Hello world + +Example usage: + +---- +bin/rename -dump -old-name=C::nX -new-name=m_nX test.cxx -- +---- + +If you get missing includes: + +---- +ln -s /usr/lib64 +---- + +== Build system integration + +LibreOffice integration example with csv handling, provided that: + +- rename-wrapper is in your PATH +- rename.csv is something like in your HOME: + +---- +C::nX,m_nX +C::nY,m_nY +---- + +Then run: + +---- +make -sr -j8 COMPILER_EXTERNAL_TOOL=1 FORCE_COMPILE_ALL=1 CCACHE_PREFIX=rename-wrapper RENAME_ARGS="-csv=$HOME/rename.csv" +---- + +Once the rewriting is done, you can overwrite the original files with the .new ones with: + +---- +for i in $(find . -name "*.new"); do mv -f $i ${i%%.new}; done +---- + +// vim: ft=asciidoc diff --git a/clang/bin/rename-wrapper b/clang/bin/rename-wrapper new file mode 100755 index 0000000..a67743f --- /dev/null +++ b/clang/bin/rename-wrapper @@ -0,0 +1,21 @@ +#!/bin/bash + +mydir=$(dirname $0) +if [ -h $0 ]; then + mydir=$(dirname $(readlink -f $0)) +fi + +c= +for i in "$@" +do + if [ "$i" = "-c" ]; then + c=1 + elif [ -n "$c" ]; then + file=$i + break + fi +done + +exec $mydir/rename $RENAME_ARGS $file -- "$@" + +# vi:set shiftwidth=4 expandtab: diff --git a/clang/find-unprefixed-members.cxx b/clang/find-unprefixed-members.cxx new file mode 100644 index 0000000..52b1529 --- /dev/null +++ b/clang/find-unprefixed-members.cxx @@ -0,0 +1,168 @@ +#include <fstream> +#include <iostream> +#include <set> +#include <sstream> + +#include <clang/AST/ASTConsumer.h> +#include <clang/AST/ASTContext.h> +#include <clang/AST/RecursiveASTVisitor.h> +#include <clang/Rewrite/Core/Rewriter.h> +#include <clang/Tooling/CommonOptionsParser.h> +#include <clang/Tooling/Tooling.h> + +class Context +{ + std::string m_aClassName; + std::string m_aClassPrefix; + +public: + Context(const std::string& rClassName, const std::string& rClassPrefix) + : m_aClassName(rClassName), + m_aClassPrefix(rClassPrefix) + { + } + + bool match(const std::string& rName) const + { + if (m_aClassName == "") + return rName.find(m_aClassPrefix) == 0; + else + return rName == m_aClassName; + } +}; + +class Visitor : public clang::RecursiveASTVisitor<Visitor> +{ + const Context m_rContext; + bool m_bFound; + +public: + Visitor(const Context& rContext) + : m_rContext(rContext), + m_bFound(false) + { + } + + bool getFound() + { + return m_bFound; + } + + /* + * class C + * { + * public: + * int nX; <- Handles this declaration. + * }; + */ + bool VisitFieldDecl(clang::FieldDecl* pDecl) + { + clang::RecordDecl* pRecord = pDecl->getParent(); + + if (m_rContext.match(pRecord->getQualifiedNameAsString())) + { + std::string aName = pDecl->getNameAsString(); + if (aName.find("m") != 0) + { + aName.insert(0, "m_"); + std::cout << pRecord->getQualifiedNameAsString() << "::" << pDecl->getNameAsString() << "," << aName << std::endl; + m_bFound = true; + } + } + + return true; + } + + /* + * class C + * { + * public: + * static const int aS[]; <- Handles e.g. this declaration; + * }; + */ + bool VisitVarDecl(clang::VarDecl* pDecl) + { + if (!pDecl->getQualifier()) + return true; + + clang::RecordDecl* pRecord = pDecl->getQualifier()->getAsType()->getAsCXXRecordDecl(); + + if (m_rContext.match(pRecord->getQualifiedNameAsString())) + { + std::string aName = pDecl->getNameAsString(); + if (aName.find("m") != 0) + { + aName.insert(0, "m_"); + std::cout << pRecord->getQualifiedNameAsString() << "::" << pDecl->getNameAsString() << "," << aName << std::endl; + m_bFound = true; + } + } + + return true; + } +}; + +class ASTConsumer : public clang::ASTConsumer +{ + const Context& m_rContext; + +public: + ASTConsumer(const Context& rContext) + : m_rContext(rContext) + { + } + + virtual void HandleTranslationUnit(clang::ASTContext& rContext) + { + if (rContext.getDiagnostics().hasErrorOccurred()) + return; + + Visitor aVisitor(m_rContext); + aVisitor.TraverseDecl(rContext.getTranslationUnitDecl()); + if (aVisitor.getFound()) + exit(1); + } +}; + +class FrontendAction +{ + const Context& m_rContext; + +public: + FrontendAction(const Context& rContext) + : m_rContext(rContext) + { + } + + clang::ASTConsumer* newASTConsumer() + { + return new ASTConsumer(m_rContext); + } +}; + +int main(int argc, const char** argv) +{ + llvm::cl::OptionCategory aCategory("find-unprefixed-members options"); + llvm::cl::opt<std::string> aClassName("class-name", + llvm::cl::desc("Qualified name (namespace::Class)."), + llvm::cl::cat(aCategory)); + llvm::cl::opt<std::string> aClassPrefix("class-prefix", + llvm::cl::desc("Qualified name prefix (e.g. namespace::Cl)."), + llvm::cl::cat(aCategory)); + clang::tooling::CommonOptionsParser aParser(argc, argv, aCategory); + + if (aClassName.empty() && aClassPrefix.empty()) + { + std::cerr << "-class-name or -class-prefix is required." << std::endl; + return 1; + } + + clang::tooling::ClangTool aTool(aParser.getCompilations(), aParser.getSourcePathList()); + + Context aContext(aClassName, aClassPrefix); + FrontendAction aAction(aContext); + std::unique_ptr<clang::tooling::FrontendActionFactory> pFactory = clang::tooling::newFrontendActionFactory(&aAction); + return aTool.run(pFactory.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/clang/rename.cxx b/clang/rename.cxx new file mode 100644 index 0000000..6ca0823 --- /dev/null +++ b/clang/rename.cxx @@ -0,0 +1,302 @@ +#include <fstream> +#include <iostream> +#include <set> +#include <sstream> + +#include <clang/AST/ASTConsumer.h> +#include <clang/AST/ASTContext.h> +#include <clang/AST/RecursiveASTVisitor.h> +#include <clang/Rewrite/Core/Rewriter.h> +#include <clang/Tooling/CommonOptionsParser.h> +#include <clang/Tooling/Tooling.h> + +class RenameRewriter : public clang::Rewriter +{ + /// Old names -> new names map. + std::map<std::string, std::string> maNameMap; + bool mbDump; + +public: + RenameRewriter(const std::map<std::string, std::string>& rNameMap, bool bDump) + : maNameMap(rNameMap), + mbDump(bDump) + { + } + + const std::map<std::string, std::string>& getNameMap() + { + return maNameMap; + } + + bool getDump() + { + return mbDump; + } +}; + +class RenameVisitor : public clang::RecursiveASTVisitor<RenameVisitor> +{ + RenameRewriter& mrRewriter; + // A set of handled locations, so in case a location would be handled + // multiple times due to macro usage, we only do the rewrite once. + // Otherwise an A -> BA replacement would be done twice. + std::set<clang::SourceLocation> maHandledLocations; + +public: + explicit RenameVisitor(RenameRewriter& rRewriter) + : mrRewriter(rRewriter) + { + } + + /* + * class C + * { + * public: + * int nX; <- Handles this declaration. + * }; + */ + bool VisitFieldDecl(clang::FieldDecl* pDecl) + { + // Qualified name includes "C::" as a prefix, normal name does not. + std::string aName = pDecl->getQualifiedNameAsString(); + const std::map<std::string, std::string>::const_iterator it = mrRewriter.getNameMap().find(aName); + if (it != mrRewriter.getNameMap().end()) + mrRewriter.ReplaceText(pDecl->getLocation(), pDecl->getNameAsString().length(), it->second); + return true; + } + + /* + * class C + * { + * public: + * static const int aS[]; <- Handles e.g. this declaration; + * }; + */ + bool VisitVarDecl(clang::VarDecl* pDecl) + { + std::string aName = pDecl->getQualifiedNameAsString(); + const std::map<std::string, std::string>::const_iterator it = mrRewriter.getNameMap().find(aName); + if (it != mrRewriter.getNameMap().end()) + mrRewriter.ReplaceText(pDecl->getLocation(), pDecl->getNameAsString().length(), it->second); + return true; + } + + /* + * C::C() + * : nX(0) <- Handles this initializer. + * { + * } + */ + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl* pDecl) + { + for (clang::CXXConstructorDecl::init_const_iterator itInit = pDecl->init_begin(); itInit != pDecl->init_end(); ++itInit) + { + const clang::CXXCtorInitializer* pInitializer = *itInit; + if (const clang::FieldDecl* pFieldDecl = pInitializer->getAnyMember()) + { + std::string aName = pFieldDecl->getQualifiedNameAsString(); + const std::map<std::string, std::string>::const_iterator it = mrRewriter.getNameMap().find(aName); + if (it != mrRewriter.getNameMap().end()) + mrRewriter.ReplaceText(pInitializer->getSourceLocation(), pFieldDecl->getNameAsString().length(), it->second); + } + } + return true; + } + + /* + * C aC; + * aC.nX = 1; <- Handles e.g. this... + * int y = aC.nX; <- ...and this. + */ + bool VisitMemberExpr(clang::MemberExpr* pExpr) + { + if (clang::ValueDecl* pDecl = pExpr->getMemberDecl()) + { + std::string aName = pDecl->getQualifiedNameAsString(); + const std::map<std::string, std::string>::const_iterator it = mrRewriter.getNameMap().find(aName); + if (it != mrRewriter.getNameMap().end()) + { + clang::SourceLocation aLocation = pExpr->getMemberLoc(); + if (pExpr->getMemberLoc().isMacroID()) + /* + * int foo(int x); + * #define FOO(a) foo(a) + * FOO(aC.nX); <- Handles this. + */ + aLocation = mrRewriter.getSourceMgr().getSpellingLoc(aLocation); + if (maHandledLocations.find(aLocation) == maHandledLocations.end()) + { + mrRewriter.ReplaceText(aLocation, pDecl->getNameAsString().length(), it->second); + maHandledLocations.insert(aLocation); + } + } + } + return true; + } + + /* + * class C + * { + * public: + * static const int aS[]; + * static const int* getS() { return aS; } <- Handles this. + * }; + */ + bool VisitDeclRefExpr(clang::DeclRefExpr* pExpr) + { + if (clang::ValueDecl* pDecl = pExpr->getDecl()) + { + std::string aName = pDecl->getQualifiedNameAsString(); + const std::map<std::string, std::string>::const_iterator it = mrRewriter.getNameMap().find(aName); + if (it != mrRewriter.getNameMap().end()) + { + clang::SourceLocation aLocation = pExpr->getLocation(); + if (aLocation.isMacroID()) + /* + * int foo(int x); + * #define FOO(a) foo(a) + * FOO(aC.nX); <- Handles this. + */ + aLocation = mrRewriter.getSourceMgr().getSpellingLoc(aLocation); + if (maHandledLocations.find(aLocation) == maHandledLocations.end()) + { + mrRewriter.ReplaceText(aLocation, pDecl->getNameAsString().length(), it->second); + maHandledLocations.insert(aLocation); + } + } + } + return true; + } +}; + +class RenameASTConsumer : public clang::ASTConsumer +{ + RenameRewriter& mrRewriter; + + std::string getNewName(const clang::FileEntry& rEntry) + { + std::stringstream ss; + ss << rEntry.getName(); + ss << ".new"; + return ss.str(); + } + +public: + RenameASTConsumer(RenameRewriter& rRewriter) + : mrRewriter(rRewriter) + { + } + + virtual void HandleTranslationUnit(clang::ASTContext& rContext) + { + if (rContext.getDiagnostics().hasErrorOccurred()) + return; + + RenameVisitor aVisitor(mrRewriter); + mrRewriter.setSourceMgr(rContext.getSourceManager(), rContext.getLangOpts()); + aVisitor.TraverseDecl(rContext.getTranslationUnitDecl()); + + for (clang::Rewriter::buffer_iterator it = mrRewriter.buffer_begin(); it != mrRewriter.buffer_end(); ++it) + { + if (mrRewriter.getDump()) + it->second.write(llvm::errs()); + else + { + const clang::FileEntry* pEntry = rContext.getSourceManager().getFileEntryForID(it->first); + if (!pEntry) + continue; + std::string aNewName = getNewName(*pEntry); + std::string aError; + std::unique_ptr<llvm::raw_fd_ostream> pStream(new llvm::raw_fd_ostream(aNewName.c_str(), aError, llvm::sys::fs::F_None)); + if (aError.empty()) + it->second.write(*pStream); + } + } + } +}; + +class RenameFrontendAction +{ + RenameRewriter& mrRewriter; + +public: + RenameFrontendAction(RenameRewriter& rRewriter) + : mrRewriter(rRewriter) + { + } + + clang::ASTConsumer* newASTConsumer() + { + return new RenameASTConsumer(mrRewriter); + } +}; + +/// Parses rCsv and puts the first two column of it into rNameMap. +static void parseCsv(const std::string& rCsv, std::map<std::string, std::string>& rNameMap) +{ + std::ifstream aStream(rCsv); + if (!aStream.is_open()) + { + std::cerr << "parseCsv: failed to open " << rCsv << std::endl; + return; + } + + std::string aLine; + while (std::getline(aStream, aLine)) + { + std::stringstream ss(aLine); + std::string aOldName; + if (!std::getline(ss, aOldName, ',')) + { + std::cerr << "parseCsv: first std::getline() failed for line '" << aLine << "'" << std::endl; + return; + } + std::string aNewName; + if (!std::getline(ss, aNewName, ',')) + { + std::cerr << "parseCsv: second std::getline() failed for line '" << aLine << "'" << std::endl; + return; + } + rNameMap[aOldName] = aNewName; + } + + aStream.close(); +} + +int main(int argc, const char** argv) +{ + llvm::cl::OptionCategory aCategory("rename options"); + llvm::cl::opt<std::string> aOldName("old-name", + llvm::cl::desc("Old, qualified name (Class::member)."), + llvm::cl::cat(aCategory)); + llvm::cl::opt<std::string> aNewName("new-name", + llvm::cl::desc("New, non-qualified name (without Class::)."), + llvm::cl::cat(aCategory)); + llvm::cl::opt<std::string> aCsv("csv", + llvm::cl::desc("Path to a CSV file, containing multiple renames -- seprator must be a comma (,)."), + llvm::cl::cat(aCategory)); + llvm::cl::opt<bool> bDump("dump", + llvm::cl::desc("Dump output on the console instead of writing to .new files."), + llvm::cl::cat(aCategory)); + clang::tooling::CommonOptionsParser aParser(argc, argv, aCategory); + + std::map<std::string, std::string> aNameMap; + if (!aOldName.empty() && !aNewName.empty()) + aNameMap[aOldName] = aNewName; + else if (!aCsv.empty()) + parseCsv(aCsv, aNameMap); + else + { + std::cerr << "either -old-name + -new-name or -csv is required." << std::endl; + return 1; + } + + clang::tooling::ClangTool aTool(aParser.getCompilations(), aParser.getSourcePathList()); + + RenameRewriter aRewriter(aNameMap, bDump); + RenameFrontendAction aAction(aRewriter); + std::unique_ptr<clang::tooling::FrontendActionFactory> pFactory = clang::tooling::newFrontendActionFactory(&aAction); + return aTool.run(pFactory.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/clang/test.cxx b/clang/test.cxx new file mode 100644 index 0000000..68fe85f --- /dev/null +++ b/clang/test.cxx @@ -0,0 +1,37 @@ +#include "test.hxx" + +const int C::aS[] = { + 0 +}; + +C::C() + : nX(0), + nY(0), + nZ(0), + pX(0) +{ +} + +C::~C() +{ + DELETEZ( pX ); +} + +int foo(int x) +{ + return x; +} + +#define FOO(a) foo(a) + +int main(void) +{ + C aC; + aC.nX = 1; + int y = aC.nX; + FOO(aC.nX); + OSL_ENSURE(aC.nX, "test"); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/clang/test.hxx b/clang/test.hxx new file mode 100644 index 0000000..8f98f72 --- /dev/null +++ b/clang/test.hxx @@ -0,0 +1,39 @@ +class C +{ +public: + int nX, nY, nZ; + int* pX; + static const int aS[]; + C(); + ~C(); + + static const int* getS() { return aS; } +}; + +namespace ns +{ +class C +{ +public: + int nX, mnY, m_nZ; + + C() { } +}; +} + +#define DELETEZ( p ) ( delete p,p = 0 ) + +void sal_detail_logFormat(char const * /*area*/, char const * /*where*/, char const * /*format*/, ...) { } +#define SAL_DETAIL_LOG_FORMAT(condition, area, ...) \ + do { \ + if (condition) { \ + sal_detail_logFormat((area), __VA_ARGS__); \ + } \ + } while (0) +#define SAL_DETAIL_WARN_IF_FORMAT(condition, area, ...) \ + SAL_DETAIL_LOG_FORMAT( \ + (condition), \ + area, __VA_ARGS__) +#define OSL_ENSURE(c, m) SAL_DETAIL_WARN_IF_FORMAT(!(c), "legacy.osl", "%s", m) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits