Contract syntax¶
Every construct is written qualified by the namespace cppverify:
cppverify::pre(x > 0), cppverify::check(e), cppverify::forall(k, ...).
A namespace alias shortens it. The examples of this reference write the
constructs through the alias cv and use the names of <cppverify.h>
(valid, seq, set, multiset, map) unqualified, as a file
does after namespace cv = cppverify; and using namespace cppverify;:
namespace cv = cppverify;
using namespace cppverify;
int abs_value(int x)
cv::pre(x > -2147483647 - 1)
cv::post(cv::result >= 0)
{
return x < 0 ? -x : x;
}
The parser recognizes a construct when the qualifier names the global namespace
cppverify, writtencppverify::,::cppverify::, or through any alias. A word without the qualifier is ordinary C++:pre,result,ghost, orcheckmay name your own variables and functions, and no header of the standard library is affected.using namespace cppverify;does not make the bare words constructs; writing one bare where only the construct fits is an error that shows the qualified spelling.With
-fverify-contracts, the namespace and<cppverify.h>are available in every C++ file without an#include. Without the flag the constructs are not recognized.
C++26 contracts¶
This compiler does not implement C++26 contracts. With -fverify-contracts,
a bare pre(...) or post(...) after a function declarator is an error
whose fix-it writes the cpp-verify construct, and so is a bare
contract_assert:
int positive(int x)
pre(x > 0)
{
contract_assert(x > 0);
return x;
}
bare.cpp:2:3: error: C++26 contract specifiers are not supported; a cpp-verify precondition is written 'cppverify::pre'
bare.cpp:4:3: error: 'contract_assert' is a cpp-verify construct only when qualified: write 'cppverify::check'
Without the flag, Clang reads neither as a contract: pre(x > 0) there is
error: expected function body after function declarator.
One condition per clause¶
Each clause takes exactly one expression. To state two conditions, join them
with && or write two clauses, which mean the same: every cv::pre must
hold, and so must every cv::post.
int clamp(int x)
cv::post(cv::result >= 0 && cv::result <= 10)
{
return x < 0 ? 0 : x > 10 ? 10 : x;
}
int clamp_again(int x)
cv::post(cv::result >= 0)
cv::post(cv::result <= 10)
{
return x < 0 ? 0 : x > 10 ? 10 : x;
}
Anything written after the expression, inside the parentheses, is an error.
A missing && is the usual cause:
int clamp(int x)
cv::post(cv::result >= 0 cv::result <= 10)
{
return x < 0 ? 0 : x > 10 ? 10 : x;
}
clauses.cpp:2:28: error: expected ')' after cppverify::post expression
2 | cv::post(cv::result >= 0 cv::result <= 10)
| ^
clauses.cpp:2:11: note: to match this '('
2 | cv::post(cv::result >= 0 cv::result <= 10)
| ^
Before 0.2, the parser kept the first condition and dropped the rest without
a word, so a clause with a missing && checked less than it said: the
example above verified cv::result >= 0 alone.
Full table:
Syntax |
Placement |
Meaning |
|---|---|---|
|
After |
Precondition |
|
After |
Postcondition |
|
After |
Writable cells, ranges |
|
After |
Opt a pointer/reference address pair into aliasing |
|
Spec functions |
Soft precondition |
|
Spec functions |
Cells |
|
Spec functions |
Domain on which the body defines the spec |
|
Spec functions returning |
The least predicate the body defines (no |
|
Spec function definitions |
Proof steps for that clause’s check |
|
After |
A case of the contract |
|
After the behaviors |
Some / at most one behavior applies |
|
After loop |
Loop invariant |
|
Loop / function |
Termination measure (required on executable loops) |
|
Executable loop / function |
Allow divergence; proofs are |
|
In struct/class |
Field invariant |
|
Statement |
Proof-only block |
|
Statement |
Function-scoped ghost variable |
|
Statement |
Proof obligation |
|
Statement |
Proof obligation with a local proof |
|
Statement |
Chain of proved steps |
|
Statement anywhere in a function body, ghost blocks included |
Unfold recursive spec |
|
Statement anywhere in a function body, ghost blocks included |
Make spec |
|
Expression |
Bounded |
|
Expression |
Bounded |
|
Expression |
|
|
In a quantifier body |
Instantiation pattern |
|
Expression |
Some integer satisfying |
|
In |
Pre-state value |
|
In |
Return value |
|
Decl |
Spec function |
|
Decl |
Proof function |
|
|
Spec collections |
|
A top-level condition of |
|
Macros named like a construct¶
The preprocessor runs before the parser sees a construct, so a function-like
macro named like a construct expands inside the qualified name. A header that
defines check(condition) breaks every cppverify::check after it:
#define check(condition) ((void)0)
int positive(int x)
cv::pre(x > 0)
{
cv::check(x > 0);
return x;
}
macros.cpp:6:7: error: expected unqualified-id
#undef check after the header’s #include restores the construct:
#define check(condition) ((void)0)
#undef check
int positive(int x)
cv::pre(x > 0)
{
cv::check(x > 0);
return x;
}
Verified: positive [backend=z3]
An object-like macro has the same effect on a construct written without
parentheses, such as cv::result.
On Apple platforms (macOS, iOS, and the others), <AssertMacros.h>, which
some of Apple’s framework headers include, defines check(assertion) unless
__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES is 0. With
-fverify-contracts on an Apple target, the compiler therefore defines it
to 0 before any header, unless the command line defines it, and
cppverify::check works next to that header. The header then defines only
its __Check, __Require, and __Verify macros, the names Apple
recommends. A file that uses the old names (check, require,
verify, and their variants) can switch to the __ names, or keep the
old ones with -D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=1 on
the command line; cppverify::check then works only after an #undef
check. Defining it to 1 with #define in the source also works, with
a -Wmacro-redefined warning.