summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorManuel Klimek <klimek@google.com>2011-05-31 23:49:32 +0000
committerManuel Klimek <klimek@google.com>2011-05-31 23:49:32 +0000
commit64cbdf370984783911bb6d3bc25ec35a8b59e998 (patch)
treed9f1e5c11584852365aeef877c1f1a36db5548e3 /examples
parentd65e091631cc521fcb95a16d6587c0fed8b7164b (diff)
downloadclang-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.txt1
-rw-r--r--examples/Tooling/RemoveCStrCalls/CMakeLists.txt5
-rw-r--r--examples/Tooling/RemoveCStrCalls/RemoveCStrCalls.cpp229
-rwxr-xr-xexamples/Tooling/replace.py50
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