我正在研究从以下源代码生成的 Clang AST:
template<class T>
struct my_class
{
template<class U>
void foo(U arg);
};
template<class V>
template<class W> void my_class<V>::foo(W arg)
{
}
int main()
{
return 0;
}
我有一个
FunctionDecl
对应于第 9 行 foo
的定义。我想在第 8 行找到 SourceLocation
的 template<class V>
。
这可能吗?
我正在分析 AST,以便重写它。
DeclaratorDecl
,
并可以使用检索
getNumTemplateParameterLists()
和
getTemplateParameterList(unsigned)
方法。
这些方法没有任何文档,但是如果您查看
Decl.h
源码,可以看到这些方法访问了一个
QualifierInfo
结构,其中包含两个相关领域的文档。
TemplateParameterList
,
template
关键字和尖括号的位置可以是
通过调用 getTemplateLoc()
、getLAngleLoc()
和
getRAngleLoc()
。各个参数的位置可用
从 NamedDecl
节点可通过 asArray()
访问。
因此,给定
FunctionDecl *functionDecl
,我们可以打印(比如说)
外部 template
关键字的位置如下:
for (unsigned i=0; i < functionDecl->getNumTemplateParameterLists(); ++i) {
clang::TemplateParameterList *paramList =
functionDecl->getTemplateParameterList(i);
cout << "outer template parameter list " << i << "\n"
<< " TemplateLoc: "
<< paramList->getTemplateLoc().
printToString(m_astContext.getSourceManager()) << "\n";
}
一个相关的怪癖/细节:当
DeclaratorDecl
(它是 FunctionDecl
和 CXXMethodDecl
的子类)具有“外部”模板参数时,其 getBeginLoc()
方法将返回 template
关键字的位置第一个外层。如果您想获取声明开始位置after所有模板参数列表(内部和外部,尽管文档仅提到外部),请使用DeclaratorDecl::getInnerLocStart()
。对于问题中的示例,它返回 void
关键字的位置(对于 foo
的两个声明)。
这是演示该方法的完整程序:
// outer-tparams.cc
// Report "outer" template parameter lists.
#include "clang/AST/RecursiveASTVisitor.h" // clang::RecursiveASTVisitor
#include "clang/Basic/Diagnostic.h" // clang::DiagnosticsEngine
#include "clang/Basic/DiagnosticOptions.h" // clang::DiagnosticOptions
#include "clang/Frontend/ASTUnit.h" // clang::ASTUnit
#include "clang/Frontend/CompilerInstance.h" // clang::CompilerInstance
#include "clang/Serialization/PCHContainerOperations.h" // clang::PCHContainerOperations
#include "llvm/Support/raw_ostream.h" // llvm::raw_string_ostream
#include <iostream> // std::cout
#include <sstream> // std::ostringstream
#include <string> // std::string
#include <assert.h> // assert
// Convenient string construction.
#define stringb(stuff) \
(static_cast<std::ostringstream&>(std::ostringstream() << stuff).str())
using clang::dyn_cast;
using std::cout;
using std::endl;
using std::string;
class Visitor : public clang::RecursiveASTVisitor<Visitor> {
public: // data
clang::ASTContext &m_astContext;
public: // methods
Visitor(clang::ASTUnit *astUnit)
: m_astContext(astUnit->getASTContext())
{}
// Convenience methods to stringify some things.
string locStr(clang::SourceLocation loc) const;
string declLocStr(clang::Decl const *decl) const;
string declNameLocStr(clang::NamedDecl const *decl) const;
string templateParameterListStr(
clang::TemplateParameterList const *paramList) const;
// Visitor methods.
bool VisitFunctionDecl(clang::FunctionDecl *functionDecl);
// Kick off the traversal.
void traverseTU();
};
string Visitor::locStr(clang::SourceLocation loc) const
{
return loc.printToString(m_astContext.getSourceManager());
}
string Visitor::declLocStr(clang::Decl const *decl) const
{
return locStr(decl->getLocation());
}
string Visitor::declNameLocStr(clang::NamedDecl const *decl) const
{
return stringb(decl->getQualifiedNameAsString() <<
" declared at " << declLocStr(decl));
}
string Visitor::templateParameterListStr(
clang::TemplateParameterList const *paramList) const
{
string ret;
llvm::raw_string_ostream rso(ret);
clang::PrintingPolicy printingPolicy(m_astContext.getLangOpts());
bool const omitTemplateKW = false;
paramList->print(rso, m_astContext, printingPolicy, omitTemplateKW);
// Annoyingly, the string has a trailing space.
ret.erase(ret.find_last_not_of(" \t\n\r\f\v") + 1);
return ret;
}
bool Visitor::VisitFunctionDecl(clang::FunctionDecl *functionDecl)
{
cout << "FunctionDecl: " << declNameLocStr(functionDecl) << "\n";
// Iterate over the "outer" template parameter lists, i.e., those
// associated with qualifiers in the name of the declared entity, but
// not directly associated with the entity itself. (Parameters for
// the entity itself are on the 'TemplateDecl' if it is a template.)
for (unsigned i=0; i < functionDecl->getNumTemplateParameterLists(); ++i) {
clang::TemplateParameterList *paramList =
functionDecl->getTemplateParameterList(i);
cout << " outer template parameter list " << i << "\n"
<< " syntax: \"" << templateParameterListStr(paramList) << "\"\n"
<< " TemplateLoc: " << locStr(paramList->getTemplateLoc()) << "\n"
<< " LAngleLoc: " << locStr(paramList->getLAngleLoc()) << "\n"
<< " RAngleLoc: " << locStr(paramList->getRAngleLoc()) << "\n"
;
}
return true;
}
void Visitor::traverseTU()
{
this->TraverseDecl(m_astContext.getTranslationUnitDecl());
}
int main(int argc, char const **argv)
{
// Copy the arguments into a vector of char pointers since that is
// what 'createInvocationFromCommandLine' wants.
std::vector<char const *> commandLine;
{
// Path to the 'clang' binary that I am behaving like. This path is
// used to compute the location of compiler headers like stddef.h.
commandLine.push_back(CLANG_LLVM_INSTALL_DIR "/bin/clang");
for (int i = 1; i < argc; ++i) {
commandLine.push_back(argv[i]);
}
}
// Parse the command line options.
std::shared_ptr<clang::CompilerInvocation> compilerInvocation(
clang::createInvocation(llvm::ArrayRef(commandLine)));
if (!compilerInvocation) {
// Command line parsing errors have already been printed.
return 2;
}
// Boilerplate.
std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
new clang::PCHContainerOperations());
clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
clang::CompilerInstance::createDiagnostics(
&(compilerInvocation->getDiagnosticOpts())));
// Run the Clang parser to produce an AST.
std::unique_ptr<clang::ASTUnit> ast(
clang::ASTUnit::LoadFromCompilerInvocationAction(
compilerInvocation,
pchContainerOps,
diagnosticsEngine));
if (ast == nullptr ||
diagnosticsEngine->getNumErrors() > 0) {
// Error messages have already been printed.
return 2;
}
Visitor visitor(ast.get());
visitor.traverseTU();
return 0;
}
// EOF
# Makefile
# Default target.
all:
.PHONY: all
# ---- Configuration ----
# Installation directory from a binary distribution.
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04
# ---- llvm-config query results ----
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config
# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)
# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)
# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)
# ---- Compiler options ----
# C++ compiler.
CXX := $(CLANG_LLVM_INSTALL_DIR)/bin/clang++
# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror
# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)
# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'
# Linker options.
LDFLAGS =
# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp
# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)
# It appears that llvm::raw_os_ostream::~raw_os_ostream is missing from
# libclang-cpp, so I have to link with LLVMSupport statically.
LDFLAGS += -lLLVMSupport
# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)
# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cc
$(CXX) -c -o $@ $(CXXFLAGS) $<
# Executable.
all: outer-tparams.exe
outer-tparams.exe: outer-tparams.o
$(CXX) -g -Wall -o $@ $^ $(LDFLAGS)
# Test.
.PHONY: run
run: outer-tparams.exe
./outer-tparams.exe test.cc
.PHONY: clean
clean:
$(RM) *.o *.exe
# EOF
当在
test.cc
上运行时,问题中的示例:
template<class T>
struct my_class
{
template<class U>
void foo(U arg);
};
template<class V>
template<class W> void my_class<V>::foo(W arg)
{
}
int main()
{
return 0;
}
它打印:
FunctionDecl: my_class::foo declared at test.cc:5:8
FunctionDecl: my_class::foo declared at test.cc:9:37
outer template parameter list 0
syntax: "template <class V>"
TemplateLoc: test.cc:8:1 <-------- what we wanted
LAngleLoc: test.cc:8:9
RAngleLoc: test.cc:8:17
FunctionDecl: main declared at test.cc:13:5
当运行这个更复杂的示例时:
// test2.cc
// More complicated example.
template <class P1>
class C1 {
template <class P2>
class C2 {
template <class P3>
class C3 {
class C4 {
template <class P5>
class C5 {
int regularMethod();
template <class P6>
int templatedMethod();
};
};
};
};
};
template <class P1>
template <class P2>
template <class P3>
template <class P5> // Last "outer" list.
int C1<P1>::C2<P2>::C3<P3>::C4::C5<P5>::regularMethod()
{
return 1;
}
template <class P1>
template <class P2>
template <class P3>
template <class P5> // Last "outer" list.
template <class P6> // Not an "outer" list.
int C1<P1>::C2<P2>::C3<P3>::C4::C5<P5>::templatedMethod()
{
return 2;
}
template <>
template <>
template <>
template <>
template <> // This *is* an "outer" template parameter list.
int C1<int>::C2<int>::C3<int>::C4::C5<int>::templatedMethod<int>()
{
return 3;
}
// EOF
它打印:
FunctionDecl: C1::C2::C3::C4::C5::regularMethod declared at test2.cc:13:15
FunctionDecl: C1::C2::C3::C4::C5::templatedMethod declared at test2.cc:16:15
FunctionDecl: C1::C2::C3::C4::C5::regularMethod declared at test2.cc:27:41
outer template parameter list 0
syntax: "template <class P1>"
TemplateLoc: test2.cc:23:1
LAngleLoc: test2.cc:23:10
RAngleLoc: test2.cc:23:19
outer template parameter list 1
syntax: "template <class P2>"
TemplateLoc: test2.cc:24:1
LAngleLoc: test2.cc:24:10
RAngleLoc: test2.cc:24:19
outer template parameter list 2
syntax: "template <class P3>"
TemplateLoc: test2.cc:25:1
LAngleLoc: test2.cc:25:10
RAngleLoc: test2.cc:25:19
outer template parameter list 3
syntax: "template <class P5>"
TemplateLoc: test2.cc:26:1
LAngleLoc: test2.cc:26:10
RAngleLoc: test2.cc:26:19
FunctionDecl: C1::C2::C3::C4::C5::templatedMethod declared at test2.cc:37:41
outer template parameter list 0
syntax: "template <class P1>"
TemplateLoc: test2.cc:32:1
LAngleLoc: test2.cc:32:10
RAngleLoc: test2.cc:32:19
outer template parameter list 1
syntax: "template <class P2>"
TemplateLoc: test2.cc:33:1
LAngleLoc: test2.cc:33:10
RAngleLoc: test2.cc:33:19
outer template parameter list 2
syntax: "template <class P3>"
TemplateLoc: test2.cc:34:1
LAngleLoc: test2.cc:34:10
RAngleLoc: test2.cc:34:19
outer template parameter list 3
syntax: "template <class P5>"
TemplateLoc: test2.cc:35:1
LAngleLoc: test2.cc:35:10
RAngleLoc: test2.cc:35:19
FunctionDecl: C1<int>::C2<int>::C3<int>::C4::C5<int>::templatedMethod declared at test2.cc:47:45
outer template parameter list 0
syntax: "template <>"
TemplateLoc: test2.cc:42:1
LAngleLoc: test2.cc:42:10
RAngleLoc: test2.cc:42:11
outer template parameter list 1
syntax: "template <>"
TemplateLoc: test2.cc:43:1
LAngleLoc: test2.cc:43:10
RAngleLoc: test2.cc:43:11
outer template parameter list 2
syntax: "template <>"
TemplateLoc: test2.cc:44:1
LAngleLoc: test2.cc:44:10
RAngleLoc: test2.cc:44:11
outer template parameter list 3
syntax: "template <>"
TemplateLoc: test2.cc:45:1
LAngleLoc: test2.cc:45:10
RAngleLoc: test2.cc:45:11
outer template parameter list 4
syntax: "template <>"
TemplateLoc: test2.cc:46:1
LAngleLoc: test2.cc:46:10
RAngleLoc: test2.cc:46:11