[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