False positive for -Wunreachable-code

The following typescript shows that clang incorrectly seems to mark as
unreachable a throw of a temporary.

Please note that if I remove the destructor of struct s, no warning is
generated.

$ cat w.cc
struct s {
  explicit s() { }
  ~s() { }
};

int f_bad(int x) {
  if (x)
    throw s();
  return 0;
}

int f_good(int x) {
  if (x) {
    s e;
    throw e;
  }
  return 0;
}
$ llvm/Debug+Asserts/bin/clang++ -Wunreachable-code -c w.cc
w.cc:8:5: warning: will never be executed [-Wunreachable-code]
    throw s();
    ^~~~~~~~~
1 warning generated.

-Wunreachable-code works by looking for CFG blocks without predecessors. If we compare the two attached CFGs, we can see that when the destructor is present there is an extra 'unreachable' block that starts with the throw statement. I'm guessing this has to do with the implementation of C++ destructors and exceptions (implicit destructor call?), but I'm not an expert in this area. I'll let someone with more C++ CFG knowledge confirm.

CFG-with-destructor.dot (645 Bytes)

CFG-without-destructor.dot (542 Bytes)

I don't know the C++ CFG, but I can explain the semantics just so we're clear.

Destructors for temporaries are called at the end of the full-expression in the reverse order of construction. So in f(A(), B()), one valid ordering of execution is:
1. a := A()
2. b := B()
3. f(a,b)
4. b.~B()
5. a.~A()

If there's an exception at any point, you call the destructors for the temporaries you've already created:
1. a := A()
2. b := B() // If this throws, do 5
3. f(a,b) // If this throws, do 4 and 5
4. b.~B() // If this throws, do 5
5. a.~A()

If f is noreturn — or if it's not actually a call, but instead something like a throw — then 4 and 5 are unreachable along the normal execution path, but they *are* still reachable along the exceptions path.

In addition to all this, the exception itself is a temporary object whose destructor is not actually called until it is caught and its catch handler exits without rethrowing it.

The CFG is probably trying to model that the destructor for 's' (associated with the full expression) is not reachable along the normal path.

John.