Skip to content

Commit 39690e1

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 8a41865 commit 39690e1

14 files changed

+382
-40
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
return 0;
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
return 0;
25+
}
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-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: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ void c_typecheck_baset::typecheck_new_symbol(symbolt &symbol)
153153
{
154154
typecheck_function_body(symbol);
155155
}
156+
else if(
157+
!symbol.is_macro &&
158+
to_code_with_contract_type(symbol.type).has_contract())
159+
{
160+
add_parameters_to_symbol_table(symbol);
161+
}
156162
else
157163
{
158164
// we don't need the identifiers
@@ -335,7 +341,15 @@ void c_typecheck_baset::typecheck_redefinition_non_type(
335341
if(old_ct.has_ellipsis() && !new_ct.has_ellipsis())
336342
old_ct=new_ct;
337343
else if(!old_ct.has_ellipsis() && new_ct.has_ellipsis())
344+
{
345+
if(to_code_with_contract_type(new_ct).has_contract())
346+
{
347+
error().source_location = new_symbol.location;
348+
error() << "code contract on incomplete function re-declaration" << eom;
349+
throw 0;
350+
}
338351
new_ct=old_ct;
352+
}
339353

340354
if(inlined)
341355
{
@@ -710,12 +724,14 @@ void c_typecheck_baset::typecheck_declaration(
710724

711725
// check the contract, if any
712726
symbolt &new_symbol = symbol_table.get_writeable_ref(identifier);
713-
if(new_symbol.type.id() == ID_code)
727+
if(
728+
new_symbol.type.id() == ID_code &&
729+
to_code_with_contract_type(new_symbol.type).has_contract())
714730
{
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);
731+
// We typecheck this after the function body done above, so as to have
732+
// parameter symbols available.
733+
code_with_contract_typet code_type =
734+
to_code_with_contract_type(new_symbol.type);
719735

720736
for(auto &expr : code_type.requires_contract())
721737
{
@@ -745,21 +761,62 @@ void c_typecheck_baset::typecheck_declaration(
745761
CPROVER_PREFIX "loop_entry is not allowed in postconditions.");
746762
}
747763

748-
if(!as_const(code_type).ensures().empty())
764+
for(auto &ensures : code_type.ensures())
749765
{
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-
}
766+
typecheck_expr(ensures);
767+
implicit_typecast_bool(ensures);
768+
disallow_subexpr_by_id(
769+
ensures,
770+
ID_loop_entry,
771+
CPROVER_PREFIX "loop_entry is not allowed in postconditions.");
759772
}
760773

761774
if(return_type.id() != ID_empty)
762775
parameter_map.erase(CPROVER_PREFIX "return_value");
776+
777+
// create a contract symbol
778+
symbolt contract;
779+
contract.name = "contract::" + id2string(new_symbol.name);
780+
contract.base_name = new_symbol.name;
781+
contract.pretty_name = new_symbol.name;
782+
contract.is_property = true;
783+
contract.type = code_type;
784+
contract.mode = new_symbol.mode;
785+
contract.module = module;
786+
contract.location = new_symbol.location;
787+
788+
auto entry = symbol_table.insert(std::move(contract));
789+
if(!entry.second)
790+
{
791+
error().source_location = new_symbol.location;
792+
error() << "contract '" << new_symbol.display_name()
793+
<< "' already set at " << entry.first.location.as_string()
794+
<< eom;
795+
throw 0;
796+
}
797+
798+
// Remove the contract from the original symbol as we have created a
799+
// dedicated contract symbol.
800+
new_symbol.type.remove(ID_C_spec_assigns);
801+
new_symbol.type.remove(ID_C_spec_ensures);
802+
new_symbol.type.remove(ID_C_spec_ensures_contract);
803+
new_symbol.type.remove(ID_C_spec_requires);
804+
new_symbol.type.remove(ID_C_spec_requires_contract);
805+
806+
// remove parameter declarations if those were only added for type
807+
// checking the contract
808+
if(new_symbol.value.is_nil())
809+
{
810+
for(const auto &parameter : code_type.parameters())
811+
{
812+
const irep_idt &identifier = parameter.get_identifier();
813+
814+
symbol_tablet::symbolst::const_iterator p_s_it =
815+
symbol_table.symbols.find(identifier);
816+
if(p_s_it != symbol_table.symbols.end())
817+
symbol_table.erase(p_s_it);
818+
}
819+
}
763820
}
764821
}
765822
}

src/goto-instrument/contracts/contracts.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,29 @@ code_contractst::create_ensures_instruction(
733733
return std::make_pair(std::move(ensures_program), std::move(history));
734734
}
735735

736+
static const code_with_contract_typet &
737+
get_contract(const irep_idt &function, const namespacet &ns)
738+
{
739+
const std::string function_str = id2string(function);
740+
const symbolt &function_symbol = ns.lookup(function);
741+
742+
const symbolt *contract_sym;
743+
if(ns.lookup("contract::" + function_str, contract_sym))
744+
{
745+
// no contract provided in the source or just an empty assigns clause
746+
return to_code_with_contract_type(function_symbol.type);
747+
}
748+
749+
const auto &type = to_code_with_contract_type(contract_sym->type);
750+
if(type != function_symbol.type)
751+
{
752+
throw invalid_input_exceptiont(
753+
"Contract of '" + function_str + "' has different signature.");
754+
}
755+
756+
return type;
757+
}
758+
736759
void code_contractst::apply_function_contract(
737760
const irep_idt &function,
738761
const source_locationt &location,
@@ -745,14 +768,13 @@ void code_contractst::apply_function_contract(
745768
// function pointers.
746769
PRECONDITION(const_target->call_function().id() == ID_symbol);
747770

748-
// Retrieve the function type, which is needed to extract the contract
749-
// components.
750771
const irep_idt &target_function =
751772
to_symbol_expr(const_target->call_function()).get_identifier();
752773
const symbolt &function_symbol = ns.lookup(target_function);
753-
const auto &type = to_code_with_contract_type(function_symbol.type);
774+
const code_typet &function_type = to_code_type(function_symbol.type);
754775

755776
// Isolate each component of the contract.
777+
const auto &type = get_contract(target_function, ns);
756778
auto assigns_clause = type.assigns();
757779
auto requires = conjunction(type.requires());
758780
auto ensures = conjunction(type.ensures());
@@ -770,7 +792,7 @@ void code_contractst::apply_function_contract(
770792
// if true, the call return variable variable was created during replacement
771793
bool call_ret_is_fresh_var = false;
772794

773-
if(type.return_type() != empty_typet())
795+
if(function_type.return_type() != empty_typet())
774796
{
775797
// Check whether the function's return value is not disregarded.
776798
if(target->call_lhs().is_not_nil())
@@ -797,14 +819,15 @@ void code_contractst::apply_function_contract(
797819
// fresh variable to replace __CPROVER_return_value with.
798820
call_ret_is_fresh_var = true;
799821
const symbolt &fresh = get_fresh_aux_symbol(
800-
type.return_type(),
822+
function_type.return_type(),
801823
id2string(target_function),
802824
"ignored_return_value",
803825
const_target->source_location(),
804826
symbol_table.lookup_ref(target_function).mode,
805827
ns,
806828
symbol_table);
807-
symbol_exprt ret_val(CPROVER_PREFIX "return_value", type.return_type());
829+
symbol_exprt ret_val(
830+
CPROVER_PREFIX "return_value", function_type.return_type());
808831
auto fresh_sym_expr = fresh.symbol_expr();
809832
common_replace.insert(ret_val, fresh_sym_expr);
810833
call_ret_opt = fresh_sym_expr;
@@ -1234,11 +1257,6 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12341257
const auto &goto_function = function_obj->second;
12351258
auto &function_body = function_obj->second.body;
12361259

1237-
// Get assigns clause for function
1238-
const symbolt &function_symbol = ns.lookup(function);
1239-
const auto &function_with_contract =
1240-
to_code_with_contract_type(function_symbol.type);
1241-
12421260
instrument_spec_assignst instrument_spec_assigns(
12431261
function, goto_functions, symbol_table, log.get_message_handler());
12441262

@@ -1282,8 +1300,8 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12821300
insert_before_swap_and_advance(
12831301
function_body, instruction_it, snapshot_static_locals);
12841302

1285-
// Track targets mentionned in the specification
1286-
for(auto &target : function_with_contract.assigns())
1303+
// Track targets mentioned in the specification
1304+
for(auto &target : get_contract(function, ns).assigns())
12871305
{
12881306
goto_programt payload;
12891307
instrument_spec_assigns.track_spec_target(target, payload);
@@ -1292,6 +1310,7 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12921310

12931311
// Track formal parameters
12941312
goto_programt snapshot_function_parameters;
1313+
const symbolt &function_symbol = ns.lookup(function);
12951314
for(const auto &param : to_code_type(function_symbol.type).parameters())
12961315
{
12971316
goto_programt payload;
@@ -1419,9 +1438,7 @@ void code_contractst::add_contract_check(
14191438
{
14201439
PRECONDITION(!dest.instructions.empty());
14211440

1422-
const symbolt &function_symbol = ns.lookup(mangled_function);
1423-
const auto &code_type = to_code_with_contract_type(function_symbol.type);
1424-
1441+
const auto &code_type = get_contract(wrapper_function, ns);
14251442
auto assigns = code_type.assigns();
14261443
auto requires = conjunction(code_type.requires());
14271444
auto ensures = conjunction(code_type.ensures());
@@ -1445,6 +1462,7 @@ void code_contractst::add_contract_check(
14451462
goto_programt check;
14461463

14471464
// prepare function call including all declarations
1465+
const symbolt &function_symbol = ns.lookup(mangled_function);
14481466
code_function_callt call(function_symbol.symbol_expr());
14491467

14501468
// Create a replace_symbolt object, for replacing expressions in the callee

src/goto-instrument/horn_encoding.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,12 +699,6 @@ static exprt simplifying_not(exprt src)
699699
return not_exprt(src);
700700
}
701701

702-
static bool has_contract(const code_with_contract_typet &contract)
703-
{
704-
return !contract.assigns().empty() || !contract.requires().empty() ||
705-
!contract.ensures().empty();
706-
}
707-
708702
void state_encodingt::function_call_symbol(
709703
goto_programt::const_targett loc,
710704
encoding_targett &dest)
@@ -1007,7 +1001,7 @@ void state_encoding(
10071001
const auto &symbol = ns.lookup(f->first);
10081002
if(
10091003
f->first == goto_functionst::entry_point() ||
1010-
has_contract(to_code_with_contract_type(symbol.type)))
1004+
to_code_with_contract_type(symbol.type).has_contract())
10111005
{
10121006
dest.annotation("");
10131007
dest.annotation("function " + id2string(f->first));

src/linking/linking.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,11 @@ void linkingt::duplicate_non_type_symbol(
11011101
symbolt &old_symbol,
11021102
symbolt &new_symbol)
11031103
{
1104+
// we do not permit multiple contracts to be defined, or cases where only one
1105+
// of the symbols is a contract
1106+
if(old_symbol.is_property || new_symbol.is_property)
1107+
link_error(old_symbol, new_symbol, "conflict on code contract");
1108+
11041109
// see if it is a function or a variable
11051110

11061111
bool is_code_old_symbol=old_symbol.type.id()==ID_code;

0 commit comments

Comments
 (0)