Understanding Clang CFG for try/catch statements

Viewed 127

I'm trying to understand Clang's CFG by looking at its dumped output and it's unclear to me how try/catch statements are represented in the CFG.

Consider this little snippet:

int func(int x);

int func2(int x) {
  try {
    return func(x);
  } catch(...) {
    return 0;
  }
}

The dumped CFG is the following:

$ clang++ -Xclang -analyze -Xclang -analyzer-checker=debug.DumpCFG -fsyntax-only test.cpp
int func2(int x)
 [B4 (ENTRY)]
   Succs (1): B3

 [B1]
   T: try ...
   Succs (1): B2

 [B2]
  catch (...):
   1: catch (...) {
[B2.3]}
   2: 0
   3: return [B2.2];
   Preds (1): B1
   Succs (1): B0

 [B3]
   1: func
   2: [B3.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(int))
   3: x
   4: [B3.3] (ImplicitCastExpr, LValueToRValue, int)
   5: [B3.2]([B3.4])
   6: return [B3.5];
   Preds (1): B4
   Succs (1): B0

 [B0 (EXIT)]
   Preds (2): B2 B3

I do not understand how the B1 basic block is linked to the others. The entry block seemingly jumps directly to B3 which contains the body of the try{} statement. Then, B3 has the exit block as its only successor. So B1 and B2 seems to be unlinked from the main flow of the function.

How do I have to interpret the CFG in this case?

1 Answers

clang's CFG support for try statements isn't complete.

12 years ago, clang used to add an edge from every function call to these exception blocks. But https://github.com/llvm/llvm-project/commit/04c6851cd6053c638e68bf1d7b99dda14ea267fb undid that in the name of build performance (exceptions aren't only difficult to reason about for humans), and that's how things still mostly look today: https://github.com/llvm/llvm-project/blob/d677a7cb056b17145a50ec8ca2ab6d5f4c494749/clang/lib/Analysis/CFG.cpp#L2636

There's a bool to turn these edges on, but it's not hooked up to any clang commandline flag.

So these CFG blocks for try statements are mostly not used. They have the catch blocks as successors, and reachable code analysis just treats them as additional roots (essentially assuming that it's always possible for something in the try block to to throw). (See mentions of "CXXTryStmt" in https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/AnalysisBasedWarnings.cpp)

The one thing that does add an edge to a CFG block for a try statement is an explicit throw in try try body:

% cat foo.cc
int func() {
  try {
    throw 0;
  } catch(...) {
    return 0;
  }
}

% clang++ -Xclang -analyze -Xclang -analyzer-checker=debug.DumpCFG -fsyntax-only foo.cc
int func()
 [B4 (ENTRY)]
   Succs (1): B3

 [B1]
   T: try ...
   Preds (1): B3
   Succs (1): B2

 [B2]
  catch (...):
   1: catch (...) {
[B2.3]}
   2: 0
   3: return [B2.2];
   Preds (1): B1
   Succs (1): B0

 [B3]
   1: 0
   2: throw [B3.1]
   Preds (1): B4
   Succs (1): B1

 [B0 (EXIT)]
   Preds (1): B2
Related