N1478: Supporting 'noreturn' in C1x

N1478: Supporting the 'noreturn' property in C1x

David Svoboda
svoboda@cert.org
Original: 2010-01-05
Current: 2010-05-25

The noreturn keyword is useful for a few library functions such as abort and exit which cannot return. The user can also define their own functions that never return using this keyword.


_Noreturn void fatal (void); 
void fatal() { 
  /* ... */
  exit(1); 
} 

The noreturn keyword tells the compiler to assume that fatal() cannot return. It can then optimize without regard to what would happen if fatal ever did return. This makes slightly better code. More importantly, it helps avoid spurious warnings of uninitialized variables. You cannot assume that registers saved by the calling function are restored before calling the noreturn function. It does not make sense for a noreturn function to have a return type other than void.

This is a good keyword for standardization because it gives additional information that can be used by the optimizer, but does not alter the semantics of the program if removed. It is supported by both MSVC and GCC.

History

This paper grew out of WG14/N1403, which proposed a complete attribute syntax for C. The consensus of WG14 from the Fall 2009 meeting in Santa Cruz was to entertain future proposals using keywords for individual attributes, rather than a complete attribute framework. This paper is one such proposal, we could theoretically submit proposals for other keywords currently handled as attributes as separate papers.

Changelog

Rationale

The noreturn keyword will enable optimizers to produce faster code, as functions that are guaranteedc not to return can produce less machine code. But a greater benefit will befall static analyzers, for two reasons. First, the noreturn keyword provides additional insight on the part of the programmer...an SA tool can provide a warning if a noreturn function actually might return. Second, an SA tool can make improved inferences about execution paths. Consider the following code:
void f (void) {
  FILE *f;
  f = fopen( file, ...);
  if (f == NULL) {
    handle_error( ... );
  }
  /* work with f */
}
If an SA tool wishes to infer that the /* work with f */ code only executes if f points to an open file, it currently has a difficult task...it has to do sophisticated execution analysis on the handle_error() function to see if it returns. It may not have access to the handle_error() source code, further complicating the process. A developer can virtually eliminate this complexity by indicating that handle_error() should be a noreturn function. Consequently, the SA tool can easily assume that f is a valid file when necessary, and consequently make stronger guarantees about the correctness of the code.

Type System

The _Noreturn keyword is not considered to be 'part of the type system'; thus there is no sense in the type 'pointer to noreturn function'. If a noreturn function is invoked via a pointer to function, it need not be treated as a noreturn function.

This is a saccrifice because the type system could help to identify locations where a noreturn function was useful. For instance, a function that takes a function pointer as an argument could insist that its argument be a noreturn function.

However, there is a simple workaround. It is easy to encapsulate the 'noreturn-ness' of a function pointer:

If a function wishes to treat a function pointer as a pointer to a noreturn function, it can simply call exit() after the function pointer. Consequently, if the pointed-to function actually does return, the system exits immediately.


void (*handle_error(void)); /* this should be a noreturn function */
...
void fn() {
  if (!operation_successful()) {
    handle_error();

    // If we cannot guarantee the pointer does not return,
    // we can stop this flow of control thus:
    exit(1);
  }
  // at this point, the operation was successful
}
Therefore, we consider the sacrifice of not including 'noreturn' in the type system to be a worthwhile trade-off.

The same argument could be extended to claim that _Noreturn itself is not necessary, because a developer could always append an exit() call after any non-returning function. To apply to the fopen() example above, this would yield:

void f (void) {
  FILE *f;
  f = fopen( file, ...);
  if (f == NULL) {
    handle_error( ... );
    exit(1);
  }
  /* work with f */
}
Assuming a static analyzer knows that exit() never returns, it can easily infer that f is a valid function pointer once the if-clause is complete.

This approach has one major disadvantage, however. It forces responsibility for handling 'noreturn' onto any function that calls a noreturn function. This increases the complexity of using non-returning functions, and also increases the likelihood the developer will forget to call exit(), which leaves them in the same state as we are in today.

The noreturn keyword allows developers to invoke noreturn functions without concern for their return status, simplifying their code and reducing the chance for error. Explicitly calling exit() after a noreturn function should only be necessary when the noreturn function is accessed through a function pointer.

Compatibility

GCC

GCC implements their own attribute syntax, which is documented in [N1403]. GCC provides a 'noreturn' attribute with the same semantics proposed in this document. The following code samples are accepted by GCC 4.3 (on Ubuntu Linux 9.04) as well-formed.

void fn(void) __attribute__ ((noreturn));
__attribute__ ((noreturn)) void fn(void);
__attribute__ ((noreturn)) void fn(void) {exit(1);}
Consequently, the following macro definition is sufficient to achieve compatibility with GCC:

#define _Noreturn __attribute__ ((noreturn))
Consequently, the following are accepted as valid by GCC:

_Noreturn void fn(void);
_Noreturn void fn(void) {exit(1);}

Microsoft Visual C++

Microsoft Visual C implements their own attribute syntax, using the __declspec keyword, which is also documented in [N1403]. MS provides a 'noreturn' attribute with the same semantics proposed in this document. The following code samples are accepted by MSVC 2008 Express (on Windows XP SP3) as well-formed.

__declspec( noreturn) void fn(void);
__declspec( noreturn) void fn(void) {exit(1);}
Consequently, the following macro definition is sufficient to achieve compatibility with MSVC:

#define _Noreturn __declspec( noreturn)
Consequently, the following are accepted as valid by MSVC:

_Noreturn void fn(void);
_Noreturn void fn(void) {exit(1);}

C++0x

The new C++ standard includes a syntax for attributes distinct from GCC and MSVC, and includes a noreturn attribute compatible with this paper. As of this writing, the latest draft standard is [N3000], and section 7.6.3 describes the noreturn attribute. It indicates the following would be valid uses of a noreturn attribute:

void fn [[noreturn]] (void);
void fn [[noreturn]] (void) {exit(1);}
The draft standard for C++0x, [N3000], section 8.3, paragraph 5, states:

In a declaration attribute-specifieropt T attribute-specifieropt D where D is an unadorned identifier the type of this identifier is “T”. The first optional attribute-specifier appertains to the entity being declared. The second optional attribute-specifier appertains to the type T, but not to the class or enumeration declared in the decl-specifier-seq, if any. This implies that if the [[noreturn]] attribute precedes the declaration, it still appertains to the function being declared. Therefore, the following declarations have the same meaning:


[[noreturn]] void fn(void);
[[noreturn]] void fn(void) {exit(1);}
Consequently, the following macro definition is sufficient to achieve compatibility with C++0x:

#define _Noreturn [[noreturn]]
Consequently, the following would be accepted as valid for C++0x and C:

_Noreturn void fn(void);
_Noreturn void fn(void) {exit(1);}

Cross-platform

Employing the noreturn property on cross-platform code therefore requires some simple macro substitution:

#if _MSC_VER >= 1310 
/* MS Visual Studio 2003/.NET Framework 1.1 or newer */
#define NORETURN _declspec( noreturn)
#elif __GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ >= 5)
/* GCC 2.5 or newer */
#define NORETURN __attribute__ ((noreturn))
#elif __cplusplus >= ???
// ??? = the value [tbd] specified in C++0x 16.8
#define NORETURN [[noreturn]]
#elif __STDC_VERSION__ >= 201ymmL 
// the value specified in C1x 6.10.8)
#define NORETURN _Noreturn
#else
#define NORETURN ???
// (my project's choice: #error, or nothing)
#endif
Hence you can use the NORETURN macro to specify the noreturn property in portable code:

NORETURN void fn(void);
NORETURN void fn(void) {exit(1);}

Proposed Changes

Add the _Noreturn keyword to section 6.4.1 "Keywords"
keyword: one of
...
  _Thread_local
  _Noreturn

Modify section 6.7.4 as indicated:

  Syntax
        function-specifier:
                inline
                _Noreturn
Modify paragraph 4 to read:
In a hosted environment, no function specifier shall appear in a
declaration of main.
Add a new paragraph after paragraph 4 before paragraph 5:
A function specifier may appear more than once; the behavior is the
same as if it appeared only once.
Modify paragraph 6 (formerly paragraph 5) to read:
A function declared with an inline function specifier is an inline
function. Making a function an inline function suggests that calls to
the function be as fast as possible.120) The extent to which such
suggestions are effective is implementation-defined.121)
This essentially takes the statement about how many times function specifiers may appear out of the paragraph about inline, and makes it into a paragraph by itself, because it can apply to the new keyword.

Add a new paragraph:

The keyword _Noreturn specifies that a function does not return. If a
function f is called where f was previously declared with the noreturn
attribute, and f eventually returns, the behavior is undefined.

[ Example: 
_Noreturn void f () { 
  abort(); /* ok */
}
_Noreturn void g (int i) { /* causes undefined behavior if i <= 0 */
  if (i > 0) 
    abort(); 
}
]
Add a "Recommended practice" section to 6.7.4, which contains the following paragraph:
The implementation should produce a diagnostic message if a function f
is declared with the noreturn keyword, but the implementation cannot
be certain that the function does not return.

Usage

Standard functions that should have the noreturn keyword:

Reference

[C99] ISO/IEC 9899:201x, C Standard
[N1403] Towards support for attributes in C, http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1403.pdf
[N1422] Draft Minutes, October 2009, http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1422.pdf
[N3000] Working Draft Standard for Programming language C++ http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n3000.pdf