Skip to content

Commit 1d0340c

Browse files
tautschnigRemi Delmas
and
Remi Delmas
committed
CONTRACTS: store contracts in dedicated symbols
Contracts can now be placed on declarations or definitions, and result in a fresh symbol being generated. That new symbol has its name prefixed with "contracts::" to place them in a pseudo-namespace, and has "is_property" set (which was an otherwise unused symbolt flag). Co-authored-by: Remi Delmas <[email protected]>
1 parent b22ded7 commit 1d0340c

16 files changed

+455
-41
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
int foo(int *arr, int size);
2+
3+
int foo(int *arr, int size)
4+
// clang-format off
5+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
6+
__CPROVER_assigns(
7+
arr[0], arr[size-1];
8+
size >= 10: arr[5];
9+
)
10+
__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0)
11+
__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value)
12+
// clang-format on
13+
;
14+
15+
int foo(int *arr, int size)
16+
{
17+
arr[0] = 0;
18+
arr[size - 1] = 0;
19+
return size < 10 ? 0 : arr[5];
20+
}
21+
22+
int main()
23+
{
24+
int arr[10];
25+
int retval = foo(arr, 10);
26+
__CPROVER_assert(retval == arr[5], "should succeed");
27+
return 0;
28+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
int foo(int *arr, int size)
2+
{
3+
arr[0] = 0;
4+
arr[size - 1] = 0;
5+
return size < 10 ? 0 : arr[5];
6+
}
7+
8+
int foo(int *arr, int size)
9+
// clang-format off
10+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
11+
__CPROVER_assigns(
12+
arr[0], arr[size-1];
13+
size >= 10: arr[5];
14+
)
15+
__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0)
16+
__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value)
17+
// clang-format on
18+
;
19+
20+
int main()
21+
{
22+
int arr[10];
23+
int retval = foo(arr, 10);
24+
__CPROVER_assert(retval == arr[5], "should succeed");
25+
return 0;
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
int foo(int *arr, int size)
2+
// clang-format off
3+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
4+
__CPROVER_assigns(
5+
arr[0], arr[size-1];
6+
size >= 10: arr[5];
7+
)
8+
__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0)
9+
__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value)
10+
// clang-format on
11+
;
12+
13+
int foo(int *arr, int size)
14+
{
15+
arr[0] = 0;
16+
arr[size - 1] = 0;
17+
return size < 10 ? 0 : arr[5];
18+
}
19+
20+
int main()
21+
{
22+
int arr[10];
23+
int retval = foo(arr, 10);
24+
__CPROVER_assert(retval == arr[5], "should succeed");
25+
return 0;
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
int foo(int *arr, int size)
2+
// clang-format off
3+
__CPROVER_requires(size > 0 && __CPROVER_is_fresh(arr, size))
4+
__CPROVER_assigns(
5+
arr[0], arr[size-1];
6+
size >= 10: arr[5];
7+
)
8+
__CPROVER_ensures(arr[0] == 0 && arr[size-1] == 0)
9+
__CPROVER_ensures(size >= 10 ==> arr[5] == __CPROVER_return_value)
10+
// clang-format on
11+
;
12+
13+
int main()
14+
{
15+
int arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
16+
int retval = foo(arr, 10);
17+
assert(arr[0] == 0);
18+
assert(arr[1] == 9);
19+
assert(arr[2] == 8);
20+
assert(arr[3] == 7);
21+
assert(arr[4] == 6);
22+
assert(arr[5] == retval);
23+
assert(arr[6] == 4);
24+
assert(arr[7] == 3);
25+
assert(arr[8] == 2);
26+
assert(arr[9] == 0);
27+
return 0;
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main-contract-after-declaration.c
3+
--replace-call-with-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that we can have a function declaration with a contract after
10+
having seen an earlier declaration of that same function.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main-contract-after-definition.c
3+
--replace-call-with-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that we can have a function declaration with a contract after
10+
having seen that function's definition.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main-definition-after-contract.c
3+
--replace-call-with-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that we can have a function declaration with a contract and
10+
without body, then the function definition, and successfully replace a call to
11+
the function by the contract.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main-no-definition.c
3+
--replace-call-with-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
This test checks that we can have a function declaration with a contract and
10+
without body and replace a call to the function by the contract.

src/ansi-c/c_typecheck_base.cpp

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ void c_typecheck_baset::typecheck_new_symbol(symbolt &symbol)
153153
{
154154
typecheck_function_body(symbol);
155155
}
156-
else
156+
else if(
157+
symbol.is_macro ||
158+
!to_code_with_contract_type(symbol.type).has_contract())
157159
{
158160
// we don't need the identifiers
159161
for(auto &parameter : to_code_type(symbol.type).parameters())
@@ -335,7 +337,15 @@ void c_typecheck_baset::typecheck_redefinition_non_type(
335337
if(old_ct.has_ellipsis() && !new_ct.has_ellipsis())
336338
old_ct=new_ct;
337339
else if(!old_ct.has_ellipsis() && new_ct.has_ellipsis())
340+
{
341+
if(to_code_with_contract_type(new_ct).has_contract())
342+
{
343+
error().source_location = new_symbol.location;
344+
error() << "code contract on incomplete function re-declaration" << eom;
345+
throw 0;
346+
}
338347
new_ct=old_ct;
348+
}
339349

340350
if(inlined)
341351
{
@@ -408,6 +418,22 @@ void c_typecheck_baset::typecheck_redefinition_non_type(
408418
// overwrite type (because of parameter names)
409419
old_symbol.type=new_symbol.type;
410420
}
421+
else if(to_code_with_contract_type(new_ct).has_contract())
422+
{
423+
// overwrite type to add contract, but keep the old parameter identifiers
424+
// (if any)
425+
auto new_parameters_it = new_ct.parameters().begin();
426+
for(auto &p : old_ct.parameters())
427+
{
428+
if(new_parameters_it != new_ct.parameters().end())
429+
{
430+
new_parameters_it->set_identifier(p.get_identifier());
431+
++new_parameters_it;
432+
}
433+
}
434+
435+
old_symbol.type = new_symbol.type;
436+
}
411437

412438
return;
413439
}
@@ -710,12 +736,44 @@ void c_typecheck_baset::typecheck_declaration(
710736

711737
// check the contract, if any
712738
symbolt &new_symbol = symbol_table.get_writeable_ref(identifier);
713-
if(new_symbol.type.id() == ID_code)
739+
if(
740+
new_symbol.type.id() == ID_code &&
741+
to_code_with_contract_type(new_symbol.type).has_contract())
714742
{
715-
// We typecheck this after the
716-
// function body done above, so as to have parameter symbols
717-
// available
718-
auto &code_type = to_code_with_contract_type(new_symbol.type);
743+
code_with_contract_typet code_type =
744+
to_code_with_contract_type(new_symbol.type);
745+
746+
// ensure parameter declarations are in the symbol table
747+
std::vector<irep_idt> temporary_parameter_symbols;
748+
for(auto &p : code_type.parameters())
749+
{
750+
if(p.get_base_name().empty())
751+
continue;
752+
753+
// produce identifier
754+
irep_idt base_name = p.get_base_name();
755+
irep_idt parameter_identifier =
756+
id2string(identifier) + "::" + id2string(base_name);
757+
758+
p.set_identifier(parameter_identifier);
759+
760+
if(
761+
symbol_table.symbols.find(parameter_identifier) ==
762+
symbol_table.symbols.end())
763+
{
764+
parameter_symbolt p_symbol;
765+
766+
p_symbol.type = p.type();
767+
p_symbol.name = parameter_identifier;
768+
p_symbol.base_name = base_name;
769+
p_symbol.location = p.source_location();
770+
771+
symbolt *new_p_symbol;
772+
move_symbol(p_symbol, new_p_symbol);
773+
774+
temporary_parameter_symbols.push_back(parameter_identifier);
775+
}
776+
}
719777

720778
for(auto &expr : code_type.requires_contract())
721779
{
@@ -745,21 +803,52 @@ void c_typecheck_baset::typecheck_declaration(
745803
CPROVER_PREFIX "loop_entry is not allowed in postconditions.");
746804
}
747805

748-
if(!as_const(code_type).ensures().empty())
806+
for(auto &ensures : code_type.ensures())
749807
{
750-
for(auto &ensures : code_type.ensures())
751-
{
752-
typecheck_expr(ensures);
753-
implicit_typecast_bool(ensures);
754-
disallow_subexpr_by_id(
755-
ensures,
756-
ID_loop_entry,
757-
CPROVER_PREFIX "loop_entry is not allowed in postconditions.");
758-
}
808+
typecheck_expr(ensures);
809+
implicit_typecast_bool(ensures);
810+
disallow_subexpr_by_id(
811+
ensures,
812+
ID_loop_entry,
813+
CPROVER_PREFIX "loop_entry is not allowed in postconditions.");
759814
}
760815

761816
if(return_type.id() != ID_empty)
762817
parameter_map.erase(CPROVER_PREFIX "return_value");
818+
819+
// create a contract symbol
820+
symbolt contract;
821+
contract.name = "contract::" + id2string(new_symbol.name);
822+
contract.base_name = new_symbol.name;
823+
contract.pretty_name = new_symbol.name;
824+
contract.is_property = true;
825+
contract.type = code_type;
826+
contract.mode = new_symbol.mode;
827+
contract.module = module;
828+
contract.location = new_symbol.location;
829+
830+
auto entry = symbol_table.insert(std::move(contract));
831+
if(!entry.second)
832+
{
833+
error().source_location = new_symbol.location;
834+
error() << "contract '" << new_symbol.display_name()
835+
<< "' already set at " << entry.first.location.as_string()
836+
<< eom;
837+
throw 0;
838+
}
839+
840+
// Remove the contract from the original symbol as we have created a
841+
// dedicated contract symbol.
842+
new_symbol.type.remove(ID_C_spec_assigns);
843+
new_symbol.type.remove(ID_C_spec_ensures);
844+
new_symbol.type.remove(ID_C_spec_ensures_contract);
845+
new_symbol.type.remove(ID_C_spec_requires);
846+
new_symbol.type.remove(ID_C_spec_requires_contract);
847+
848+
// remove parameter declarations if those were only added for type
849+
// checking the contract
850+
for(const auto &parameter_id : temporary_parameter_symbols)
851+
symbol_table.remove(parameter_id);
763852
}
764853
}
765854
}

0 commit comments

Comments
 (0)