Skip to content

Checked C clang user manual

David Tarditi edited this page Oct 30, 2019 · 23 revisions

This page describes how to use the Checked C version of clang. The compiler is quite similar to version 8.0.0 of clang, except that we haven't applied fixes made during the stabilization period of 8.0.0 (after the branch for 8.0.0 was taken).

Getting started

The extension is enabled by default, so just use the Checked C version of clang like you would use clang. If you had a source file myfile.c, you can compile it using:

clang myfile.c

This assumes that the Checked C version of clang is on your path.

Hello, world

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

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

// Make the top-level scope a checked scope.  This allows only the use of checked
// pointer and array types or declarations with bounds-safe interfaces.
#pragma CHECKED_SCOPE ON

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

If you 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 CHECKED_SCOPE ON at the top-level of a C file to require only checked types to be used, you should include the checked header files before your top-level #pragma CHECKED_SCOPE ON pragma (we haven't implemented pushing/popping CHECKED_SCOPE pragmas yet, and each header file ends with #pragma CHECKED_SCOPE 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 an easy-to-read name as an identifier (ptr is a popular identifier name).

Further Tips for Converting Legacy Codebases from C to CheckedC

The checkedC language repo contains a wiki page with tips for converting legacy codebases.

Runtime checking failures

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

#include <stdlib_checked.h>
#include <signal_checked.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 are implied by 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 that the declared bounds are valid. In this case, the compiler is silent.
  2. Prove 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 is 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 review code the warnings to make sure that the bounds declarations 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