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
This commit is contained in:
Manuel Klimek 2011-05-31 23:49:32 +00:00
Родитель d65e091631
Коммит 64cbdf3709
13 изменённых файлов: 5643 добавлений и 32 удалений

Просмотреть файл

@ -4,3 +4,4 @@ add_clang_executable(clang-check
ClangCheck.cpp
)
add_subdirectory(RemoveCStrCalls)

Просмотреть файл

@ -0,0 +1,5 @@
set(LLVM_USED_LIBS clangTooling clangBasic clangAST)
add_clang_executable(remove-cstr-calls
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());
}

50
examples/Tooling/replace.py Executable file
Просмотреть файл

@ -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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -43,11 +43,11 @@ bool RunSyntaxOnlyToolOnCode(
/// \param Argv The command line arguments, including the path the binary
/// was started with (Argv[0]).
bool RunToolWithFlags(
clang::FrontendAction* ToolAction, int Argc, char *Argv[]);
clang::FrontendAction *ToolAction, int Argc, char *Argv[]);
/// \brief Converts a vector<string> into a vector<char*> suitable to pass
/// to main-style functions taking (int Argc, char *Argv[]).
std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command);
std::vector<char*> CommandLineToArgv(const std::vector<std::string> *Command);
/// \brief Specifies the working directory and command of a compilation.
struct CompileCommand {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

564
lib/Tooling/ASTMatchers.cpp Normal file
Просмотреть файл

@ -0,0 +1,564 @@
//===--- ASTMatchers.cpp - Structural query framework ---------------------===//
//
// 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 framework of AST matchers that can be used to express
// structural queries on C++ code.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/DenseMap.h"
#include <assert.h>
#include <stddef.h>
#include <set>
#include <utility>
namespace clang {
namespace tooling {
// Returns the value that 'a_map' maps 'key' to, or NULL if 'key' is
// not in 'a_map'.
template <typename Map>
static const typename Map::mapped_type *Find(
const Map &AMap, const typename Map::key_type &Key) {
typename Map::const_iterator It = AMap.find(Key);
return It == AMap.end() ? NULL : &It->second;
}
// We use memoization to avoid running the same matcher on the same
// AST node twice. This pair is the key for looking up match
// result. It consists of an ID of the MatcherInterface (for
// identifying the matcher) and a pointer to the AST node.
typedef std::pair<uint64_t, const void*> UntypedMatchInput;
// Used to store the result of a match and possibly bound nodes.
struct MemoizedMatchResult {
bool ResultOfMatch;
BoundNodes Nodes;
};
// A RecursiveASTVisitor that traverses all children or all descendants of
// a node.
class MatchChildASTVisitor
: public clang::RecursiveASTVisitor<MatchChildASTVisitor> {
public:
typedef clang::RecursiveASTVisitor<MatchChildASTVisitor> VisitorBase;
// Creates an AST visitor that matches 'matcher' on all children or
// descendants of a traversed node. max_depth is the maximum depth
// to traverse: use 1 for matching the children and INT_MAX for
// matching the descendants.
MatchChildASTVisitor(const UntypedBaseMatcher *BaseMatcher,
ASTMatchFinder *Finder,
BoundNodesBuilder *Builder,
int MaxDepth,
ASTMatchFinder::TraversalMethod Traversal)
: BaseMatcher(BaseMatcher),
Finder(Finder),
Builder(Builder),
CurrentDepth(-1),
MaxDepth(MaxDepth),
Traversal(Traversal),
Matches(false) {}
// Returns true if a match is found in the subtree rooted at the
// given AST node. This is done via a set of mutually recursive
// functions. Here's how the recursion is done (the *wildcard can
// actually be Decl, Stmt, or Type):
//
// - Traverse(node) calls BaseTraverse(node) when it needs
// to visit the descendants of node.
// - BaseTraverse(node) then calls (via VisitorBase::Traverse*(node))
// Traverse*(c) for each child c of 'node'.
// - Traverse*(c) in turn calls Traverse(c), completing the
// recursion.
template <typename T>
bool FindMatch(const T &Node) {
Reset();
Traverse(Node);
return Matches;
}
// The following are overriding methods from the base visitor class.
// They are public only to allow CRTP to work. They are *not *part
// of the public API of this class.
bool TraverseDecl(clang::Decl *DeclNode) {
return (DeclNode == NULL) || Traverse(*DeclNode);
}
bool TraverseStmt(clang::Stmt *StmtNode) {
const clang::Stmt *StmtToTraverse = StmtNode;
if (Traversal ==
ASTMatchFinder::kIgnoreImplicitCastsAndParentheses) {
const clang::Expr *ExprNode = dyn_cast_or_null<clang::Expr>(StmtNode);
if (ExprNode != NULL) {
StmtToTraverse = ExprNode->IgnoreParenImpCasts();
}
}
return (StmtToTraverse == NULL) || Traverse(*StmtToTraverse);
}
bool TraverseType(clang::QualType TypeNode) {
return Traverse(TypeNode);
}
bool shouldVisitTemplateInstantiations() const { return true; }
private:
// Resets the state of this object.
void Reset() {
Matches = false;
CurrentDepth = -1;
}
// Forwards the call to the corresponding Traverse*() method in the
// base visitor class.
bool BaseTraverse(const clang::Decl &DeclNode) {
return VisitorBase::TraverseDecl(const_cast<clang::Decl*>(&DeclNode));
}
bool BaseTraverse(const clang::Stmt &StmtNode) {
return VisitorBase::TraverseStmt(const_cast<clang::Stmt*>(&StmtNode));
}
bool BaseTraverse(clang::QualType TypeNode) {
return VisitorBase::TraverseType(TypeNode);
}
// Traverses the subtree rooted at 'node'; returns true if the
// traversal should continue after this function returns; also sets
// matched_ to true if a match is found during the traversal.
template <typename T>
bool Traverse(const T &Node) {
COMPILE_ASSERT(IsBaseType<T>::value,
traverse_can_only_be_instantiated_with_base_type);
++CurrentDepth;
bool ShouldContinue;
if (CurrentDepth == 0) {
// We don't want to match the root node, so just recurse.
ShouldContinue = BaseTraverse(Node);
} else if (BaseMatcher->Matches(Node, Finder, Builder)) {
Matches = true;
ShouldContinue = false; // Abort as soon as a match is found.
} else if (CurrentDepth < MaxDepth) {
// The current node doesn't match, and we haven't reached the
// maximum depth yet, so recurse.
ShouldContinue = BaseTraverse(Node);
} else {
// The current node doesn't match, and we have reached the
// maximum depth, so don't recurse (but continue the traversal
// such that other nodes at the current level can be visited).
ShouldContinue = true;
}
--CurrentDepth;
return ShouldContinue;
}
const UntypedBaseMatcher *const BaseMatcher;
ASTMatchFinder *const Finder;
BoundNodesBuilder *const Builder;
int CurrentDepth;
const int MaxDepth;
const ASTMatchFinder::TraversalMethod Traversal;
bool Matches;
};
// Controls the outermost traversal of the AST and allows to match multiple
// matchers.
class MatchASTVisitor : public clang::RecursiveASTVisitor<MatchASTVisitor>,
public ASTMatchFinder {
public:
MatchASTVisitor(std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> > *Triggers,
clang::SourceManager *VisitorSourceManager,
clang::LangOptions *LanguageOptions)
: Triggers(Triggers),
VisitorSourceManager(VisitorSourceManager),
LanguageOptions(LanguageOptions),
ActiveASTContext(NULL) {
assert(VisitorSourceManager != NULL);
assert(LanguageOptions != NULL);
// FIXME: add rewriter_(*source_manager, *language_options)
}
void set_active_ast_context(clang::ASTContext *NewActiveASTContext) {
ActiveASTContext = NewActiveASTContext;
}
// The following Visit*() and Traverse*() functions "override"
// methods in RecursiveASTVisitor.
bool VisitTypedefDecl(clang::TypedefDecl *DeclNode) {
// When we see 'typedef A B', we add name 'B' to the set of names
// A's canonical type maps to. This is necessary for implementing
// IsDerivedFrom(x) properly, where x can be the name of the base
// class or any of its aliases.
//
// In general, the is-alias-of (as defined by typedefs) relation
// is tree-shaped, as you can typedef a type more than once. For
// example,
//
// typedef A B;
// typedef A C;
// typedef C D;
// typedef C E;
//
// gives you
//
// A
// |- B
// `- C
// |- D
// `- E
//
// It is wrong to assume that the relation is a chain. A correct
// implementation of IsDerivedFrom() needs to recognize that B and
// E are aliases, even though neither is a typedef of the other.
// Therefore, we cannot simply walk through one typedef chain to
// find out whether the type name matches.
const clang::Type *TypeNode = DeclNode->getUnderlyingType().getTypePtr();
const clang::Type *CanonicalType = // root of the typedef tree
ActiveASTContext->getCanonicalType(TypeNode);
TypeToUnqualifiedAliases[CanonicalType].insert(
DeclNode->getName().str());
return true;
}
bool TraverseDecl(clang::Decl *DeclNode);
bool TraverseStmt(clang::Stmt *StmtNode);
bool TraverseType(clang::QualType TypeNode);
bool TraverseTypeLoc(clang::TypeLoc TypeNode);
// Matches children or descendants of 'Node' with 'BaseMatcher'.
template <typename T>
bool MemoizedMatchesRecursively(
const T &Node, const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder, int MaxDepth,
TraversalMethod Traversal) {
COMPILE_ASSERT((llvm::is_same<T, clang::Decl>::value) ||
(llvm::is_same<T, clang::Stmt>::value),
type_does_not_support_memoization);
const UntypedMatchInput input(BaseMatcher.GetID(), &Node);
std::pair <MemoizationMap::iterator, bool>
InsertResult = ResultCache.insert(
std::make_pair(input, MemoizedMatchResult()));
if (InsertResult.second) {
BoundNodesBuilder DescendantBoundNodesBuilder;
InsertResult.first->second.ResultOfMatch =
MatchesRecursively(Node, BaseMatcher, &DescendantBoundNodesBuilder,
MaxDepth, Traversal);
InsertResult.first->second.Nodes =
DescendantBoundNodesBuilder.Build();
}
InsertResult.first->second.Nodes.CopyTo(Builder);
return InsertResult.first->second.ResultOfMatch;
}
// Matches children or descendants of 'Node' with 'BaseMatcher'.
template <typename T>
bool MatchesRecursively(
const T &Node, const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder, int MaxDepth,
TraversalMethod Traversal) {
MatchChildASTVisitor Visitor(
&BaseMatcher, this, Builder, MaxDepth, Traversal);
return Visitor.FindMatch(Node);
}
virtual bool ClassIsDerivedFrom(const clang::CXXRecordDecl *Declaration,
const std::string &BaseName) const;
// Implements ASTMatchFinder::MatchesChildOf.
virtual bool MatchesChildOf(const clang::Decl &DeclNode,
const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder,
TraversalMethod Traversal) {
return MatchesRecursively(
DeclNode, BaseMatcher, Builder, 1, Traversal);
}
virtual bool MatchesChildOf(const clang::Stmt &StmtNode,
const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder,
TraversalMethod Traversal) {
return MatchesRecursively(
StmtNode, BaseMatcher, Builder, 1, Traversal);
}
// Implements ASTMatchFinder::MatchesDescendantOf.
virtual bool MatchesDescendantOf(const clang::Decl &DeclNode,
const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder) {
return MemoizedMatchesRecursively(
DeclNode, BaseMatcher, Builder, INT_MAX, kAsIs);
}
virtual bool MatchesDescendantOf(const clang::Stmt &StmtNode,
const UntypedBaseMatcher &BaseMatcher,
BoundNodesBuilder *Builder) {
return MemoizedMatchesRecursively(
StmtNode, BaseMatcher, Builder, INT_MAX, kAsIs);
}
bool shouldVisitTemplateInstantiations() const { return true; }
private:
// Returns true if 'TypeNode' is also known by the name 'Name'. In other
// words, there is a type (including typedef) with the name 'Name'
// that is equal to 'TypeNode'.
bool TypeHasAlias(
const clang::Type *TypeNode, const std::string &Name) const {
const clang::Type *const CanonicalType =
ActiveASTContext->getCanonicalType(TypeNode);
const std::set<std::string> *UnqualifiedAlias =
Find(TypeToUnqualifiedAliases, CanonicalType);
return UnqualifiedAlias != NULL && UnqualifiedAlias->count(Name) > 0;
}
// Matches all registered matchers on the given node and calls the
// result callback for every node that matches.
template <typename T>
void Match(const T &node) {
for (std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> >::const_iterator
It = Triggers->begin(), End = Triggers->end();
It != End; ++It) {
BoundNodesBuilder Builder;
if (It->first->Matches(node, this, &Builder)) {
MatchFinder::MatchResult Result;
Result.Nodes = Builder.Build();
Result.Context = ActiveASTContext;
Result.SourceManager = VisitorSourceManager;
It->second->Run(Result);
}
}
}
std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> > *const Triggers;
clang::SourceManager *const VisitorSourceManager;
clang::LangOptions *const LanguageOptions;
clang::ASTContext *ActiveASTContext;
// Maps a canonical type to the names of its typedefs.
llvm::DenseMap<const clang::Type*, std::set<std::string> >
TypeToUnqualifiedAliases;
// Maps (matcher, node) -> the match result for memoization.
typedef llvm::DenseMap<UntypedMatchInput, MemoizedMatchResult> MemoizationMap;
MemoizationMap ResultCache;
};
// Returns true if the given class is directly or indirectly derived
// from a base type with the given name. A class is considered to be
// also derived from itself.
bool MatchASTVisitor::ClassIsDerivedFrom(
const clang::CXXRecordDecl *Declaration,
const std::string &BaseName) const {
if (std::string(Declaration->getName()) == BaseName) {
return true;
}
if (!Declaration->hasDefinition()) {
return false;
}
typedef clang::CXXRecordDecl::base_class_const_iterator BaseIterator;
for (BaseIterator It = Declaration->bases_begin(),
End = Declaration->bases_end(); It != End; ++It) {
const clang::Type *TypeNode = It->getType().getTypePtr();
if (TypeHasAlias(TypeNode, BaseName))
return true;
// clang::Type::getAs<...>() drills through typedefs.
if (TypeNode->getAs<clang::DependentNameType>() != NULL ||
TypeNode->getAs<clang::TemplateTypeParmType>() != NULL) {
// Dependent names and template TypeNode parameters will be matched when
// the template is instantiated.
continue;
}
clang::CXXRecordDecl *ClassDecl = NULL;
clang::TemplateSpecializationType const *TemplateType =
TypeNode->getAs<clang::TemplateSpecializationType>();
if (TemplateType != NULL) {
if (TemplateType->getTemplateName().isDependent()) {
// Dependent template specializations will be matched when the
// template is instantiated.
continue;
}
// For template specialization types which are specializing a template
// declaration which is an explicit or partial specialization of another
// template declaration, getAsCXXRecordDecl() returns the corresponding
// ClassTemplateSpecializationDecl.
//
// For template specialization types which are specializing a template
// declaration which is neither an explicit nor partial specialization of
// another template declaration, getAsCXXRecordDecl() returns NULL and
// we get the CXXRecordDecl of the templated declaration.
clang::CXXRecordDecl *SpecializationDecl =
TemplateType->getAsCXXRecordDecl();
if (SpecializationDecl != NULL) {
ClassDecl = SpecializationDecl;
} else {
ClassDecl = llvm::dyn_cast<clang::CXXRecordDecl>(
TemplateType->getTemplateName()
.getAsTemplateDecl()->getTemplatedDecl());
}
} else {
ClassDecl = TypeNode->getAsCXXRecordDecl();
}
assert(ClassDecl != NULL);
assert(ClassDecl != Declaration);
if (ClassIsDerivedFrom(ClassDecl, BaseName)) {
return true;
}
}
return false;
}
bool MatchASTVisitor::TraverseDecl(clang::Decl *DeclNode) {
if (DeclNode == NULL) {
return true;
}
Match(*DeclNode);
return clang::RecursiveASTVisitor<MatchASTVisitor>::TraverseDecl(DeclNode);
}
bool MatchASTVisitor::TraverseStmt(clang::Stmt *StmtNode) {
if (StmtNode == NULL) {
return true;
}
Match(*StmtNode);
return clang::RecursiveASTVisitor<MatchASTVisitor>::TraverseStmt(StmtNode);
}
bool MatchASTVisitor::TraverseType(clang::QualType TypeNode) {
Match(TypeNode);
return clang::RecursiveASTVisitor<MatchASTVisitor>::TraverseType(TypeNode);
}
bool MatchASTVisitor::TraverseTypeLoc(clang::TypeLoc TypeLoc) {
return clang::RecursiveASTVisitor<MatchASTVisitor>::
TraverseType(TypeLoc.getType());
}
class MatchASTConsumer : public clang::ASTConsumer {
public:
MatchASTConsumer(std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> > *Triggers,
MatchFinder::ParsingDoneTestCallback *ParsingDone,
clang::SourceManager *ConsumerSourceManager,
clang::LangOptions *LanaguageOptions)
: Visitor(Triggers, ConsumerSourceManager, LanaguageOptions),
ParsingDone(ParsingDone) {}
private:
virtual void HandleTranslationUnit(
clang::ASTContext &Context) { // NOLINT: external API uses refs
if (ParsingDone != NULL) {
ParsingDone->Run();
}
Visitor.set_active_ast_context(&Context);
Visitor.TraverseDecl(Context.getTranslationUnitDecl());
Visitor.set_active_ast_context(NULL);
}
MatchASTVisitor Visitor;
MatchFinder::ParsingDoneTestCallback *ParsingDone;
};
class MatchASTAction : public clang::ASTFrontendAction {
public:
explicit MatchASTAction(
std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> > *Triggers,
MatchFinder::ParsingDoneTestCallback *ParsingDone)
: Triggers(Triggers),
ParsingDone(ParsingDone) {}
private:
clang::ASTConsumer *CreateASTConsumer(
clang::CompilerInstance &Compiler,
llvm::StringRef) {
return new MatchASTConsumer(Triggers,
ParsingDone,
&Compiler.getSourceManager(),
&Compiler.getLangOpts());
}
std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> > *const Triggers;
MatchFinder::ParsingDoneTestCallback *ParsingDone;
};
MatchFinder::MatchCallback::~MatchCallback() {}
MatchFinder::ParsingDoneTestCallback::~ParsingDoneTestCallback() {}
MatchFinder::MatchFinder() : ParsingDone(NULL) {}
MatchFinder::~MatchFinder() {
for (std::vector< std::pair<const UntypedBaseMatcher*,
MatchFinder::MatchCallback*> >::const_iterator
It = Triggers.begin(), End = Triggers.end();
It != End; ++It) {
delete It->first;
delete It->second;
}
}
void MatchFinder::AddMatcher(const Matcher<clang::Decl> &NodeMatch,
MatchCallback *Action) {
Triggers.push_back(std::make_pair(
new TypedBaseMatcher<clang::Decl>(NodeMatch), Action));
}
void MatchFinder::AddMatcher(const Matcher<clang::QualType> &NodeMatch,
MatchCallback *Action) {
Triggers.push_back(std::make_pair(
new TypedBaseMatcher<clang::QualType>(NodeMatch), Action));
}
void MatchFinder::AddMatcher(const Matcher<clang::Stmt> &NodeMatch,
MatchCallback *Action) {
Triggers.push_back(std::make_pair(
new TypedBaseMatcher<clang::Stmt>(NodeMatch), Action));
}
bool MatchFinder::FindAll(const std::string &Code) {
return RunSyntaxOnlyToolOnCode(
new MatchASTAction(&Triggers, ParsingDone), Code);
}
clang::FrontendAction *MatchFinder::NewVisitorAction() {
return new MatchASTAction(&Triggers, ParsingDone);
}
class MatchFinderFrontendActionFactory : public FrontendActionFactory {
public:
explicit MatchFinderFrontendActionFactory(MatchFinder *Finder)
: Finder(Finder) {}
virtual clang::FrontendAction *New() {
return Finder->NewVisitorAction();
}
private:
MatchFinder *const Finder;
};
FrontendActionFactory *MatchFinder::NewFrontendActionFactory() {
return new MatchFinderFrontendActionFactory(this);
}
void MatchFinder::RegisterTestCallbackAfterParsing(
MatchFinder::ParsingDoneTestCallback *NewParsingDone) {
ParsingDone = NewParsingDone;
}
} // end namespace tooling
} // end namespace clang

Просмотреть файл

@ -1,6 +1,7 @@
SET(LLVM_USED_LIBS clangBasic clangFrontend clangAST)
add_clang_library(clangTooling
ASTMatchers.cpp
JsonCompileCommandLineDatabase.cpp
Tooling.cpp
)

Просмотреть файл

@ -1,4 +1,4 @@
//===--- Tooling.cpp - Running clang standalone tools --------------------===//
//===--- Tooling.cpp - Running clang standalone tools ---------------------===//
//
// The LLVM Compiler Infrastructure
//
@ -44,7 +44,7 @@ namespace {
// - it must contain at least a program path,
// - argv[0], ..., and argv[argc - 1] mustn't be NULL, and
// - argv[argc] must be NULL.
void ValidateArgv(int argc, char* argv[]) {
void ValidateArgv(int argc, char *argv[]) {
if (argc < 1) {
fprintf(stderr, "ERROR: argc is %d. It must be >= 1.\n", argc);
abort();
@ -69,7 +69,7 @@ void ValidateArgv(int argc, char* argv[]) {
// code that sets up a compiler to run tools on it, and we should refactor
// it to be based on the same framework.
static clang::Diagnostic* NewTextDiagnostics() {
static clang::Diagnostic *NewTextDiagnostics() {
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagIDs(
new clang::DiagnosticIDs());
clang::TextDiagnosticPrinter *DiagClient = new clang::TextDiagnosticPrinter(
@ -81,15 +81,15 @@ static clang::Diagnostic* NewTextDiagnostics() {
static int StaticSymbol;
/// \brief Builds a clang driver initialized for running clang tools.
static clang::driver::Driver* NewDriver(clang::Diagnostic* Diagnostics,
const char* BinaryName) {
static clang::driver::Driver *NewDriver(clang::Diagnostic *Diagnostics,
const char *BinaryName) {
// This just needs to be some symbol in the binary.
void* const SymbolAddr = &StaticSymbol;
void *const SymbolAddr = &StaticSymbol;
const llvm::sys::Path ExePath =
llvm::sys::Path::GetMainExecutable(BinaryName, SymbolAddr);
const std::string DefaultOutputName = "a.out";
clang::driver::Driver* CompilerDriver = new clang::driver::Driver(
clang::driver::Driver *CompilerDriver = new clang::driver::Driver(
ExePath.str(), llvm::sys::getHostTriple(),
DefaultOutputName, false, false, *Diagnostics);
CompilerDriver->setTitle("clang_based_tool");
@ -98,8 +98,8 @@ static clang::driver::Driver* NewDriver(clang::Diagnostic* Diagnostics,
/// \brief Retrieves the clang CC1 specific flags out of the compilation's jobs.
/// Returns NULL on error.
static const clang::driver::ArgStringList* GetCC1Arguments(
clang::Diagnostic* Diagnostics, clang::driver::Compilation* Compilation) {
static const clang::driver::ArgStringList *GetCC1Arguments(
clang::Diagnostic *Diagnostics, clang::driver::Compilation *Compilation) {
// We expect to get back exactly one Command job, if we didn't something
// failed. Extract that job from the Compilation.
const clang::driver::JobList &Jobs = Compilation->getJobs();
@ -124,10 +124,10 @@ static const clang::driver::ArgStringList* GetCC1Arguments(
}
/// \brief Returns a clang build invocation initialized from the CC1 flags.
static clang::CompilerInvocation* NewInvocation(
clang::Diagnostic* Diagnostics,
const clang::driver::ArgStringList& CC1Args) {
clang::CompilerInvocation* Invocation = new clang::CompilerInvocation;
static clang::CompilerInvocation *NewInvocation(
clang::Diagnostic *Diagnostics,
const clang::driver::ArgStringList &CC1Args) {
clang::CompilerInvocation *Invocation = new clang::CompilerInvocation;
clang::CompilerInvocation::CreateFromArgs(
*Invocation, CC1Args.data(), CC1Args.data() + CC1Args.size(),
*Diagnostics);
@ -137,11 +137,11 @@ static clang::CompilerInvocation* NewInvocation(
/// \brief Runs the specified clang tool action and returns whether it executed
/// successfully.
static bool RunInvocation(const char* BinaryName,
clang::driver::Compilation* Compilation,
clang::CompilerInvocation* Invocation,
const clang::driver::ArgStringList& CC1Args,
clang::FrontendAction* ToolAction) {
static bool RunInvocation(const char *BinaryName,
clang::driver::Compilation *Compilation,
clang::CompilerInvocation *Invocation,
const clang::driver::ArgStringList &CC1Args,
clang::FrontendAction *ToolAction) {
llvm::OwningPtr<clang::FrontendAction> ScopedToolAction(ToolAction);
// Show the invocation, with -v.
if (Invocation->getHeaderSearchOpts().Verbose) {
@ -164,7 +164,7 @@ static bool RunInvocation(const char* BinaryName,
if (Compiler.getHeaderSearchOpts().UseBuiltinIncludes &&
Compiler.getHeaderSearchOpts().ResourceDir.empty()) {
// This just needs to be some symbol in the binary.
void* const SymbolAddr = &StaticSymbol;
void *const SymbolAddr = &StaticSymbol;
Compiler.getHeaderSearchOpts().ResourceDir =
clang::CompilerInvocation::GetResourcesPath(BinaryName, SymbolAddr);
}
@ -175,7 +175,7 @@ static bool RunInvocation(const char* BinaryName,
/// \brief Converts a string vector representing a Command line into a C
/// string vector representing the Argv (including the trailing NULL).
std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command) {
std::vector<char*> CommandLineToArgv(const std::vector<std::string> *Command) {
std::vector<char*> Result(Command->size() + 1);
for (std::vector<char*>::size_type I = 0; I < Command->size(); ++I) {
Result[I] = const_cast<char*>((*Command)[I].c_str());
@ -185,14 +185,14 @@ std::vector<char*> CommandLineToArgv(const std::vector<std::string>* Command) {
}
bool RunToolWithFlags(
clang::FrontendAction* ToolAction, int Args, char* Argv[]) {
clang::FrontendAction *ToolAction, int Args, char *Argv[]) {
ValidateArgv(Args, Argv);
const llvm::OwningPtr<clang::Diagnostic> Diagnostics(NewTextDiagnostics());
const llvm::OwningPtr<clang::driver::Driver> Driver(
NewDriver(Diagnostics.get(), Argv[0]));
const llvm::OwningPtr<clang::driver::Compilation> Compilation(
Driver->BuildCompilation(llvm::ArrayRef<const char*>(Argv, Args)));
const clang::driver::ArgStringList* const CC1Args = GetCC1Arguments(
const clang::driver::ArgStringList *const CC1Args = GetCC1Arguments(
Diagnostics.get(), Compilation.get());
if (CC1Args == NULL) {
return false;
@ -208,11 +208,11 @@ bool RunToolWithFlags(
/// \param FileContents A mapping from file name to source code. For each
/// entry a virtual file mapping will be created when running the tool.
bool RunToolWithFlagsOnCode(
const std::vector<std::string>& CommandLine,
const std::map<std::string, std::string>& FileContents,
clang::FrontendAction* ToolAction) {
const std::vector<std::string> &CommandLine,
const std::map<std::string, std::string> &FileContents,
clang::FrontendAction *ToolAction) {
const std::vector<char*> Argv = CommandLineToArgv(&CommandLine);
const char* const BinaryName = Argv[0];
const char *const BinaryName = Argv[0];
const llvm::OwningPtr<clang::Diagnostic> Diagnostics(NewTextDiagnostics());
const llvm::OwningPtr<clang::driver::Driver> Driver(
@ -224,7 +224,7 @@ bool RunToolWithFlagsOnCode(
const llvm::OwningPtr<clang::driver::Compilation> Compilation(
Driver->BuildCompilation(llvm::ArrayRef<const char*>(&Argv[0],
Argv.size() - 1)));
const clang::driver::ArgStringList* const CC1Args = GetCC1Arguments(
const clang::driver::ArgStringList *const CC1Args = GetCC1Arguments(
Diagnostics.get(), Compilation.get());
if (CC1Args == NULL) {
return false;
@ -236,7 +236,7 @@ bool RunToolWithFlagsOnCode(
It = FileContents.begin(), End = FileContents.end();
It != End; ++It) {
// Inject the code as the given file name into the preprocessor options.
const llvm::MemoryBuffer* Input =
const llvm::MemoryBuffer *Input =
llvm::MemoryBuffer::getMemBuffer(It->second.c_str());
Invocation->getPreprocessorOpts().addRemappedFile(It->first.c_str(), Input);
}
@ -247,8 +247,8 @@ bool RunToolWithFlagsOnCode(
bool RunSyntaxOnlyToolOnCode(
clang::FrontendAction *ToolAction, llvm::StringRef Code) {
const char* const FileName = "input.cc";
const char* const CommandLine[] = {
const char *const FileName = "input.cc";
const char *const CommandLine[] = {
"clang-tool", "-fsyntax-only", FileName
};
std::map<std::string, std::string> FileContents;

Просмотреть файл

@ -0,0 +1,20 @@
// RUN: rm -rf %t
// RUN: mkdir %t
// RUN: echo '[{"directory":".","command":"clang++ '$(llvm-config --cppflags all)' -c %s","file":"%s"}]' > %t/compile_commands.json
// RUN: remove-cstr-calls %t %s | FileCheck %s
#include <string>
namespace llvm { struct StringRef { StringRef(const char *p); }; }
void f1(const std::string &s) {
f1(s.c_str()); // CHECK:remove-cstr-calls.cpp:11:6:11:14:s
}
void f2(const llvm::StringRef r) {
std::string s;
f2(s.c_str()); // CHECK:remove-cstr-calls.cpp:15:6:15:14:s
}
void f3(const llvm::StringRef &r) {
std::string s;
f3(s.c_str()); // CHECK:remove-cstr-calls.cpp:19:6:19:14:s
}

Просмотреть файл

@ -69,3 +69,8 @@ add_clang_unittest(JsonCompileCommandLineDatabase
Tooling/JsonCompileCommandLineDatabaseTest.cpp
USED_LIBS gtest gtest_main clangTooling
)
add_clang_unittest(ASTMatchersTest
Tooling/ASTMatchersTest.cpp
USED_LIBS gtest gtest_main clangTooling
)

Разница между файлами не показана из-за своего большого размера Загрузить разницу