Skip to content

Checked C clang user manual

David Tarditi edited this page Apr 12, 2018 · 28 revisions

This page describes how to use the Checked C version of clang.

The Checked C version of clang is not ready for production use. The implementation of the Checked C extension is fairly incomplete, the compiler has only been tested on x86 and x64 Windows and x64 Linux, and testing has not been extensive. For example, we have not compiled and tested large production C and C++ code bases using the compiler. The compiler is quite similar to version 6.0.0 of clang, except that we haven't applied fixes made during the stabilization period of 6.0.0 (after the branch for 6.0.0 was taken).

Getting started

The extension is enabled by default, so just use Checked C version of clang as you would use clang.

Hello, world

Here is the source code for "hello, world" in Checked C:

#include <stdio_checked.h>
#include <stdchecked.h>

#pragma BOUNDS_CHECKED ON

int main(int argc, nt_array_ptr<char> argv checked[] : count(argc)) {
  puts("hello, world");
  return 0;
}

If have a copy of the Checked C repo and clang is on your path, just go to the sample directory and run clang hello-world.c to compile the program.

Header files

There are Checked C versions of the C standard library headers. These redeclare existing C functions with bounds-safe interfaces that describe their expected behavior. These are already on your clang system include path, so you don't need to do anything to add it to your include path.

If you are using #pragma BOUNDS_CHECKED on at the top-level of a C file to force the use of only checked types, you should include these header files before your top-level #pragma BOUNDS_CHECKED ON pragma (we haven't implemented pushing/popping BOUNDS_CHECK pragmas yet, and each header file ends with #pragma BOUNDS_CHECK OFF).

If you want to use easy-to-read names for your Checked C types and keywords (such as array_ptr instead of _Array_ptr) use #include <stdchecked.h>. This include file is also already on your clang system include path. You can only use this header file if your code doesn't use a easy-to-read name as an identifier (ptr is a popular identifier name).

Runtime checking failures

If bounds checking or other Checked C runtime checks fail, your program will execute an illegal instruction. You can C signal handling to catch the illegal instruction signal and have your program exit gracefully. For example:

#include <signal.h>
void handle_error(int err) {
  _Exit(1);
}

int main(int argc, _Array_ptr<_Nt_array_ptr<char>> argv : count(argc)) {
  // Set up the handler for a failing runtime check.   A SIGILL is raised when a Checked C
  // runtime check fails.
  signal(SIGILL, handle_error);
  ...

New compiler flags

The flag -fcheckedc-extension enables the Checked C language extension. It is on by default. If you need to disable the Checked C language extension, you can use the flag -fno-checkedc-extension.

The flag -fdump-inferred-bounds is meant for compiler developer use. It causes the compiler to dump the bounds that the compiler is inferring for program expressions. The dumped bounds are expressed using the internal clang IR. This is useful for seeing exactly what bounds the compiler really thinks an expression has, which makes it helpful for debugging problems related to bounds inference or incorrect bounds.

Warnings for checking of bounds declarations

The compiler always checks whether declared bounds are valid. This is important because bounds checking is not meaningful if the declared bounds are wrong. Declared bounds are valid if they follow from existing declared bounds and other information in the program. For example, given

void f(array_ptr<int> p : count(len), int len) {
  array_ptr<int> r : count(len) = p;
  ...
}

The declared bounds for r of count(len) are valid because p has inferred bounds of count(len) The bounds of the left-hand side of the initializer (count(len)) imply that the bounds for the right-hand side are valid.

The compiler does one of three things when it checks a bounds declaration:

  1. Prove to itself that the declared bounds are valid. In this case, the compiler is silent.
  2. Prove to itself that the declared bounds are invalid. In this case, the compiler issues an error message. The error message cannot be suppressed.
  3. Be unable to prove that the declared bounds are valid or invalid. In this case, the compiler issues a warning. The static checking is not very sophisticated at this point. It does not understand simple dataflow facts across statements, for example, so the compiler could issue many warnings for bounds declarations, depending on your source code.

Our recommended approach where the compiler issues a warning to use a dynamic_bounds_cast expression to check that the bounds are valid at run time. For example, given

array_ptr<int> p : count(len) = ...
array_ptr<int> r : count(len - 1) = p + 1;

the compiler will issue a warning for the assignment to r. This can be avoided with:

array_ptr<int> r : count(len - 1) = 
   dynamic_bounds_cast<array_ptr<int>>(p + 1, count(len - 1);

However, you may not want to modify your code until the static checking gets smarter. If you do that, we strongly recommend you carefully code review the warnings, to make sure that the bounds declrations are correct. You can use the following flags to suppress warnings about checking of bounds declarations.

  • -Wno-check-bounds-decls turns off all warnings.
  • -Wno-check-bounds-decls-unchecked-scope turns off warnings for unchecked scopes.
  • -Wno-check-bounds-decls-checked-scope turns off warnings for checked scopes (not recommended).

Checking memory accesses

The compiler uses the static analysis for checking of bounds declarations to check memory accesses also. The compiler will warn when it encounters a memory access that is definitely out-of-bounds. The program will crash with a bounds checking failure if it ever reaches that memory access. You can disable this warning with

  • -Wno-check-memory-accesses

Checking uses of dynamic_check

Checked C introduces a dynamic_check(e)expression that always checks that e is true at runtime. If it fails, the program exits with a runtime failure. Unlike asserts, this expression is never removed. The compiler will warn if it proves that e will always be false. You can disable this warning with

  • -Wno-checkedc