Skip to content

2021-09-14 Release of the Checked C Clang compiler

Compare
Choose a tag to compare

Summary

This is a release build of the Checked C Clang compiler for Windows 32-bit and 64-bit platforms. It is for use by developers who want to experiment with the Checked C language extensions to guarantee the type safety of their C code. It is based on the Checked C specification version 0.9, which is also one of the released artifacts.

Release Date and Tag

14th September 2021 CheckedC-Clang-12.0.1-rel3

Installation Notes

Clang expects an existing C/C++ compiler before running the installer. If installing on a fresh machine, first install the C/C++ compiler. We recommend Visual Studio 2019, which has a free Community version available. Use Visual Studio 2019's installer to ensure a C/C++ compiler and runtime are present before installing Checked C Clang.

  • The binaries are installers for 32-bit and 64-bit Windows versions of the compiler
  • The compiler will be installed in a separate directory from your existing Clang install. If you are also using the production version of Clang, do not add the Checked C version to your path.

Using the Compiler

See the Checked C Clang users manual for directions on how to use the compiler.

There are now two ways to use the Checked C Clang compiler in Visual Studio. The LLVM project has created a Visual Studio extension. You can use a property page for your project to directly choose the Checked C Clang compiler binary.

Visual Studio 2019 also now directly supports Clang/LLVM. This blog post describes the support for CMake projects. This blog post describes support for MSBuild projects. If you installed the Checked C Clang compiler and added it to your PATH variable, Visual Studio should directly pick it up. Otherwise, you can follow the directions to use a custom installation of Clang.

Change Notes

Core features

  • PR #1183: Get rvalue bounds for the value of lvalue expressions from the CheckingState. The observed bounds of an lvalue expression e are the bounds (if any) for e that are currently stored in the checking state.
  • PR #1181: Member expression bounds checking in multiple assignments. The behavior for checking member expression bounds in full expressions that contain multiple assignments is now consistent with the behavior for checking the bounds of variables.
  • PR #1176: Bounds checking for pointer dereferences and array subscripts. The static checking for bounds declarations has been extended to check the bounds of pointers of type _Ptr<T> or _Nt_array_ptr<T> that are stored in pointer dereferences or array subscripts.
  • PR #1169: Bounds checking bounds-safe interfaces in unchecked scopes. In an unchecked scope, if p is an unchecked pointer with a bounds-safe interface, and p is never assigned a checked pointer value, then the declared bounds of p are not validated.
  • PR #1166: Update bounds checking notes. Only one note is emitted per statement per lvalue expression for an expression with unknown bounds being assigned to an lvalue expression.
  • PR #1149: Handle complex conditionals in bounds widening. Bounds widening is now possible in presence of complex conditionals like: if (*p != 0)", "if ((c = *p) == 'a'), etc.
  • PRs #1174, #1182: Support variadic function calls in checked scope. The compiler now supports calling variadic functions in checked scope. These are functions like printf, scanf, etc that take a format string and have a variable number of arguments. The arguments and format specifiers for these functions are checked. The checking of variadic functions in checked scope follows these rules:
    • All warnings issued by the -Wformat family of flags are errors in checked scope.
    • No bounds checking of arguments to variadic functions like printf/scanf, etc is done.
    • For printf-like functions:
      • %s is allowed only with argument type _Nt_array_ptr or _Nt_checked.
      • %p is allowed with any argument type.
      • %n is disallowed.
      • For all other format specifiers, only scalar argument types are allowed.
    • For scanf-like functions:
      • %s is disallowed.
      • All width modifiers to format specifiers are disallowed.
      • %p is disallowed
      • %n is disallowed
      • For all other format specifiers, only _Ptr argument types are allowed.

Checking return bounds

The static checking for bounds declarations has been extended to check the declared bounds for a function. The static checking validates that:

  1. When an expression e is returned from a function f, the inferred bounds of e imply the declared bounds of f.
  2. Parameters used in the declared bounds of f are unmodified.

For example, consider the following sample program involving declared function bounds:

_Array_ptr<int> f1(_Array_ptr<int> p : count(2),
                   _Array_ptr<int> q : count(1),
                   int test) : count(2) {
  if (test)
    // No errors or warnings.
    // bounds(p, p + 2) implies bounds(_Return_value, _Return_value + 2).
    return p;

  // error: return value bounds do not imply declared return bounds for 'f1'
  return q;
}

_Array_ptr<int> f2(int i) : count(i) {
  // error: modified expression 'i' used in the declared return bounds for 'f2'
  i++;
}

Support for bundled blocks

The compiler now supports the grouping of expression statements and declarations into bundled blocks. Bounds declarations are checked only at the end of bundled blocks. For more details, please refer to Sections 3.5 and 4.7 of the Checked C specification. Given below is an example that illustrates the use of bundled blocks.

#include <string.h>

struct Node {
  _Nt_array_ptr<char> name;
  unsigned int age;
};

struct Group {
  _Array_ptr<struct Node> list : count (n);
  unsigned int n;
};

// get the first name that starts with the letters 'Ar'
_Nt_array_ptr<char> get_name(_Array_ptr<struct Group> groups : count(gcnt), unsigned int gcnt)
_Checked {
  unsigned int n = 0;
  _Array_ptr<struct Node> group : count(n) = 0;

  for (int i = 0; i < gcnt; i++) {
    _Bundled {
      group = groups[i].list;
      n = groups[i].n;
    }
    for (int j = 0; j < n; j++) {
      _Nt_array_ptr<char> name = group[j].name;
      unsigned int m = strlen(name) _Where name : count(m);
      if (m >= 2 && name[0] == 'A' && name[1] == 'r')
        return name;
    }
  }
  return "";
}

Bug fixes

  • Issue #1148: Inconsistent behavior with str and &str[0] (fixed by PR #1163).
  • Issue #1184: Equality should not be recorded between expressions such as x and x + 1 (fixed by PR #1162).
  • Issue #1153: Assertion fail during bounds widening (fixed by PR #1154).

Extension Features Implemented

See the implementation roadmap and status page. Some runtime checks and some static checking is not implemented yet.