考虑foo.cc
中的以下结构定义:
struct Foo
{
int &bar;
};
由于bar
具有引用类型,因此隐式删除了隐式平凡默认构造函数Foo::Foo()
。然而,这个事实似乎并没有反映在clang生成的AST中。例如,运行clang -Xclang -ast-dump foo.cc
会导致:
`-CXXRecordDecl 0x5612ba4c03b8 <test.cc:1:1, col:24> col:8 struct Foo definition
|-DefinitionData pass_in_registers aggregate trivially_copyable trivial literal
| |-DefaultConstructor exists trivial needs_implicit
| |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveConstructor exists simple trivial needs_implicit
| |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveAssignment exists trivial needs_implicit
| `-Destructor simple irrelevant trivial needs_implicit
|-CXXRecordDecl 0x5612ba4c04e0 <col:1, col:8> col:8 implicit struct Foo
`-FieldDecl 0x5612ba4c05b8 <col:14, col:19> col:19 bar 'int &'
所以这里看起来有一个隐含的琐碎默认构造函数存在,但没有提到它被删除。类似地,clang::CXXRecordDecl
API似乎也没有提供确定这一点的方法。但是,在这一点上(在语义分析之后(,这些信息难道不应该可用吗?如何使用clang AST API来查明某些类的隐式琐碎默认构造函数是否被隐式删除?
使用的CCD_ 6Sema
(语义分析(类。这将填充CXXRecordDecl
具有CXXConstructorDecl
节点,然后可以使用CXXRecordDecl::decls
对其进行迭代或使用CCD_ 11。
一个棘手的问题是获取Sema
对象以便调用所需的方法。如果您正在使用的CCD_ 13方法CCD_ 14,返回具有getSema
方法的ASTUnit
对象。
如果您正在使用CCD_ 17,可以使用CCD_ 18直接获取CCD_,或将run
与FrontendAction
并使用其CCD_ 22方法。(教程使此操作变得有些困难,因为它使用newFrontendActionFactory
。处理这种复杂情况可能是一个新问题的主题。(
在任何一种情况下,一旦隐式方法的AST节点创建,isDeleted
方法(FunctionDecl
申报(如果方法已隐式或显式执行,则将返回true
已删除。
最后,请注意,如果您使用RecursiveASTVisitor
访问方法,默认情况下它会跳过隐式定义的方法。更改即,实现shouldVisitImplicitCode
方法并使其返回CCD_ 29。
完整示例
以下是用于生成和运行报告所有构造函数,包括那些隐式的构造函数,以及它们是否已删除。
deleted-ctor.cc
:
// deleted-ctor.cc
// Report deleted constructors.
#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/Sema/Sema.h" // clang::Sema
#include "clang/Serialization/PCHContainerOperations.h" // clang::PCHContainerOperations
#include <iostream> // std::cout
#include <string> // std::string
using std::cout;
using std::string;
class Visitor : public clang::RecursiveASTVisitor<Visitor> {
public: // data
clang::ASTUnit *m_astUnit;
clang::ASTContext &m_astContext;
public: // methods
Visitor(clang::ASTUnit *astUnit)
: m_astUnit(astUnit),
m_astContext(astUnit->getASTContext())
{}
// Convenience methods to stringify some things.
string locStr(clang::SourceLocation loc);
string declLocStr(clang::Decl const *decl);
string typeStr(clang::QualType qualType);
// By default, AST visitors skip implicit nodes even if they are
// present in the AST. Returning true here ensures we will not skip
// them.
bool shouldVisitImplicitCode() { return true; }
// Visitor methods.
bool VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl);
bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *methodDecl);
// Kick off the traversal.
void traverseTU();
};
string Visitor::locStr(clang::SourceLocation loc)
{
return loc.printToString(m_astContext.getSourceManager());
}
string Visitor::declLocStr(clang::Decl const *decl)
{
return locStr(decl->getLocation());
}
string Visitor::typeStr(clang::QualType qualType)
{
return qualType.getAsString();
}
bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl)
{
if (recordDecl->getDefinition() != recordDecl) {
// This is not the definition, so ignore it.
}
else {
cout << recordDecl->Decl::getDeclKindName()
<< " "" << recordDecl->getQualifiedNameAsString()
<< "" at " << declLocStr(recordDecl)
<< ":n";
// Without this, the implicit members are not in the AST.
m_astUnit->getSema().ForceDeclarationOfImplicitMembers(recordDecl);
}
return true;
}
bool Visitor::VisitCXXConstructorDecl(clang::CXXConstructorDecl *ctor)
{
cout << " " << ctor->Decl::getDeclKindName()
<< " "" << ctor->getQualifiedNameAsString()
<< "" type="" << typeStr(ctor->getType())
<< "" at "
<< declLocStr(ctor)
<< ": deleted=" << ctor->isDeleted()
<< "n";
return true;
}
void Visitor::traverseTU()
{
this->TraverseDecl(m_astContext.getTranslationUnitDecl());
}
int main(int argc, char const **argv)
{
// Point 'LoadFromCommandLine' at the clang binary so it will be able
// to find its compiler headers such as stddef.h.
argv[0] = CLANG_LLVM_INSTALL_DIR "/bin/clang";
// Boilerplate setup for 'LoadFromCommandLine'.
std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
new clang::PCHContainerOperations());
clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
clang::CompilerInstance::createDiagnostics(
new clang::DiagnosticOptions));
// Run the Clang parser to produce an AST.
std::unique_ptr<clang::ASTUnit> ast(clang::ASTUnit::LoadFromCommandLine(
argv,
argv + argc,
pchContainerOps,
diagnosticsEngine,
llvm::StringRef() /*ResourceFilesPath, evidently ignored*/));
if (ast == nullptr ||
diagnosticsEngine->getNumErrors() > 0) {
// Error messages have already been printed.
return 2;
}
Visitor visitor(ast.get());
visitor.traverseTU();
return 0;
}
// EOF
Makefile
:
# Makefile
# Default target.
all:
.PHONY: all
# ---- Configuration ----
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
# Downloaded from: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-14.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 = g++
CXX := $(CLANG_LLVM_INSTALL_DIR)/bin/clang++
# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror
# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment
# 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)
# 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: deleted-ctor.exe
deleted-ctor.exe: deleted-ctor.o
$(CXX) -g -Wall -o $@ $^ $(LDFLAGS)
# Test.
.PHONY: run
run: deleted-ctor.exe
./deleted-ctor.exe test.cc
.PHONY: clean
clean:
$(RM) *.o *.exe
# EOF
test.cc
:
// test.cc
// Test for deleted-ctor.exe.
// Explicit ctor, not deleted.
class END {
public:
END();
};
// Explicit ctor, deleted.
class ED {
public:
ED()=delete;
};
// Implicit ctor, not deleted.
class IND {
public:
};
// Implicit ctor, deleted.
class ID {
public:
int &i;
};
// EOF
示例运行:
$ make run
./deleted-ctor.exe test.cc
CXXRecord "END" at test.cc:5:7:
CXXConstructor "END::END" type="void (void)" at test.cc:7:3: deleted=0
CXXConstructor "END::END" type="void (const class END &)" at test.cc:5:7: deleted=0
CXXConstructor "END::END" type="void (class END &&)" at test.cc:5:7: deleted=0
CXXRecord "ED" at test.cc:11:7:
CXXConstructor "ED::ED" type="void (void)" at test.cc:13:3: deleted=1
CXXConstructor "ED::ED" type="void (const class ED &)" at test.cc:11:7: deleted=0
CXXConstructor "ED::ED" type="void (class ED &&)" at test.cc:11:7: deleted=0
CXXRecord "IND" at test.cc:18:7:
CXXConstructor "IND::IND" type="void (void)" at test.cc:18:7: deleted=0
CXXConstructor "IND::IND" type="void (const class IND &)" at test.cc:18:7: deleted=0
CXXConstructor "IND::IND" type="void (class IND &&)" at test.cc:18:7: deleted=0
CXXRecord "ID" at test.cc:23:7:
CXXConstructor "ID::ID" type="void (void)" at test.cc:23:7: deleted=1
CXXConstructor "ID::ID" type="void (const class ID &)" at test.cc:23:7: deleted=0
CXXConstructor "ID::ID" type="void (class ID &&)" at test.cc:23:7: deleted=0
注意,ED
和ID
的第一个构造函数都被标记用CCD_ 35。
在CXXRecordDecl
节点下的AST中不存在隐式构造函数,而仅在DefinitionData
下存在。
您可以在AST插件中使用Sema::ForceDeclarationOfImplicitMembers
来获取构造函数,然后检查是否已删除。
// force declaration of implict constructors
clang::Sema &sema = Instance.getSema();
sema.ForceDeclarationOfImplicitMembers(const_cast<CXXRecordDecl*>(cxxRecordDecl));
// then check whether constructor is deleted
for (const auto* ctor : cxxRecordDecl->ctors()) {
if (ctor->isDeleted() {
// do something
}
}