Skip to content

Commit 28900c7

Browse files
committed
Fix function contracts with ignored return value
This commit ensures that `--replace-call-with-contract` works for calls to functions that mention `__CPROVER_return_value` in the postcondition, where the call ignores the return value. Previously, goto-instrument would not rewrite the `__CPROVER_return_value` identifier in the body of the calling function, so CBMC would abort when it looks up that symbol (which wasn't declared in the calling function).
1 parent 3ce7307 commit 28900c7

File tree

5 files changed

+112
-7
lines changed

5 files changed

+112
-7
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <stdlib.h>
2+
3+
int get_at_idx(int const *const arr, const size_t len, const size_t idx)
4+
__CPROVER_requires(__CPROVER_r_ok(arr, len) && idx < len)
5+
__CPROVER_ensures(__CPROVER_return_value == arr[idx])
6+
{
7+
return arr[idx];
8+
}
9+
10+
void main()
11+
{
12+
int a[5] = {0};
13+
get_at_idx(a, 5, 3);
14+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--replace-all-calls-with-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring
9+
--
10+
This test exposes a bug where CBMC would crash on a program where a function
11+
with a return value has a postcondition that mentions __CPROVER_return_value,
12+
but the caller does not assign the return value to anything.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int get_at_idx(int const *const arr, const size_t len, const size_t idx)
5+
__CPROVER_requires(__CPROVER_r_ok(arr, len) && idx < len)
6+
__CPROVER_ensures(__CPROVER_return_value == arr[idx])
7+
{
8+
return arr[idx];
9+
}
10+
11+
void main()
12+
{
13+
int a[5] = {0};
14+
a[3] = 7;
15+
int x = get_at_idx(a, 5, 3);
16+
assert(x == 7);
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
main.c
3+
--replace-all-calls-with-contracts
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--

src/goto-instrument/code_contracts.cpp

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@ Date: February 2016
2727

2828
#include "loop_utils.h"
2929

30+
/// Predicate to be used with the exprt::visit() function. The function
31+
/// found_return_value() will return `true` iff this predicate is called on an
32+
/// expr that contains `__CPROVER_return_value`.
33+
class return_value_visitort : public const_expr_visitort
34+
{
35+
public:
36+
return_value_visitort() : const_expr_visitort()
37+
{
38+
}
39+
40+
// \brief Has this object been passed to exprt::visit() on an exprt whose
41+
// descendants contain __CPROVER_return_value?
42+
bool found_return_value()
43+
{
44+
return found;
45+
}
46+
47+
void operator()(const exprt &exp) override
48+
{
49+
if(exp.id() != ID_symbol)
50+
return;
51+
const symbol_exprt &sym = to_symbol_expr(exp);
52+
found |= sym.get_identifier() == CPROVER_PREFIX "return_value";
53+
}
54+
55+
protected:
56+
bool found;
57+
};
58+
3059
static void check_apply_invariants(
3160
goto_functionst::goto_functiont &goto_function,
3261
const local_may_aliast &local_may_alias,
@@ -147,17 +176,42 @@ bool code_contractst::apply_contract(
147176
if(ensures.is_nil())
148177
return false;
149178

150-
// replace formal parameters by arguments, replace return
151179
replace_symbolt replace;
152-
153-
// TODO: return value could be nil
154-
if(type.return_type()!=empty_typet())
180+
// Replace return value
181+
if(type.return_type() != empty_typet())
155182
{
156-
symbol_exprt ret_val(CPROVER_PREFIX "return_value", call.lhs().type());
157-
replace.insert(ret_val, call.lhs());
183+
if(call.lhs().is_not_nil())
184+
{
185+
// foo() ensures that its return value is > 5. Then rewrite calls to foo:
186+
// x = foo() -> assume(__CPROVER_return_value > 5) -> assume(x > 5)
187+
symbol_exprt ret_val(CPROVER_PREFIX "return_value", call.lhs().type());
188+
replace.insert(ret_val, call.lhs());
189+
}
190+
else
191+
{
192+
// Function does have a return value, but call is not being assigned to
193+
// anything so we can't use the trick above.
194+
return_value_visitort v;
195+
ensures.visit(v);
196+
if(v.found_return_value())
197+
{
198+
// The postcondition does mention __CPROVER_return_value, so mint a
199+
// fresh variable to replace __CPROVER_return_value with.
200+
const symbolt &fresh = get_fresh_aux_symbol(
201+
type.return_type(),
202+
id2string(function),
203+
"ignored_return_value",
204+
call.source_location(),
205+
symbol_table.lookup_ref(function).mode,
206+
ns,
207+
symbol_table);
208+
symbol_exprt ret_val(CPROVER_PREFIX "return_value", type.return_type());
209+
replace.insert(ret_val, fresh.symbol_expr());
210+
}
211+
}
158212
}
159213

160-
// formal parameters
214+
// Replace formal parameters
161215
code_function_callt::argumentst::const_iterator a_it=
162216
call.arguments().begin();
163217
for(code_typet::parameterst::const_iterator

0 commit comments

Comments
 (0)