Opt -dot-cfg with exceptions

I try to acquire a visual CFG representation of some code like this:

template <int> int throwing();
template <int> int nonthrowing() noexcept;

int test(int a) {
    a = throwing<0>() + nonthrowing<0>();
    try {
        a = throwing<1>() + throwing<2>() + nonthrowing<1>();
        try {
            a = throwing<3>();
            if (a % 2 == 0)
                throw(a == 22 || a == 44);
        } catch (bool) {
            return throwing<4>();
        } catch (...) {
            throw throwing<5>() + nonthrowing<2>();
        }
        return a + 1;
    } catch (char c) {
        return -2;
    } catch (int ec) {
        return throwing<6>() + ec;
    }
    return a;
}

However, when I run the following command

clang++ exceptions.cpp -emit-llvm -c
opt -dot-cfg exceptions.bc
#... Writing '.__clang_call_terminate.dot'...

The only output I get is this:

cat .__clang_call_terminate.dot
digraph "CFG for '__clang_call_terminate' function" {
        label="CFG for '__clang_call_terminate' function";

        Node0x74c5730 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{%1:\l  %2 = call i8* @__cxa_begin_catch(i8* %0) #5\l  call void @_ZSt9terminatev() #6\l  unreachable\l}"];
}

When I check the exceptions.ll produced by clang -S -emit-llvm exceptions.cpp I can see a lot more basic-block and jumps all around, so the question remains.

How can I get the CFG of the C++ code I mentioned?

CFG is generated per function, running your example locally:

~/test# clang++ test.cpp -emit-llvm -c
~/test# opt -dot-cfg test.bc 
WARNING: You're attempting to print out a bitcode file.
This is inadvisable as it may cause display problems. If
you REALLY want to taste LLVM bitcode first-hand, you
can force output with the `-f' option.

Writing '._Z4testi.dot'...
Writing '.__clang_call_terminate.dot'...
~/test# head ._Z4testi.dot 
digraph "CFG for '_Z4testi' function" {
        label="CFG for '_Z4testi' function";

        Node0x37c0570 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{%1:\l  %2 = alloca i32, align 4\l  %3 = alloca i32, align 4\l  %4 = alloca i8*, align 8\l  %5 = alloca i32, align 4\l  %6 = alloca i8, align 1\l  %7 = alloca i32, align 4\l  %8 = alloca i8, align 1\l  store i32 %0, i32* %3, align 4\l  %9 = call i32 @_Z8throwingILi0EEiv()\l  %10 = call i32 @_Z11nonthrowingILi0EEiv() #5\l  %11 = add nsw i32 %9, %10\l  store i32 %11, i32* %3, align 4\l  %12 = invoke i32 @_Z8throwingILi1EEiv()\l          to label %13 unwind label %34\l}"];

As a sanity check, perhaps try using a different version of Clang/build you own?

For me the complete output of opt is this:

WARNING: You're attempting to print out a bitcode file.
This is inadvisable as it may cause display problems. If
you REALLY want to taste LLVM bitcode first-hand, you
can force output with the `-f' option.

Writing '.__clang_call_terminate.dot'...

Thus, what you have is different. I don’t have a ._Z4testi.dot file.
I tried it with the official clang-14 ubuntu package.
I also tried this with a top-of-the-tree clang build yesterday with the same results on an Ubuntu 18.04.6 LTS machine.

Anyway, could you please attach the ._Z4testi.dot file to at least have that?

Oh interesting, building clang-14 I don’t see ._Z4testi.dot showing up. My first run was using clang-12 and ._Z4testi.dot shows up again.

Here’s the complete contents of the file:

digraph "CFG for '_Z4testi' function" {
	label="CFG for '_Z4testi' function";

	Node0x2c1f70 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{entry:\l  %retval = alloca i32, align 4\l  %a.addr = alloca i32, align 4\l  %exn.slot = alloca i8*, align 8\l  %ehselector.slot = alloca i32, align 4\l  %0 = alloca i8, align 1\l  %ec = alloca i32, align 4\l  %c = alloca i8, align 1\l  store i32 %a, i32* %a.addr, align 4\l  %call = call noundef i32 @_Z8throwingILi0EEiv()\l  %call1 = call noundef i32 @_Z11nonthrowingILi0EEiv() #5\l  %add = add nsw i32 %call, %call1\l  store i32 %add, i32* %a.addr, align 4\l  %call2 = invoke noundef i32 @_Z8throwingILi1EEiv()\l          to label %invoke.cont unwind label %lpad\l}"];
	Node0x2c1f70 -> Node0x2cb650;
	Node0x2c1f70 -> Node0x2cb830;
	Node0x2cb650 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{invoke.cont:                                      \l  %call4 = invoke noundef i32 @_Z8throwingILi2EEiv()\l          to label %invoke.cont3 unwind label %lpad\l}"];
	Node0x2cb650 -> Node0x2cb6a0;
	Node0x2cb650 -> Node0x2cb830;
	Node0x2cb6a0 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{invoke.cont3:                                     \l  %add5 = add nsw i32 %call2, %call4\l  %call6 = call noundef i32 @_Z11nonthrowingILi1EEiv() #5\l  %add7 = add nsw i32 %add5, %call6\l  store i32 %add7, i32* %a.addr, align 4\l  %call10 = invoke noundef i32 @_Z8throwingILi3EEiv()\l          to label %invoke.cont9 unwind label %lpad8\l}"];
	Node0x2cb6a0 -> Node0x2cb6f0;
	Node0x2cb6a0 -> Node0x2cb880;
	Node0x2cb6f0 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{invoke.cont9:                                     \l  store i32 %call10, i32* %a.addr, align 4\l  %1 = load i32, i32* %a.addr, align 4\l  %rem = srem i32 %1, 2\l  %cmp = icmp eq i32 %rem, 0\l  br i1 %cmp, label %if.then, label %if.end\l|{<s0>T|<s1>F}}"];
	Node0x2cb6f0:s0 -> Node0x2cb740;
	Node0x2cb6f0:s1 -> Node0x2cba60;
	Node0x2cb740 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7b59970",label="{if.then:                                          \l  %exception = call i8* @__cxa_allocate_exception(i64 1) #5\l  %2 = load i32, i32* %a.addr, align 4\l  %cmp11 = icmp eq i32 %2, 22\l  br i1 %cmp11, label %lor.end, label %lor.rhs\l|{<s0>T|<s1>F}}"];
	Node0x2cb740:s0 -> Node0x2cb7e0;
	Node0x2cb740:s1 -> Node0x2cb790;
	Node0x2cb790 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7bca170",label="{lor.rhs:                                          \l  %3 = load i32, i32* %a.addr, align 4\l  %cmp12 = icmp eq i32 %3, 44\l  br label %lor.end\l}"];
	Node0x2cb790 -> Node0x2cb7e0;
	Node0x2cb7e0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7b59970",label="{lor.end:                                          \l  %4 = phi i1 [ true, %if.then ], [ %cmp12, %lor.rhs ]\l  %frombool = zext i1 %4 to i8\l  store i8 %frombool, i8* %exception, align 16\l  invoke void @__cxa_throw(i8* %exception, i8* bitcast (i8** @_ZTIb to i8*),\l... i8* null) #6\l          to label %unreachable unwind label %lpad8\l}"];
	Node0x2cb7e0 -> Node0x2cbfb0;
	Node0x2cb7e0 -> Node0x2cb880;
	Node0x2cb830 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{lpad:                                             \l  %5 = landingpad \{ i8*, i32 \}\l          catch i8* bitcast (i8** @_ZTIc to i8*)\l          catch i8* bitcast (i8** @_ZTIi to i8*)\l  %6 = extractvalue \{ i8*, i32 \} %5, 0\l  store i8* %6, i8** %exn.slot, align 8\l  %7 = extractvalue \{ i8*, i32 \} %5, 1\l  store i32 %7, i32* %ehselector.slot, align 4\l  br label %catch.dispatch29\l}"];
	Node0x2cb830 -> Node0x2cbc40;
	Node0x2cb880 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{lpad8:                                            \l  %8 = landingpad \{ i8*, i32 \}\l          catch i8* bitcast (i8** @_ZTIb to i8*)\l          catch i8* null\l  %9 = extractvalue \{ i8*, i32 \} %8, 0\l  store i8* %9, i8** %exn.slot, align 8\l  %10 = extractvalue \{ i8*, i32 \} %8, 1\l  store i32 %10, i32* %ehselector.slot, align 4\l  br label %catch.dispatch\l}"];
	Node0x2cb880 -> Node0x2cb8d0;
	Node0x2cb8d0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{catch.dispatch:                                   \l  %sel = load i32, i32* %ehselector.slot, align 4\l  %11 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIb to i8*)) #5\l  %matches = icmp eq i32 %sel, %11\l  br i1 %matches, label %catch21, label %catch\l|{<s0>T|<s1>F}}"];
	Node0x2cb8d0:s0 -> Node0x2cb920;
	Node0x2cb8d0:s1 -> Node0x2cb9c0;
	Node0x2cb920 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{catch21:                                          \l  %exn22 = load i8*, i8** %exn.slot, align 8\l  %12 = call i8* @__cxa_begin_catch(i8* %exn22) #5\l  %13 = load i8, i8* %12, align 1\l  %tobool = trunc i8 %13 to i1\l  %frombool23 = zext i1 %tobool to i8\l  store i8 %frombool23, i8* %0, align 1\l  %call26 = invoke noundef i32 @_Z8throwingILi4EEiv()\l          to label %invoke.cont25 unwind label %lpad24\l}"];
	Node0x2cb920 -> Node0x2cb970;
	Node0x2cb920 -> Node0x2cbbf0;
	Node0x2cb970 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{invoke.cont25:                                    \l  store i32 %call26, i32* %retval, align 4\l  call void @__cxa_end_catch() #5\l  br label %return\l}"];
	Node0x2cb970 -> Node0x2cbec0;
	Node0x2cb9c0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b9d0f970",label="{catch:                                            \l  %exn = load i8*, i8** %exn.slot, align 8\l  %14 = call i8* @__cxa_begin_catch(i8* %exn) #5\l  %exception13 = call i8* @__cxa_allocate_exception(i64 4) #5\l  %15 = bitcast i8* %exception13 to i32*\l  %call16 = invoke noundef i32 @_Z8throwingILi5EEiv()\l          to label %invoke.cont15 unwind label %lpad14\l}"];
	Node0x2cb9c0 -> Node0x2cba10;
	Node0x2cb9c0 -> Node0x2cbab0;
	Node0x2cba10 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b2ccfb70",label="{invoke.cont15:                                    \l  %call17 = call noundef i32 @_Z11nonthrowingILi2EEiv() #5\l  %add18 = add nsw i32 %call16, %call17\l  store i32 %add18, i32* %15, align 16\l  invoke void @__cxa_throw(i8* %exception13, i8* bitcast (i8** @_ZTIi to i8*),\l... i8* null) #6\l          to label %unreachable unwind label %lpad19\l}"];
	Node0x2cba10 -> Node0x2cbfb0;
	Node0x2cba10 -> Node0x2cbb00;
	Node0x2cba60 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{if.end:                                           \l  br label %try.cont\l}"];
	Node0x2cba60 -> Node0x2cbdd0;
	Node0x2cbab0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b2ccfb70",label="{lpad14:                                           \l  %16 = landingpad \{ i8*, i32 \}\l          cleanup\l          catch i8* bitcast (i8** @_ZTIc to i8*)\l          catch i8* bitcast (i8** @_ZTIi to i8*)\l  %17 = extractvalue \{ i8*, i32 \} %16, 0\l  store i8* %17, i8** %exn.slot, align 8\l  %18 = extractvalue \{ i8*, i32 \} %16, 1\l  store i32 %18, i32* %ehselector.slot, align 4\l  call void @__cxa_free_exception(i8* %exception13) #5\l  br label %ehcleanup\l}"];
	Node0x2cbab0 -> Node0x2cbb50;
	Node0x2cbb00 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b2ccfb70",label="{lpad19:                                           \l  %19 = landingpad \{ i8*, i32 \}\l          cleanup\l          catch i8* bitcast (i8** @_ZTIc to i8*)\l          catch i8* bitcast (i8** @_ZTIi to i8*)\l  %20 = extractvalue \{ i8*, i32 \} %19, 0\l  store i8* %20, i8** %exn.slot, align 8\l  %21 = extractvalue \{ i8*, i32 \} %19, 1\l  store i32 %21, i32* %ehselector.slot, align 4\l  br label %ehcleanup\l}"];
	Node0x2cbb00 -> Node0x2cbb50;
	Node0x2cbb50 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b9d0f970",label="{ehcleanup:                                        \l  invoke void @__cxa_end_catch()\l          to label %invoke.cont20 unwind label %terminate.lpad\l}"];
	Node0x2cbb50 -> Node0x2cbba0;
	Node0x2cbb50 -> Node0x2cbf60;
	Node0x2cbba0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b9d0f970",label="{invoke.cont20:                                    \l  br label %catch.dispatch29\l}"];
	Node0x2cbba0 -> Node0x2cbc40;
	Node0x2cbbf0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#b9d0f970",label="{lpad24:                                           \l  %22 = landingpad \{ i8*, i32 \}\l          cleanup\l          catch i8* bitcast (i8** @_ZTIc to i8*)\l          catch i8* bitcast (i8** @_ZTIi to i8*)\l  %23 = extractvalue \{ i8*, i32 \} %22, 0\l  store i8* %23, i8** %exn.slot, align 8\l  %24 = extractvalue \{ i8*, i32 \} %22, 1\l  store i32 %24, i32* %ehselector.slot, align 4\l  call void @__cxa_end_catch() #5\l  br label %catch.dispatch29\l}"];
	Node0x2cbbf0 -> Node0x2cbc40;
	Node0x2cbc40 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7af9170",label="{catch.dispatch29:                                 \l  %sel30 = load i32, i32* %ehselector.slot, align 4\l  %25 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIc to i8*)) #5\l  %matches31 = icmp eq i32 %sel30, %25\l  br i1 %matches31, label %catch40, label %catch.fallthrough\l|{<s0>T|<s1>F}}"];
	Node0x2cbc40:s0 -> Node0x2cbc90;
	Node0x2cbc40:s1 -> Node0x2cbce0;
	Node0x2cbc90 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7b59970",label="{catch40:                                          \l  %exn41 = load i8*, i8** %exn.slot, align 8\l  %26 = call i8* @__cxa_begin_catch(i8* %exn41) #5\l  %27 = load i8, i8* %26, align 1\l  store i8 %27, i8* %c, align 1\l  store i32 -2, i32* %retval, align 4\l  call void @__cxa_end_catch() #5\l  br label %return\l}"];
	Node0x2cbc90 -> Node0x2cbec0;
	Node0x2cbce0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7b59970",label="{catch.fallthrough:                                \l  %28 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*)) #5\l  %matches32 = icmp eq i32 %sel30, %28\l  br i1 %matches32, label %catch33, label %eh.resume\l|{<s0>T|<s1>F}}"];
	Node0x2cbce0:s0 -> Node0x2cbd30;
	Node0x2cbce0:s1 -> Node0x2cbf10;
	Node0x2cbd30 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7bca170",label="{catch33:                                          \l  %exn34 = load i8*, i8** %exn.slot, align 8\l  %29 = call i8* @__cxa_begin_catch(i8* %exn34) #5\l  %30 = bitcast i8* %29 to i32*\l  %31 = load i32, i32* %30, align 4\l  store i32 %31, i32* %ec, align 4\l  %call37 = invoke noundef i32 @_Z8throwingILi6EEiv()\l          to label %invoke.cont36 unwind label %lpad35\l}"];
	Node0x2cbd30 -> Node0x2cbd80;
	Node0x2cbd30 -> Node0x2cbe20;
	Node0x2cbd80 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7bca170",label="{invoke.cont36:                                    \l  %32 = load i32, i32* %ec, align 4\l  %add38 = add nsw i32 %call37, %32\l  store i32 %add38, i32* %retval, align 4\l  call void @__cxa_end_catch() #5\l  br label %return\l}"];
	Node0x2cbd80 -> Node0x2cbec0;
	Node0x2cbdd0 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{try.cont:                                         \l  %33 = load i32, i32* %a.addr, align 4\l  %add28 = add nsw i32 %33, 1\l  store i32 %add28, i32* %retval, align 4\l  br label %return\l}"];
	Node0x2cbdd0 -> Node0x2cbec0;
	Node0x2cbe20 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#aec9fc70",label="{lpad35:                                           \l  %34 = landingpad \{ i8*, i32 \}\l          cleanup\l  %35 = extractvalue \{ i8*, i32 \} %34, 0\l  store i8* %35, i8** %exn.slot, align 8\l  %36 = extractvalue \{ i8*, i32 \} %34, 1\l  store i32 %36, i32* %ehselector.slot, align 4\l  call void @__cxa_end_catch() #5\l  br label %eh.resume\l}"];
	Node0x2cbe20 -> Node0x2cbf10;
	Node0x2cbe70 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#3d50c370",label="{try.cont42:                                       \l  %37 = load i32, i32* %a.addr, align 4\l  store i32 %37, i32* %retval, align 4\l  br label %return\l}"];
	Node0x2cbe70 -> Node0x2cbec0;
	Node0x2cbec0 [shape=record,color="#b70d28ff", style=filled, fillcolor="#b70d2870",label="{return:                                           \l  %38 = load i32, i32* %retval, align 4\l  ret i32 %38\l}"];
	Node0x2cbf10 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#f7bca170",label="{eh.resume:                                        \l  %exn43 = load i8*, i8** %exn.slot, align 8\l  %sel44 = load i32, i32* %ehselector.slot, align 4\l  %lpad.val = insertvalue \{ i8*, i32 \} undef, i8* %exn43, 0\l  %lpad.val45 = insertvalue \{ i8*, i32 \} %lpad.val, i32 %sel44, 1\l  resume \{ i8*, i32 \} %lpad.val45\l}"];
	Node0x2cbf60 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#4c66d670",label="{terminate.lpad:                                   \l  %39 = landingpad \{ i8*, i32 \}\l          catch i8* null\l  %40 = extractvalue \{ i8*, i32 \} %39, 0\l  call void @__clang_call_terminate(i8* %40) #7\l  unreachable\l}"];
	Node0x2cbfb0 [shape=record,color="#3d50c3ff", style=filled, fillcolor="#779af770",label="{unreachable:                                      \l  unreachable\l}"];
}