C++ bounded::integer library

The Code

BitBucket repository.

C++ Now 2014 Presentation

C++ Now 2014 video

The problems with built-in types

C++ integer types are fraught with peril. It is undefined behavior for a signed integer to overflow, but unsigned types wrap around. All types engage in unsafe implicit conversions, and in general it can be difficult to write portable code because of these conversions.

The majority of vulnerabilities in both C and C++ are fundamentally integer overflow errors. The most common example is a "buffer overflow" or "buffer overrun". It looks something like this:

int array [5];
int result = array[5];

The programmer attempted to access the element at index 5, but array can only be indexed between 0 and 4 (5 elements total). This is actually an integer overflow error! The integer overflow occurs because the correct "type" to index into array is an integer between 0 and 4, but built-in arrays are actually indexed with std::size_t, which is between 0 and a very large number (typically 2 ** 64 - 1 on modern systems). The type we are using to index the array is too wide which leaves the compiler unable to verify that our code is correct.

bounded::integer attempts to solve all of these problems and more. It provides more safety than Ada range types and more efficiency than the C / C++ built-in integer types. Before going into how it achieves this, let's first see what code written using bounded::integer looks like.

bounded::integer code

First, some basic usage:

bounded::integer<0, 10> x(5);
bounded::integer<5, 9> y(6);
auto z = x + y;
// decltype(z) == bounded::integer<5, 19>
std::cout << z << '\n';
// prints 11

This code is just saying that x is always going to be between 0 and 10 (inclusive), and y is always going to be between 5 and 9. The result of adding them is between 5 and 19.

Here we see the most important idea behind using the library, and that is type deduction. The goal is to specify only the bounds of your inputs, and have the compiler deduce the bounds of all intermediate and result types. That was pretty boring though, what about a more complex example?

bounded::integer<-1, 15> make_value();
bounded::array<std::string, 8> make_strings();

template<typename Integer>
constexpr auto frobnicate(Integer const integer) {
	return bounded::min(integer, 6_bi) + 1_bi;
}

auto g() {
	auto const my_strings = make_strings();
	auto const safe_index = frobnicate(make_value());
	auto const unsafe_index = make_value();
	return my_strings.at(unsafe_index) + my_strings.at(safe_index);
}

There is a lot going on here, so let's take this one step at a time.

First we have the function frobnicate. It accepts any integer type and performs a few simple transformations on its value. It starts by getting the lesser of the value you pass in and 6. That _bi is just a user-defined literal that results in a type that can only hold the value of 6 (it has both a minimum and a maximum value of 6). The type of the result of the call to bounded::min has a maximum value that is at most 6. Then we add 1_bi to the result, which shifts the minimum and maximum up by 1.

In the assignment to safe_index, therefore, we are passing in an integer between -1 and 15 (the type of make_value()) and getting out an integer between 0 and 7. The bounded::min call restricts the range to be -1 to 6, and then we add 1 to that.

The net result of this is that the call my_strings.at(unsafe_index) has a runtime check, because the index might be out of range. However, the call to my_strings.at(safe_index) has no run time check because the type is definitely in range. If we had instead used my_strings[unsafe_index], the code would not have compiled.

Robust Code in C++

This example shows off the strength of the bounded::integer library. Many people choose C++ for performance. If we offer a solution that has run-time overhead, those people will not use the solution and instead stick with the unsafe old code. By making extensive use of compile-time code generation, we are able to give safe calls no overhead, but prevent undefined behavior with unsafe calls.

Reference

For a full reference of what is available in the bounded::integer library, see the bounded::integer reference.