libcontract provides a convenient way to create a contract checking statement (CCS) library for any project written in C.
libcontract has two templates that can be used to generate a CCS library, a basic template and an extended template. The basic version implements CCSs roughly as was proposed for C++20, while the more extensive set of CCSs correspond to the proposal/roadmap Contract Checking in C++: A (long-term) Road Map
The goal of libcontract is to provide CCS libraries that are useful for both library and application developers. Moreover, the CCSs must be easily testable during unit testing.
The usage model is simple: Use create_contract_lib.py
to create a CCS library
from one of the provided templates according to your needs. Then use the
created library in your project in one of three ways:
- Copy the relevant files into your project
- Include the whole library directory in your project (requires cmake)
- Build and install the CCS library and use it like any other installed library
create_contract_lib.py
requires docopt.
The CCS libraries created from the templates have no dependencies themselves,
but to build and run all the unit tests you need:
Compiling the generated CCS libraries requires a C99 compiler, and building the unit tests require a C/C++ compilers that conforms to the minimum requirements imposed by the unit testing frameworks.
Why bother to use CCS macros when the standard library already provides the
assert
macro? There are several benefits to the CCS macros:
- Easier to find the violations.
assert
only prints the failed assertion, but the CCS macros pass the source location to the violation handler. - The CCSs are easy to verify during unit testing with the help of the
CHECK_VIOLATES_CONTRACT
macro. In contrast, it is hard to test asserts in unit tests without overriding theassert
macro, replaceabort
during linking or similar tricks. Alternatively, on can use a unit testing framework that supports catching signals. This is how the CCS macros and the default violation handler is tested. It is, however, very slow, and would not be a good testing strategy in large projects. - The code inside your CCSs will be checked by the compiler even if all CCSs are turned off.
- More versatile:
- several levels of CCSs, each with configurable behaviour
- you can easily use your own violation handler
There are ways to make standard asserts easier to debug, see for instance asserts in llvm, but is still not as verbose as the information provided by the CCS macros.
For most new projects the basic version of the CCSs should suffice. If you want to add CCSs to an existing projects of at least moderate size, then the extended version is probably a better fit because of the review role. The extended version is compatible with the basic version so it is not a problem if you start out with the basic one and then later on realize that you need the functionality provided by the extended version.
Suppose we are working on a project called gsl
. Furthermore, we have decided
that basic CCSs are sufficient for our needs.
First, we generate our CCS library from the basic template:
./create_contract_lib.py --prefix=gsl_ std-template
The easiest way to include the generated CCS library into your project is to copy the header and source files to your project and add them to your build.
cp -r gsl_contract/include/gsl_contract <project include directory>
cp gsl_contract/src/contract.c <project source directory>
The advantage of this way is that the CCS library will be distributed along with your project. If you chose this method, then it might still be a good idea to build the CCS library and run the self tests to verify that everything is working correctly.
# autotools
cd gsl_contract
autoreconf -if
./configure
make
# Optionally run self tests
make check
The configure script will fail if if all the available dependencies are not found. Either install the missing dependencies or disable building of the self tests:
./configure --disable-tests
# cmake
cd gsl_contract
mkdir build && cd build
cmake ..
make
# Optionally run self tests
make test
cmake will fail if if all the available dependencies are not found. Either install the missing dependencies or disable building of the self tests:
cmake .. -DTESTS=OFF
To make the CCS macros available we must include the header
gsl_contract/contract.h
.
#include "gsl_contract/contract.h"
// Very contrived example
void gsl_strcpy(char* dest, cont char* src)
{
GSL_EXPECT(dest != NULL);
GSL_EXPECT(src != NULL);
// Do the real work
}
GSL_EXPECT(cond); // Shorthand for GSL_EXPECT_DEFAULT
GSL_ENSURE(cond); // Shorthand for GSL_ENSURE_DEFAULT
GSL_ASSERT(cond); // Shorthand for GSL_ASSERT_DEFAULT
// Default level CCSs. Checked in default and audit level builds.
GSL_EXPECT_DEFAULT(cond);
GSL_ENSURE_DEFAULT(cond);
GSL_ASSERT_DEFAULT(cond);
// Audit level CCSs. Only checked in audit level builds.
GSL_EXPECT_AUDIT(cond);
GSL_ENSURE_AUDIT(cond);
GSL_ASSERT_AUDIT(cond);
// CCSs that cannot be checked. 'cond' is assumed to be true or not
// checked (ignored) depending on the build mode.
GSL_EXPECT_AXIOM(cond);
GSL_ENSURE_AXIOM(cond);
GSL_ASSERT_AXIOM(cond);
libcontract already supports four unit testing frameworks:
Furthermore, it easy to add support for more unit testing frameworks.
CCSs are tested with the macro CHECK_VIOLATES_CONTRACT
.
// Function to be testes in some .c file.
void foo(int *ptr)
{
GSL_EXPECT(ptr != NULL);
// Do something useful
}
// In UT file. Include the header that corresponds to the UT
// framework in use.
#include "gsl_contract/ut/CppUTest.h"
TEST(test_foo, test_precondition)
{
CHECK_VIOLATES_CONTRACT(foo(NULL));
}
If there is no contract violation, then the test will be marked as failed. You can only test one contract violation at the time with the violation check macro. The first violation will be caught, and the code that would produce the second violation will never be run.
// Wrong. bar will never be called because foo(NULL) violates the precondition.
TEST(test_foo, test_precondition)
{
CHECK_VIOLATES_CONTRACT(foo(NULL); bar(NULL));
}
// The correct way to test many CCSs in the same test.
TEST(test_foo, test_precondition)
{
CHECK_VIOLATES_CONTRACT(foo(NULL));
CHECK_VIOLATES_CONTRACT(bar(NULL));
}
The mechanism that allows CCSs to be tested during unit tests is implemented
with setjmp
and longjmp
. The global variables are thread local, but the
mechanism is not thread safe. Certain multithreaded use cases are safe, while
others are not.
Whenever a contract is violated the global contract violation handler is
called. The default handler prints a contract violation message to stderr
,
and then calls abort()
. If the default violation handler does not fit your
needs, then you can provide your own. See the README's of the CCS templates for
further details.
It is possible to customize the generated CCS macros to a certain degree by
giving appropriate options to create_contract_lib.py
. For instance,
./create_contract_lib.py --prefix=gsl_ --expect-name=pre --ensure-name=post std-templates
will create a CCS library with the macros
GSL_PRE(cond);
GSL_POST(cond);
GSL_ASSERT(cond);
...
See the help of create_contract_lib.py
for all available options.
- Papers
- Talks