Strange CFG for `try' blocks

Hi everyone,

Recently we've noticed a strange behaviour in CFG construction for function try blocks. Clang test suite has a test case SemaCXX/return-noreturn.cpp with functions functionTryBlock{1,2,3}, and at least for functionTryBlock2 control flow graph looks a bit strange:

$ .../clang -cc1 -nostdsysteminc .../llvm/tools/clang/test/SemaCXX/return-noreturn.cpp -fsyntax-only -fcxx-exceptions -Wreturn-type -Wmissing-noreturn -Wno-unreachable-code -Wno-covered-switch-default -analyze -analyzer-checker debug.DumpCFG 2>&1 | grep functionTryBlock2 -A 19

    int functionTryBlock2(int s)
      [B3 (ENTRY)]
        Succs (1): B0

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

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

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

The strange place here is in the fact that block [B3] goes directly to [B0], leaving the block [B1] non-visited. Moreover, for the below three code examples the generated CFG looks the same:

============ 1 =============

int foo();

int functionTryBlock2(int s) try {
   s += foo();
} catch (...) {
   return s;
}

============ 2 =============

int foo();

int functionTryBlock2(int s) {
   try {
     s += foo();
   } catch (...) {
     return s;
   }
}

============ 3 =============

int foo();

int functionTryBlock2(int s) {
   s += foo();
   try {
   } catch (...) {
     return s;
   }
}

Is it a bug? It seems that `catch' blocks can be stayed without any predecessors, since they are visited not by `control flow', but by the exception. But why `try' blocks are not correctly included into CFG? And how should the correct CFG look like?

Hi everyone,

Recently we’ve noticed a strange behaviour in CFG construction for function try blocks. Clang test suite has a test case SemaCXX/return-noreturn.cpp with functions functionTryBlock{1,2,3}, and at least for functionTryBlock2 control flow graph looks a bit strange:

$ …/clang -cc1 -nostdsysteminc …/llvm/tools/clang/test/SemaCXX/return-noreturn.cpp -fsyntax-only -fcxx-exceptions -Wreturn-type -Wmissing-noreturn -Wno-unreachable-code -Wno-covered-switch-default -analyze -analyzer-checker debug.DumpCFG 2>&1 | grep functionTryBlock2 -A 19

int functionTryBlock2(int s)
[B3 (ENTRY)]
Succs (1): B0

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

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

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

The strange place here is in the fact that block [B3] goes directly to [B0], leaving the block [B1] non-visited. Moreover, for the below three code examples the generated CFG looks the same:

============ 1 =============

int foo();

int functionTryBlock2(int s) try {
s += foo();
} catch (…) {
return s;
}

============ 2 =============

int foo();

int functionTryBlock2(int s) {
try {
s += foo();
} catch (…) {
return s;
}
}

============ 3 =============

int foo();

int functionTryBlock2(int s) {
s += foo();
try {
} catch (…) {
return s;
}
}

Is it a bug? It seems that catch' blocks can be stayed without any predecessors, since they are visited not by control flow’, but by the exception. But why `try’ blocks are not correctly included into CFG? And how should the correct CFG look like?

Unfortunately, support for exceptions in the CFG has not been fully implemented.

"Currently exceptions are treated as “black holes”, and exception-handling control structures are poorly modeled (to be conservative). This could be much improved for both C++ and Objective-C exceptions.”
from https://clang-analyzer.llvm.org/open_projects.html

Anna