[Libreoffice-commits] dev-tools.git: clang/bin clang/find-unprefixed-members.cxx clang/Makefile clang/README clang/rename.cxx clang/test.cxx clang/test.hxx
Miklos Vajna
vmiklos at collabora.co.uk
Thu May 21 03:09:08 PDT 2015
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 <vmiklos at 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: */
More information about the Libreoffice-commits
mailing list