diff options
author | Manuel Klimek <klimek@google.com> | 2011-05-31 23:49:32 +0000 |
---|---|---|
committer | Manuel Klimek <klimek@google.com> | 2011-05-31 23:49:32 +0000 |
commit | 64cbdf370984783911bb6d3bc25ec35a8b59e998 (patch) | |
tree | d9f1e5c11584852365aeef877c1f1a36db5548e3 /examples | |
parent | d65e091631cc521fcb95a16d6587c0fed8b7164b (diff) | |
download | clang-64cbdf370984783911bb6d3bc25ec35a8b59e998.tar.gz clang-64cbdf370984783911bb6d3bc25ec35a8b59e998.tar.bz2 clang-64cbdf370984783911bb6d3bc25ec35a8b59e998.tar.xz |
This patch implements an AST matching framework that allows to write
tools that match on the C++ ASTs. The main interface is in ASTMatchers.h,
an example implementation of a tool that removes redundant .c_str() calls
is in the example RemoveCStrCalls.cpp.
Various contributions:
Zhanyong Wan, Chandler Carruth, Marcin Kowalczyk, Wei Xu, James Dennett.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@132374 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'examples')
-rw-r--r-- | examples/Tooling/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/Tooling/RemoveCStrCalls/CMakeLists.txt | 5 | ||||
-rw-r--r-- | examples/Tooling/RemoveCStrCalls/RemoveCStrCalls.cpp | 229 | ||||
-rwxr-xr-x | examples/Tooling/replace.py | 50 |
4 files changed, 285 insertions, 0 deletions
diff --git a/examples/Tooling/CMakeLists.txt b/examples/Tooling/CMakeLists.txt index 257d3ea8ea..01132b858a 100644 --- a/examples/Tooling/CMakeLists.txt +++ b/examples/Tooling/CMakeLists.txt @@ -4,3 +4,4 @@ add_clang_executable(clang-check ClangCheck.cpp ) +add_subdirectory(RemoveCStrCalls) diff --git a/examples/Tooling/RemoveCStrCalls/CMakeLists.txt b/examples/Tooling/RemoveCStrCalls/CMakeLists.txt new file mode 100644 index 0000000000..66debc9288 --- /dev/null +++ b/examples/Tooling/RemoveCStrCalls/CMakeLists.txt @@ -0,0 +1,5 @@ +set(LLVM_USED_LIBS clangTooling clangBasic clangAST) + +add_clang_executable(remove-cstr-calls + RemoveCStrCalls.cpp + ) diff --git a/examples/Tooling/RemoveCStrCalls/RemoveCStrCalls.cpp b/examples/Tooling/RemoveCStrCalls/RemoveCStrCalls.cpp new file mode 100644 index 0000000000..6de9dd986a --- /dev/null +++ b/examples/Tooling/RemoveCStrCalls/RemoveCStrCalls.cpp @@ -0,0 +1,229 @@ +//===- examples/Tooling/RemoveCStrCalls.cpp - Redundant c_str call removal ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a tool that prints replacements that remove redundant +// calls of c_str() on strings. +// +// Usage: +// remove-cstr-calls <cmake-output-dir> <file1> <file2> ... +// +// Where <cmake-output-dir> is a CMake build directory in which a file named +// compile_commands.json exists (enable -DCMAKE_EXPORT_COMPILE_COMMANDS in +// CMake to get this output). +// +// <file1> ... specify the paths of files in the CMake source tree. This path +// is looked up in the compile command database. If the path of a file is +// absolute, it needs to point into CMake's source tree. If the path is +// relative, the current working directory needs to be in the CMake source +// tree and the file must be in a subdirectory of the current working +// directory. "./" prefixes in the relative files will be automatically +// removed, but the rest of a relative path must be a suffix of a path in +// the compile command line database. +// +// For example, to use remove-cstr-calls on all files in a subtree of the +// source tree, use: +// +// /path/in/subtree $ find . -name '*.cpp'| +// xargs remove-cstr-calls /path/to/source +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/system_error.h" + +using namespace clang::tooling::match; + +// FIXME: Pull out helper methods in here into more fitting places. + +// Returns the text that makes up 'node' in the source. +// Returns an empty string if the text cannot be found. +template <typename T> +std::string GetText(const clang::SourceManager &SourceManager, const T &Node) { + clang::SourceLocation StartSpellingLocatino = + SourceManager.getSpellingLoc(Node.getLocStart()); + clang::SourceLocation EndSpellingLocation = + SourceManager.getSpellingLoc(Node.getLocEnd()); + if (!StartSpellingLocatino.isValid() || !EndSpellingLocation.isValid()) { + return std::string(); + } + bool Invalid = true; + const char *Text = + SourceManager.getCharacterData(StartSpellingLocatino, &Invalid); + if (Invalid) { + return std::string(); + } + std::pair<clang::FileID, unsigned> Start = + SourceManager.getDecomposedLoc(StartSpellingLocatino); + std::pair<clang::FileID, unsigned> End = + SourceManager.getDecomposedLoc(clang::Lexer::getLocForEndOfToken( + EndSpellingLocation, 0, SourceManager, clang::LangOptions())); + if (Start.first != End.first) { + // Start and end are in different files. + return std::string(); + } + if (End.second < Start.second) { + // Shuffling text with macros may cause this. + return std::string(); + } + return std::string(Text, End.second - Start.second); +} + +// Returns the position of the spelling location of a node inside a file. +// The format is: +// "<start_line>:<start_column>:<end_line>:<end_column>" +template <typename T1> +void PrintPosition( + llvm::raw_ostream &OS, + const clang::SourceManager &SourceManager, const T1 &Node) { + clang::SourceLocation StartSpellingLocation = + SourceManager.getSpellingLoc(Node.getLocStart()); + clang::SourceLocation EndSpellingLocation = + SourceManager.getSpellingLoc(Node.getLocEnd()); + clang::PresumedLoc Start = + SourceManager.getPresumedLoc(StartSpellingLocation); + clang::SourceLocation EndToken = clang::Lexer::getLocForEndOfToken( + EndSpellingLocation, 1, SourceManager, clang::LangOptions()); + clang::PresumedLoc End = SourceManager.getPresumedLoc(EndToken); + OS << Start.getLine() << ":" << Start.getColumn() << ":" + << End.getLine() << ":" << End.getColumn(); +} + +class ReportPosition : public clang::tooling::MatchFinder::MatchCallback { + public: + virtual void Run(const clang::tooling::MatchFinder::MatchResult &Result) { + llvm::outs() << "Found!\n"; + } +}; + +// Return true if expr needs to be put in parens when it is an +// argument of a prefix unary operator, e.g. when it is a binary or +// ternary operator syntactically. +bool NeedParensAfterUnaryOperator(const clang::Expr &ExprNode) { + if (llvm::dyn_cast<clang::BinaryOperator>(&ExprNode) || + llvm::dyn_cast<clang::ConditionalOperator>(&ExprNode)) { + return true; + } + if (const clang::CXXOperatorCallExpr *op = + llvm::dyn_cast<clang::CXXOperatorCallExpr>(&ExprNode)) { + return op->getNumArgs() == 2 && + op->getOperator() != clang::OO_PlusPlus && + op->getOperator() != clang::OO_MinusMinus && + op->getOperator() != clang::OO_Call && + op->getOperator() != clang::OO_Subscript; + } + return false; +} + +// Format a pointer to an expression: prefix with '*' but simplify +// when it already begins with '&'. Return empty string on failure. +std::string FormatDereference(const clang::SourceManager &SourceManager, + const clang::Expr &ExprNode) { + if (const clang::UnaryOperator *Op = + llvm::dyn_cast<clang::UnaryOperator>(&ExprNode)) { + if (Op->getOpcode() == clang::UO_AddrOf) { + // Strip leading '&'. + return GetText(SourceManager, *Op->getSubExpr()->IgnoreParens()); + } + } + const std::string Text = GetText(SourceManager, ExprNode); + if (Text.empty()) return std::string(); + // Add leading '*'. + if (NeedParensAfterUnaryOperator(ExprNode)) { + return std::string("*(") + Text + ")"; + } + return std::string("*") + Text; +} + +class FixCStrCall : public clang::tooling::MatchFinder::MatchCallback { + public: + virtual void Run(const clang::tooling::MatchFinder::MatchResult &Result) { + const clang::CallExpr *Call = + Result.Nodes.GetStmtAs<clang::CallExpr>("call"); + const clang::Expr *Arg = + Result.Nodes.GetStmtAs<clang::Expr>("arg"); + const bool Arrow = + Result.Nodes.GetStmtAs<clang::MemberExpr>("member")->isArrow(); + // Replace the "call" node with the "arg" node, prefixed with '*' + // if the call was using '->' rather than '.'. + const std::string ArgText = Arrow ? + FormatDereference(*Result.SourceManager, *Arg) : + GetText(*Result.SourceManager, *Arg); + if (ArgText.empty()) return; + + llvm::outs() << + Result.SourceManager->getBufferName(Call->getLocStart(), NULL) << ":"; + PrintPosition(llvm::outs(), *Result.SourceManager, *Call); + llvm::outs() << ":" << ArgText << "\n"; + } +}; + +const char *StringConstructor = + "::std::basic_string<char, std::char_traits<char>, std::allocator<char> >" + "::basic_string"; + +const char *StringCStrMethod = + "::std::basic_string<char, std::char_traits<char>, std::allocator<char> >" + "::c_str"; + +int main(int argc, char **argv) { + clang::tooling::ClangTool Tool(argc, argv); + clang::tooling::MatchFinder finder; + finder.AddMatcher( + ConstructorCall( + HasDeclaration(Method(HasName(StringConstructor))), + ArgumentCountIs(2), + // The first argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). We can use the copy + // constructor of string instead (or the compiler might share + // the string object). + HasArgument( + 0, + Id("call", Call( + Callee(Id("member", MemberExpression())), + Callee(Method(HasName(StringCStrMethod))), + On(Id("arg", Expression()))))), + // The second argument is the alloc object which must not be + // present explicitly. + HasArgument( + 1, + DefaultArgument())), new FixCStrCall); + finder.AddMatcher( + ConstructorCall( + // Implicit constructors of these classes are overloaded + // wrt. string types and they internally make a StringRef + // referring to the argument. Passing a string directly to + // them is preferred to passing a char pointer. + HasDeclaration(Method(AnyOf( + HasName("::llvm::StringRef::StringRef"), + HasName("::llvm::Twine::Twine")))), + ArgumentCountIs(1), + // The only argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). StringRef also has + // a constructor from string which is more efficient (avoids + // strlen), so we can construct StringRef from the string + // directly. + HasArgument( + 0, + Id("call", Call( + Callee(Id("member", MemberExpression())), + Callee(Method(HasName(StringCStrMethod))), + On(Id("arg", Expression())))))), + new FixCStrCall); + return Tool.Run(finder.NewFrontendActionFactory()); +} + diff --git a/examples/Tooling/replace.py b/examples/Tooling/replace.py new file mode 100755 index 0000000000..a738dea70c --- /dev/null +++ b/examples/Tooling/replace.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +#===- replace.py - Applying code rewrites --------------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +# +# This script applies the rewrites generated by replace-cstr-calls on a source +# tree. +# +# Usage: +# ./replace.py < /path/to/replace-cstr-calls-output +# +#===------------------------------------------------------------------------===# + +import fileinput +import re +import sys + +for line in sys.stdin.readlines(): + # The format is: + # <file>:<start_line>:<start_column>:<end_line>:<end_column>:<replacement> + # FIXME: This currently does not support files with colons, we'll need to + # figure out a format when we implement more refactoring support. + match = re.match(r'(.*):(\d+):(\d+):(\d+):(\d+):(.*)$', line) + if match is not None: + file_name = match.group(1) + start_line, start_column = int(match.group(2)), int(match.group(3)) + end_line, end_column = int(match.group(4)), int(match.group(5)) + replacement = match.group(6) + if start_line != end_line: + print ('Skipping match "%s": only single line ' + + 'replacements are supported') % line.strip() + continue + try: + replace_file = fileinput.input(file_name, inplace=1) + for replace_line in replace_file: + # FIXME: Looping over the file for each replacement is both inefficient + # and incorrect if replacements add or remove lines. + if replace_file.lineno() == start_line: + sys.stdout.write(replace_line[:start_column-1] + replacement + + replace_line[end_column:]) + else: + sys.stdout.write(replace_line) + except OSError, e: + print 'Cannot open %s for editing' % file_name |