[prev in list] [next in list] [prev in thread] [next in thread] 

List:       gcc-bugs
Subject:    [Bug c++/61372] New: Add warning to detect noexcept functions that might throw
From:       "scovich at gmail dot com" <gcc-bugzilla () gcc ! gnu ! org>
Date:       2014-05-30 16:40:40
Message-ID: bug-61372-4 () http ! gcc ! gnu ! org/bugzilla/
[Download RAW message or body]

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61372

            Bug ID: 61372
           Summary: Add warning to detect noexcept functions that might
                    throw
           Product: gcc
           Version: 4.9.0
            Status: UNCONFIRMED
          Severity: enhancement
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: scovich at gmail dot com

The C++11 standard adds the "noexcept" specification that lets the programmer
assert that a function does not throw any exceptions (terminating execution if
that assertion ever turns out to be false at runtime). 

Unfortunately, there is currently no reliable way for a programmer to validate,
at compile time, her assertion that a function does or does not throw. 

The closest thing is -Wnoexcept, which detects the (very narrow) case where the
following all apply to some function F:
1. F lacks the noexcept declaration or has declared noexcept(false)
2. The compiler has determined that F cannot throw
3. F causes some noexcept operator to evaluate to false

Unfortunately, that narrow formulation makes it really hard to validate much of
anything (see example and further discussion below).

It would be very helpful to have a warning flag which tells the compiler to
report cases where a function's noexcept specification contradicts the
compiler's analysis of the function body. Perhaps -Wnoexcept-mismatch={1,2,3}?

1 (high priority): functions declared noexcept(true) but which contain
expressions that might throw. This validates stated noexcept assumptions,
helping to avoid issues like PR #56166.

2 (medium priority): Also report functions declared noexcept(false) that in
fact cannot throw (e.g. cases #1 and #2 for -Wnoexcept). This improves the
accuracy of noexcept  validation, and also improves performance in general (by
eliminating unwind handlers). And makes it easier to avoid/fix things like PR
#52562. 

3 (low priority): Also report functions which lack any noexcept declaration but
which cannot throw (similar to -Wsuggest-attribute for const, pure, etc.). This
also improves accuracy of noexcept, but the programmer would have to decide
whether to make the API change (marking the function noexcept) or whether it's
important to retain the ability to throw in the future.

Probably none of the above warnings should be enabled by default, but it might
make sense to enable -Wnoexcept-mismatch=1 with -Wall and -Wnoexcept-mismatch=2
with -Wextra.

To implement the warning, the compiler would make a pass over each function
body (after applying most optimizations, especially inlining and dead code
elimination). It would then infer a noexcept value by examining all function
calls that remain, and compare that result with the function's actual noexcept
specification (or lack thereof). No need for any kind of IPA: if a callee lies
about its noexcept status, it's the callee's problem.

===========================
Workaround using -Wnoexcept
===========================

One might try to combine static_assert with noexcept, e.g:

// example.cpp ////////////////
void might_throw(int); // lacks noexcept
void also_might_throw(); // lacks noexcept
void never_throw(int a) noexcept(noexcept(might_throw(a)) &&
noexcept(also_might_throw())) {
    if (a)
        might_throw(a);
    also_might_throw();
}
void foo(int a) noexcept(noexcept(might_throw(a))) {
    might_throw(a);
}
static_assert(noexcept(foo(0)), "never_throw might throw");
////////////////////////////////

There are two glaring problems with that approach, however:

1. Every expression in the function body must be part of the noexcept clause,
effectively replicating the function body in its signature (but without the
ability to declare local variables).

   - Maintaining the noexcept chain across code changes would be tedious and
error-prone for all but the smallest and most stable functions (= ie the ones
least in need of verification).

   - Operator overloading means you can't even assume basic expressions like
a+b are nothrow. To get complete coverage would require either a very careful
analysis (error prone) or cracking the entire function body into an AST atomic
expressions (tedious *and* error prone).

   - Macro expansions would add even more headaches, because they may expand to
more than one statement and/or include control flow. 

2. The static_assert must choose one set of inputs for each function call it
passes to operator noexcept. 

   - An optimizer (especially after inlining and constant propagation) could
conceivably report that the function is noexcept for that particular input,
when in fact other inputs exist that could cause an exception to be thrown
(this does not seem to be the case currently). 

   - There may not be any easy way to come up with a valid input (objects that
lack a default constructor, etc.). Using hacks like (*(T*)0) would violate all
sorts of compiler/optimizer assumptions and risks breaking the analysis.

Another possibility would be to embed multiple static_assert in the function to
verify noexcept status for every line of code. That would at least allow to use
existing local variables, and resolve most of issue #2, but issue #1 remains.
Code readability would take quite a hit, too.
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic