Open
Description
For the following input:
// test.cc
// Demonstrate CFG printing crash.
template <typename T>
struct Derived : public T {
Derived()
: T()
{}
};
// EOF
If CFG::buildCFG
is used with BuildOptions::AddInitializers
set to true
, then the result printed with CFG::print
, printing crashes:
scott@mint201vm:~/wrk/clang/bug-print-cfg$ lldb -- ./print-cfg.exe test.cc
(lldb) target create "./print-cfg.exe"
Current executable set to '/home/scott/wrk/clang/bug-print-cfg/print-cfg.exe' (x86_64).
(lldb) settings set -- target.run-args "test.cc"
(lldb) run
Process 591398 launched: '/home/scott/wrk/clang/bug-print-cfg/print-cfg.exe' (x86_64)
CFG for Derived::Derived<T>:
[B2 (ENTRY)]
Succs (1): B1
[B1]
1: Process 591398 stopped
* thread #1, name = 'print-cfg.exe', stop reason = signal SIGSEGV: invalid address (fault address: 0x28)
frame #0: 0x00007ffff036715b libclang-cpp.so.16`print_initializer(llvm::raw_ostream&, (anonymous namespace)::StmtPrinterHelper&, clang::CXXCtorInitializer const*) + 91
libclang-cpp.so.16`print_initializer:
-> 0x7ffff036715b <+91>: movq 0x28(%rax), %rax
0x7ffff036715f <+95>: testb $0x7, %al
0x7ffff0367161 <+97>: jne 0x7ffff03671b9 ; <+185>
0x7ffff0367163 <+99>: andq $-0x8, %rax
(lldb) bt
* thread #1, name = 'print-cfg.exe', stop reason = signal SIGSEGV: invalid address (fault address: 0x28)
* frame #0: 0x00007ffff036715b libclang-cpp.so.16`print_initializer(llvm::raw_ostream&, (anonymous namespace)::StmtPrinterHelper&, clang::CXXCtorInitializer const*) + 91
frame #1: 0x00007ffff035352b libclang-cpp.so.16`print_elem(llvm::raw_ostream&, (anonymous namespace)::StmtPrinterHelper&, clang::CFGElement const&) + 587
frame #2: 0x00007ffff0354d11 libclang-cpp.so.16`print_block(llvm::raw_ostream&, clang::CFG const*, clang::CFGBlock const&, (anonymous namespace)::StmtPrinterHelper&, bool, bool) + 1969
frame #3: 0x00007ffff035449f libclang-cpp.so.16`clang::CFG::print(llvm::raw_ostream&, clang::LangOptions const&, bool) const + 143
frame #4: 0x0000555555557844 print-cfg.exe`PrintCFGs::VisitFunctionDecl(clang::FunctionDecl*) + 340
frame #5: 0x000055555564776d print-cfg.exe`clang::RecursiveASTVisitor<PrintCFGs>::WalkUpFromFunctionDecl(clang::FunctionDecl*) + 93
[...bunch of RAV frames omitted...]
frame #17: 0x0000555555559646 print-cfg.exe`clang::RecursiveASTVisitor<PrintCFGs>::TraverseDecl(clang::Decl*) + 3910
frame #18: 0x0000555555557b83 print-cfg.exe`main + 771
frame #19: 0x00007fffed50f0b3 libc.so.6`__libc_start_main + 243
frame #20: 0x000055555555762e print-cfg.exe`_start + 46
The problem happens with Clang 16 as well as a trunk build from 2023-09-13.
Impact: This is mainly troublesome because printing the CFG is a natural thing to do while debugging, so when that crashes too it impedes the original debug effort.
Complete program to reproduce:
// print-cfg.cc
// Print CFGs of all functions.
#include "clang/AST/RecursiveASTVisitor.h" // clang::RecursiveASTVisitor
#include "clang/Analysis/CFG.h" // clang::CFG
#include "clang/Frontend/ASTUnit.h" // clang::ASTUnit
#include "clang/Frontend/CompilerInstance.h" // clang::CompilerInstance
#include "clang/Frontend/CompilerInvocation.h" // clang::CompilerInvocation
#include "clang/Frontend/Utils.h" // clang::createInvocation
#include "clang/Serialization/PCHContainerOperations.h" // clang::PCHContainerOperations
class PrintCFGs : public clang::RecursiveASTVisitor<PrintCFGs> {
public: // data
// The ASTUnit we want to process.
clang::ASTUnit *m_astUnit;
public: // methods
PrintCFGs(clang::ASTUnit *astUnit)
: m_astUnit(astUnit)
{}
clang::ASTContext &getASTContext()
{ return m_astUnit->getASTContext(); }
bool VisitFunctionDecl(clang::FunctionDecl *decl);
};
bool PrintCFGs::VisitFunctionDecl(
clang::FunctionDecl *decl)
{
if (!decl->doesThisDeclarationHaveABody()) {
return true;
}
llvm::outs() << "CFG for " << decl->getQualifiedNameAsString() << ":\n";
llvm::outs().flush();
clang::CFG::BuildOptions buildOptions;
// Setting this option is required for the bug to happen.
buildOptions.AddInitializers = true;
std::unique_ptr<clang::CFG> cfg(clang::CFG::buildCFG(
decl,
decl->getBody(),
&getASTContext(),
buildOptions));
if (!cfg) {
// CFG construction can fail for some template function cases.
return true;
}
// BUG: This crashes for a class template constructor that explicitly
// calls a dependent base class constructor.
cfg->print(llvm::outs(),
getASTContext().getLangOpts(),
false /*showColors*/);
llvm::outs().flush();
return true;
}
// This is boilerplate up to running the visitor.
int main(int argc, char **argv)
{
std::vector<char const *> commandLine;
commandLine.push_back(CLANG_LLVM_INSTALL_DIR "/bin/clang");
for (int i = 1; i < argc; ++i) {
commandLine.push_back(argv[i]);
}
std::shared_ptr<clang::CompilerInvocation> compilerInvocation(
clang::createInvocation(llvm::ArrayRef(commandLine)));
if (!compilerInvocation) {
return 2;
}
std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
new clang::PCHContainerOperations());
clang::DiagnosticOptions *diagnosticOptions =
&(compilerInvocation->getDiagnosticOpts());
clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
clang::CompilerInstance::createDiagnostics(
diagnosticOptions /*callee takes refcount ownership*/));
std::unique_ptr<clang::ASTUnit> ast(
clang::ASTUnit::LoadFromCompilerInvocationAction(
compilerInvocation,
pchContainerOps,
diagnosticsEngine));
if (ast == nullptr || diagnosticsEngine->getNumErrors() > 0) {
return 2;
}
clang::TranslationUnitDecl *tu =
ast->getASTContext().getTranslationUnitDecl();
PrintCFGs visitor(ast.get());
visitor.TraverseDecl(tu);
return 0;
}
// EOF
Makefile
:
# print-cfg/Makefile
# ---- Configuration ----
# Set to 1 if I am using a build from source, 0 for a binary
# distribution.
USE_SOURCE_BUILD := 0
ifeq ($(USE_SOURCE_BUILD),1)
# Use my own build.
CLANG_LLVM_SRC_DIR = $(HOME)/wrk/clang/llvm-project
CLANG_LLVM_INSTALL_DIR = $(CLANG_LLVM_SRC_DIR)/build
else
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04
endif
# Let the user override my defaults.
-include pre-config.mk
# ---- 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
# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment
# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)
ifeq ($(USE_SOURCE_BUILD),1)
# When using my own build, I need to separately point at clang includes.
CXXFLAGS += -I$(CLANG_LLVM_SRC_DIR)/clang/include
CXXFLAGS += -I$(CLANG_LLVM_INSTALL_DIR)/tools/clang/include
endif
# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'
# Switch to enable creation of .d files.
GENDEPS_FLAGS = -MMD
# 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)
# Optional custom modifications.
-include config.mk
# ---- Recipes ----
# Default target.
all:
.PHONY: all
# Pull in automatic dependencies.
-include $(wildcard *.d)
# Compile a C++ source file.
%.o: %.cc
$(CXX) -c -o $@ $(GENDEPS_FLAGS) $(CXXFLAGS) $<
OBJS :=
OBJS += print-cfg.o
# Executable.
all: print-cfg.exe
print-cfg.exe: $(OBJS)
$(CXX) -g -Wall -o $@ $(OBJS) $(LDFLAGS)
# Test.
.PHONY: check
check: print-cfg.exe test.cc
./print-cfg.exe test.cc
.PHONY: clean
clean:
$(RM) *.o *.d *.exe
$(RM) -r out
# EOF