Runtime-constraint violations

Somewhat hidden in Annex K, C11 introduces a new term into the C standard, namely runtime-constraint violations. They offer an important change of concept for the functions that are defined in that annex: if such a function is e.g called with invalid parameters, a specific function (called runtime-constraint handler) is called, that could e.g abort the program, or just issue an error message. This is in sharp contrast to the runtime error handling in the rest of the C standard, where the behavior under such errors is mostly undefined (anything may happen then) or sometimes reported to implementation defined behavior (and thus poorly portable and predictable).

Annex K, obscurely coined “Bounds checking interfaces“, introduces some typedef and a series of replacement functions for many C library functions. The function names in this series are usually derived from the name of the function they replace and by adding the suffix _s to the function name, e.g the function qsort gets a “secure” twin interface called qsort_s, as we have seen in an earlier post.

As a first example let us look into the relevant part of the specification of qsort_s:

Runtime-constraints
Neither nmemb nor size shall be greater than RSIZE_MAX. If nmemb is not equal to zero, then neither base nor compar shall be a null pointer.
If there is a runtime-constraint violation, the qsort_s function does not sort the array.

All the constraint violations that are mentioned here, would lead to undefined behavior if they occurred with qsort and not with qsort_s. For the “_s” functions the programmer has a choice of what will happen if any of these events occurs. Two runtime-constraint handlers are always guaranteed to exist, abort_handler_s and ignore_handler_s. They perform the action that you would expect from their names: the first aborts the program whenever a runtime-constraint occurs and the second just ignores all such events.

Runtime-constraint handlers

A program may declare its own runtime-constraint handlers at any time, simply through a call to set_constraint_handler_s. They must just follow the interface specification that is given by the constraint_handler_t function pointer type:

typedef void (*constraint_handler_t)(const char * restrict msg, void * restrict ptr, errno_t error);

That is the constraint handler receives an informative message string, a pointer to an object that may contain information about the context in which the violation occurred (or that can be 0), and the error code that the function would or will return. A simple handler could look as follows:

void simple_constraint_handler(const char * restrict msg, void * restrict ptr, errno_t error) {
  if (stderr) {
    if (ptr) fprintf(stderr, "A runtime-constraint of type %d occurred with object %p\n", error, ptr);
    else fprintf(stderr, "A runtime-constraint of type %d occurred\n", error);
}

A more sophisticated one could like this:

void informative_constraint_handler(const char * restrict msg, void * restrict ptr, errno_t error) {
  if (stderr) {
    size_t len = strerrorlen_s(error);                            // has no constraint violations
    char estr[len + 1];
    constraint_handler_t previous = set_constraint_handler_s(0);  // restore the default handler
    strerror_s(estr, len, error);
    if (ptr) fprintf(stderr, "A runtime-constraint of type %d occurred with object %p: %s\n", error, ptr, estr);
    else fprintf(stderr, "A runtime-constraint of type %d occurred: %s\n", error, estr);
    set_constraint_handler_s(previous);                           // restore the previous handler (that should be this one here)
  }
}

But much more would be possible, e.g to abort on certain error codes and ignore others.

Since an runtime-constraint handler may be just set up to ignore the constraint violation, all functions that return an errno_t should still be checked if the call was on error, and the Annex K functions give still some guarantees on the state of the application after the violation. As we have seen above qsort_s guarantees that the array to be sorted has not been touched under the presence of a runtime-constraint violation, and it returns an appropriate error code to let the application program know what went wrong.

Two new typedefs

This annex also defines two new typedefs. They are errno_t which is alias for int and rsize_t for size_t. Both are meant to mark a semantic difference from the type they alias; errno_t should only be used for error returns and all library functions with that return type are guaranteed to return a valid error number.

rsize_t is supposed to only hold values that correspond to possible sizes of objects on the platform and there is a macro RSIZE_MAX for the maximum value of it. For example there is currently no machine with 64 bit wide size_t that could hold an object that would be as large as SIZE_MAX. So a function seeing a value that is close to SIZE_MAX that is supposed to represent the size of an object can be pretty sure that the value is erroneous and probably the result of a wrap around of some negative value. With this reasoning, on modern machines RSIZE_MAX can be set to (SIZE_MAX/2) and with that “wrap arround” errors on size quantities can easily be detected.

The functions of Annex K that receive arrays as parameters also receive their size as an rsize_t and will check for this type of out-of-bounds error. (Probably the annex got its strange title from this idea.) Also most of them will check if pointer parameters are null pointers.

Other interfaces

Beyond parameter bounds checking, the Annex K interface provides more features.

  • We already have seen qsort_s that not only does this type of bounds checking but also adds a context pointer to the comparison interface, such that it becomes easier to use as general purpose sorting algorithm. In particular, this approach eliminates the need to communication to comparison functions through global variables. Similar rules hold for bsearch_s that augments the bsearch function as previously known.
  • Augmented interfaces from <stdio.h> forbid dereferencing of null pointers or writing with the “%n” format, such that buffer overflow attacks become more difficult.
  • There are string handling functions that complete the interfaces from <string.h>. E.g memcpy_s and strncpy_s additionally check if the two input arrays or strings overlap; strerrorlen_s and strlen_s offer overrun safe functions to check the length of strings.
  • functions for date and time
  • multibyte and wide character utilities

Implementation

There seems some ideological thinking around that Annex K, which is not related to its contents but to its originator. This really is a pity, because Annex K is relatively straight forward in its ideas and interfaces. And really it isn’t too difficult to put into code. P99 already implements a lot of it with simple wrappers around other library functions. The main thing that it doesn’t do (and probably never will) is the restriction on the possible printf (and similar) formats. They would need additions to the sources of these functions and can not be implemented with macros or alike.

P99 just wraps the functions that it amends by macro wrappers. By that it is able to give even more diagnostics of a runtime-constraint violation than is required by Annex K. Often it is even able to trace the function and its calling context in the diagnostic output that the constraint handlers provide.

Other than the wrappers in P99, I have only heard of one implementation of these functions in the context of publicly available open source C libraries, but I’d happily list more, just drop me a line:

  • SLIBC provided by SBA Research, Austria.

6 thoughts on “Runtime-constraint violations”

  1. What guarantees does qsort_s give if the comparison function fails to be consistent, eg. if it’s not transitive or does not give the opposite results if you swap its arguments? I have heared of implementations of qsort that can crash in such a case.

    1. No, there is no explicit constraint for the comparison function. In the defining text there are the same “shall” as for qsort, namely that it has to define a total ordering. So in the jargon of the C standard, passing a function that doesn't fulfill the requirement has undefined behavior. The program could crash, eat your data, whatever.

      Putting such a requirement would not be so easy, because most of the functions have the additional requirement that they don't change the data. And generally, if a runtime constraint is met and the violation should be checked "quickly".

      In the case of sorting,

      • providing a proof for the violation would require to store a violating sequence in part of the array
      • checking that the function fullfills the requirement of transitivity on an array of n items would need Ω(n²) calls to the comparison function.

      1. Understood.

        I didn’t consider that qsort_s would have to verify the invariant of the comparison function. However, it’s possible to define qsort in such a way that even if the comparison function is not consistent it still doesn’t crash but returns and leaves some permutation of the input data in the array.

        1. Yes, but it seems that the standard committee has estimated that the overhead of such a check would be too important, so it doesn’t impose it. Annex K is for checking prerogatives for functions where that is possible, it doesn’t claim more. Since the behavior isn’t defined, then, nothing forbids an implementation to proceed as you have suggested. You can’t simply rely on it.

Comments are closed.