clang-tidy and static analysis for exception safety

Hello everybody,

I want to implement an check for proper exception specification (noexcept correctness). In code review aaron.ballman pointed out that there is path sensitive analysis necessary. Since i have no experience and knowledge on that I would like to have some input from experienced programmers on that topic.

What should be possible with AST - Matching in clang-tidy:

  • a function calling only noexcept operations that does not throw can be marked noexcept
  • a function that has a throw statement not within a try/catch block can be marked noexcept(false)
  • a function calling a function that is not marked noexcept can not be marked noexcept
    but it could be noexecpt with deeper analysis on what exception could be thrown and what exceptions are handled

Having logic for these cases would allow to automatically improve noexcept-correctness.
Maybe more importantly it would be possible to find destructors and deallocation functions that could throw but never should.

Is there already functionality for such analysis, is my model even correct and how hard would the analysis be?
I have no experience in CSA development but iam fairly interested in it and would like to learn if reasonably complicated.

All the best
Jonas

As far as I see:

  • a function calling only noexcept operations that does not throw can be marked noexcept

possible

  • a function that has a throw statement not within a try/catch block can be marked noexcept(false)

Not possible

  • a function calling a function that is not marked noexcept can not be marked noexcept

but it could be noexecpt with deeper analysis on what exception could be thrown and what exceptions are handled

not possible

As far as I see:

  • a function calling only noexcept operations that does not throw can be marked noexcept

possible

  • a function that has a throw statement not within a try/catch block can be marked noexcept(false)

Not possible

  • a function calling a function that is not marked noexcept can not be marked noexcept

but it could be noexecpt with deeper analysis on what exception could be thrown and what exceptions are handled

not possible

Yea, none of these seem to be path sensitive, but only flow sensitive, which clang-tidy supports.

Ok. So i will try to start with a prototype that can the basic stuff i think of. Is there a reference/example i can look into for the flow sensitivity? I am not experienced with clang-tidy, but want to become. :slight_smile:

How would the analysis look like to see if a catch will actually catch all possible exceptions that could exist in the try block? Especially in destructors this is interesting for safety.

+mboehme, author of the use-after-move check

Yea, none of these seem to be path sensitive, but only flow sensitive, which clang-tidy supports.

I’m surprised that even flow sensitivity is needed. I would have thought a purely lexical analysis on the AST would be sufficient. What is the case where the analysis would need to compute per-program-point facts?

I could see context sensitivity being useful (to look through calls to unannotated functions) — but with the exception of function pointers and virtual calls I would expect calls to be better handled by analyzing the call graph from the bottom up, inferring the exception behavior for callees before analyzing the caller.

Devin

Yea, none of these seem to be path sensitive, but only flow sensitive, which clang-tidy supports.

I’m surprised that even flow sensitivity is needed. I would have thought a purely lexical analysis on the AST would be sufficient. What is the case where the analysis would need to compute per-program-point facts?

Well, will depend on whether you’ll count analyzing where the “throw” and the corresponding “catch” is as flow sensitive (I would, but I’m no expert :slight_smile:

Sorry for the late reply.

If you want to see an example of flow-sensitive analysis in clang-tidy, take a look at UseAfterMoveCheck.cpp.

That said, I also don’t see yet why a lexical analysis isn’t sufficient (i.e. check whether the throw is lexically contained within the catch, with special handling at the boundaries of lambdas) – though I’m sure I’m just missing something. Can you provide a link to Aaron’s review?

This article has a good overview of some of the intricacies of noexcept:

https://akrzemi1.wordpress.com/2011/06/10/using-noexcept/

In particular, it has some good examples of functions that can be marked noexcept, even though they call non-noexcept functions, and vice versa (think of a function whose implementation happens not to throw today but which doesn’t want to make that guarantee in its contract). So you’ll need some way of marking false positives and false negatives as intentional.

An additional wrinkle is when the argument to noexcept depends on a template argument – you may want to initially just ignore these cases.

I will try to work on a prototype for the clear cases, so we will find out where problems are.
The review with the remarks from aaron is here https://reviews.llvm.org/D30746

One more thing that came me to mind: Defaulted operations have the noexcept specification from the compiler to they? If yes, there should already exist some logic for marking functions/operations noexcept.

I think that check should be incremental anyway, so the absolute clear cases will be done first. If that works out, more can be added in my opinion.

Greets :slight_smile:

I will try to work on a prototype for the clear cases, so we will find out
where problems are.
The review with the remarks from aaron is here https://reviews.llvm.org/
D30746

Thanks!

I see Aaron's comment there that the analysis would need to be
path-sensitive, but unfortunately he doesn't explain why (or I missed the
explanation). Maybe you want to ask him again?

One more thing that came me to mind: Defaulted operations have the
noexcept specification from the compiler to they? If yes, there should
already exist some logic for marking functions/operations noexcept.

Not something I know anything about -- probably best to simply go digging
in the source.

I think that check should be incremental anyway, so the absolute clear
cases will be done first. If that works out, more can be added in my
opinion.

Probably a good idea for checks like this. You'll want to be biased
towards false negatives, i.e. if the check outputs a message, it's very
likely to be a real problem.