Skip to content

Commit 8d40abb

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 0c58a89 commit 8d40abb

13 files changed

+288
-21
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: 63 additions & 5 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
{
@@ -701,12 +715,14 @@ void c_typecheck_baset::typecheck_declaration(
701715

702716
// check the contract, if any
703717
symbolt &new_symbol = symbol_table.get_writeable_ref(identifier);
704-
if(new_symbol.type.id() == ID_code)
718+
if(
719+
new_symbol.type.id() == ID_code &&
720+
to_code_with_contract_type(new_symbol.type).has_contract())
705721
{
706-
// We typecheck this after the
707-
// function body done above, so as to have parameter symbols
708-
// available
709-
auto &code_type = to_code_with_contract_type(new_symbol.type);
722+
// We typecheck this after the function body done above, so as to have
723+
// parameter symbols available.
724+
code_with_contract_typet code_type =
725+
to_code_with_contract_type(new_symbol.type);
710726

711727
for(auto &requires : code_type.requires())
712728
{
@@ -744,6 +760,48 @@ void c_typecheck_baset::typecheck_declaration(
744760
if(return_type.id() != ID_empty)
745761
parameter_map.erase(CPROVER_PREFIX "return_value");
746762
}
763+
764+
// create a contract symbol
765+
symbolt contract;
766+
contract.name = "contract::" + id2string(new_symbol.name);
767+
contract.base_name = new_symbol.name;
768+
contract.pretty_name = new_symbol.name;
769+
contract.is_property = true;
770+
contract.type = code_type;
771+
contract.mode = new_symbol.mode;
772+
contract.module = module;
773+
contract.location = new_symbol.location;
774+
775+
auto entry = symbol_table.insert(std::move(contract));
776+
if(!entry.second)
777+
{
778+
error().source_location = new_symbol.location;
779+
error() << "contract '" << new_symbol.display_name()
780+
<< "' already set at " << entry.first.location.as_string()
781+
<< eom;
782+
throw 0;
783+
}
784+
785+
// Remove the contract from the original symbol as we have created a
786+
// dedicated contract symbol.
787+
new_symbol.type.remove(ID_C_spec_assigns);
788+
new_symbol.type.remove(ID_C_spec_ensures);
789+
new_symbol.type.remove(ID_C_spec_requires);
790+
791+
// remove parameter declarations if those were only added for type
792+
// checking the contract
793+
if(new_symbol.value.is_nil())
794+
{
795+
for(const auto &parameter : code_type.parameters())
796+
{
797+
const irep_idt &identifier = parameter.get_identifier();
798+
799+
symbol_tablet::symbolst::const_iterator p_s_it =
800+
symbol_table.symbols.find(identifier);
801+
if(p_s_it != symbol_table.symbols.end())
802+
symbol_table.erase(p_s_it);
803+
}
804+
}
747805
}
748806
}
749807
}

src/goto-instrument/contracts/contracts.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,29 @@ code_contractst::create_ensures_instruction(
727727
return std::make_pair(std::move(ensures_program), std::move(history));
728728
}
729729

730+
static const code_with_contract_typet &
731+
get_contract(const irep_idt &function, const namespacet &ns)
732+
{
733+
const std::string function_str = id2string(function);
734+
const symbolt &function_symbol = ns.lookup(function);
735+
736+
const symbolt *contract_sym;
737+
if(ns.lookup("contract::" + function_str, contract_sym))
738+
{
739+
// no contract provided in the source or just an empty assigns clause
740+
return to_code_with_contract_type(function_symbol.type);
741+
}
742+
743+
const auto &type = to_code_with_contract_type(contract_sym->type);
744+
if(type != function_symbol.type)
745+
{
746+
throw invalid_input_exceptiont(
747+
"Contract of '" + function_str + "' has different signature.");
748+
}
749+
750+
return type;
751+
}
752+
730753
void code_contractst::apply_function_contract(
731754
const irep_idt &function,
732755
const source_locationt &location,
@@ -739,14 +762,13 @@ void code_contractst::apply_function_contract(
739762
// function pointers.
740763
PRECONDITION(const_target->call_function().id() == ID_symbol);
741764

742-
// Retrieve the function type, which is needed to extract the contract
743-
// components.
744765
const irep_idt &target_function =
745766
to_symbol_expr(const_target->call_function()).get_identifier();
746767
const symbolt &function_symbol = ns.lookup(target_function);
747-
const auto &type = to_code_with_contract_type(function_symbol.type);
768+
const code_typet &function_type = to_code_type(function_symbol.type);
748769

749770
// Isolate each component of the contract.
771+
const auto &type = get_contract(target_function, ns);
750772
auto assigns_clause = type.assigns();
751773
auto requires = conjunction(type.requires());
752774
auto ensures = conjunction(type.ensures());
@@ -762,7 +784,7 @@ void code_contractst::apply_function_contract(
762784
// if true, the call return variable variable was created during replacement
763785
bool call_ret_is_fresh_var = false;
764786

765-
if(type.return_type() != empty_typet())
787+
if(function_type.return_type() != empty_typet())
766788
{
767789
// Check whether the function's return value is not disregarded.
768790
if(target->call_lhs().is_not_nil())
@@ -789,14 +811,15 @@ void code_contractst::apply_function_contract(
789811
// fresh variable to replace __CPROVER_return_value with.
790812
call_ret_is_fresh_var = true;
791813
const symbolt &fresh = get_fresh_aux_symbol(
792-
type.return_type(),
814+
function_type.return_type(),
793815
id2string(target_function),
794816
"ignored_return_value",
795817
const_target->source_location(),
796818
symbol_table.lookup_ref(target_function).mode,
797819
ns,
798820
symbol_table);
799-
symbol_exprt ret_val(CPROVER_PREFIX "return_value", type.return_type());
821+
symbol_exprt ret_val(
822+
CPROVER_PREFIX "return_value", function_type.return_type());
800823
auto fresh_sym_expr = fresh.symbol_expr();
801824
common_replace.insert(ret_val, fresh_sym_expr);
802825
call_ret_opt = fresh_sym_expr;
@@ -1230,11 +1253,6 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12301253
const auto &goto_function = function_obj->second;
12311254
auto &function_body = function_obj->second.body;
12321255

1233-
// Get assigns clause for function
1234-
const symbolt &function_symbol = ns.lookup(function);
1235-
const auto &function_with_contract =
1236-
to_code_with_contract_type(function_symbol.type);
1237-
12381256
instrument_spec_assignst instrument_spec_assigns(
12391257
function, goto_functions, symbol_table, log.get_message_handler());
12401258

@@ -1278,8 +1296,8 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12781296
insert_before_swap_and_advance(
12791297
function_body, instruction_it, snapshot_static_locals);
12801298

1281-
// Track targets mentionned in the specification
1282-
for(auto &target : function_with_contract.assigns())
1299+
// Track targets mentioned in the specification
1300+
for(auto &target : get_contract(function, ns).assigns())
12831301
{
12841302
goto_programt payload;
12851303
instrument_spec_assigns.track_spec_target(target, payload);
@@ -1288,6 +1306,7 @@ void code_contractst::check_frame_conditions_function(const irep_idt &function)
12881306

12891307
// Track formal parameters
12901308
goto_programt snapshot_function_parameters;
1309+
const symbolt &function_symbol = ns.lookup(function);
12911310
for(const auto &param : to_code_type(function_symbol.type).parameters())
12921311
{
12931312
goto_programt payload;
@@ -1372,9 +1391,7 @@ void code_contractst::add_contract_check(
13721391
{
13731392
PRECONDITION(!dest.instructions.empty());
13741393

1375-
const symbolt &function_symbol = ns.lookup(mangled_function);
1376-
const auto &code_type = to_code_with_contract_type(function_symbol.type);
1377-
1394+
const auto &code_type = get_contract(wrapper_function, ns);
13781395
auto assigns = code_type.assigns();
13791396
auto requires = conjunction(code_type.requires());
13801397
auto ensures = conjunction(code_type.ensures());
@@ -1397,6 +1414,7 @@ void code_contractst::add_contract_check(
13971414
goto_programt check;
13981415

13991416
// prepare function call including all declarations
1417+
const symbolt &function_symbol = ns.lookup(mangled_function);
14001418
code_function_callt call(function_symbol.symbol_expr());
14011419

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

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;

src/linking/remove_internal_symbols.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ void remove_internal_symbols(
154154
bool is_type=symbol.is_type;
155155
bool has_body=symbol.value.is_not_nil();
156156
bool has_initializer = symbol.value.is_not_nil();
157+
bool is_contract = is_function && symbol.is_property;
157158

158159
// __attribute__((constructor)), __attribute__((destructor))
159160
if(symbol.mode==ID_C && is_function && is_file_local)
@@ -182,6 +183,8 @@ void remove_internal_symbols(
182183
{
183184
get_symbols(ns, symbol, exported);
184185
}
186+
else if(is_contract)
187+
get_symbols(ns, symbol, exported);
185188
}
186189
else
187190
{

src/util/c_types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ class code_with_contract_typet : public code_typet
366366
{
367367
}
368368

369+
bool has_contract() const
370+
{
371+
return !ensures().empty() || !requires().empty() || !assigns().empty();
372+
}
373+
369374
const exprt::operandst &assigns() const
370375
{
371376
return static_cast<const exprt &>(find(ID_C_spec_assigns)).operands();

0 commit comments

Comments
 (0)