Skip to content

Commit 59bfc73

Browse files
committed
This is useful for:
+ checking containment in assigns clause verification + havocing only valid memory locations in assigns clause replacement + creating history snapshots only for valid `old` expressions
1 parent a6dc2cf commit 59bfc73

File tree

14 files changed

+254
-137
lines changed

14 files changed

+254
-137
lines changed

regression/contracts/assigns_enforce_structs_07/main.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ void f2(struct pair_of_pairs *pp) __CPROVER_assigns(*(pp->p->buf))
2525

2626
int main()
2727
{
28-
struct pair *p = nondet_bool() ? malloc(sizeof(*p)) : NULL;
28+
struct pair *p = malloc(sizeof(*p));
2929
f1(p);
30+
3031
struct pair_of_pairs *pp = malloc(sizeof(*pp));
32+
if(pp)
33+
pp->p = malloc(sizeof(*(pp->p)));
3134
f2(pp);
3235

3336
return 0;
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
KNOWNBUG
1+
CORE
22
main.c
3-
--enforce-all-contracts _ --pointer-check
3+
--enforce-all-contracts _ --malloc-may-fail --malloc-fail-null --pointer-check
44
^EXIT=10$
55
^SIGNAL=0$
6-
^\[f1.\d+\] line \d+ Check that p->buf\[\(.*\)0\] is assignable: FAILURE$
6+
^\[f1.\d+\] line \d+ Check that p->buf\[\(.*\)0\] is assignable: SUCCESS$
7+
^\[f1.pointer\_dereference.\d+\] line \d+ dereference failure: pointer invalid in p->buf\[\(.*\)0\]: FAILURE$
78
^\[f1.pointer\_dereference.\d+\] line \d+ dereference failure: pointer NULL in p->buf: FAILURE$
8-
^\[f2.\d+\] line \d+ Check that pp->p->buf\[\(.*\)0\] is assignable: FAILURE$
9+
^\[f2.\d+\] line \d+ Check that pp->p->buf\[\(.*\)0\] is assignable: SUCCESS$
10+
^\[f2.pointer\_dereference.\d+\] line \d+ dereference failure: pointer invalid in pp->p->buf\[\(.*\)0\]: FAILURE$
911
^\[f2.pointer\_dereference.\d+\] line \d+ dereference failure: pointer NULL in pp->p->buf: FAILURE$
1012
^VERIFICATION FAILED$
1113
--
1214
--
1315
Checks whether CBMC properly evaluates write set of members
1416
from invalid objects. In this case, they are not writable.
15-
16-
Bug: We need to check the validity of each dereference
17-
when accessing struct members.

regression/contracts/assigns_replace_07/main.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@
44

55
struct pair
66
{
7-
uint8_t *buf;
8-
size_t size;
7+
uint8_t buf[8];
98
};
109

1110
void f1(struct pair *p) __CPROVER_assigns(*(p->buf))
12-
__CPROVER_ensures(p->buf[0] == 0)
11+
__CPROVER_ensures((p == NULL) || p->buf[0] == 0)
1312
{
14-
p->buf[0] = 0;
13+
if(p != NULL)
14+
p->buf[0] = 0;
1515
}
1616

1717
int main()
1818
{
1919
struct pair *p = nondet_bool() ? malloc(sizeof(*p)) : NULL;
2020
f1(p);
2121
// clang-format off
22-
assert(p != NULL ==> p->buf[0] == 0);
22+
assert(p == NULL || p->buf[0] == 0);
2323
// clang-format on
2424
return 0;
2525
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
CORE
22
main.c
33
--replace-all-calls-with-contracts _ --pointer-check
4-
^EXIT=10$
4+
^EXIT=0$
55
^SIGNAL=0$
6-
^\[pointer\_dereference.\d+\] file main.c line \d+ dereference failure: pointer NULL in p->buf: FAILURE$
7-
^\[main.assertion.\d+\] line \d+ assertion p \!\= NULL \=\=> p->buf\[0\] \=\= 0: FAILURE$
8-
^VERIFICATION FAILED$
6+
^\[main.assertion.\d+\] line \d+ assertion p == NULL \|\| p->buf\[0\] == 0: SUCCESS$
7+
^VERIFICATION SUCCESSFUL$
98
--
109
--
11-
Checks whether CBMC properly evaluates write set of members
12-
from invalid objects. In this case, they are not writable.
10+
Checks whether CBMC properly evaluates write set of members from invalid objects.
11+
Functions are not expected to write to invalid locations; CBMC flags such writes.
12+
For contract checking, we ignore invalid targets in assigns clauses and assignment LHS.

regression/contracts/invar_loop-entry_check/main.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ void main()
3333
assert(x2 == z2);
3434

3535
int y3;
36-
s *s1, *s2;
36+
s s0, s1, *s2 = &s0;
3737
s2->n = malloc(sizeof(int));
38-
s1->n = s2->n;
38+
s1.n = s2->n;
3939

4040
while(y3 > 0)
41-
__CPROVER_loop_invariant(s1->n == __CPROVER_loop_entry(s1->n))
41+
__CPROVER_loop_invariant(s2->n == __CPROVER_loop_entry(s2->n))
4242
{
4343
--y3;
44-
s1->n = s1->n + 1;
45-
s1->n = s1->n - 1;
44+
s0.n = s0.n + 1;
45+
s2->n = s2->n - 1;
4646
}
4747

48-
assert(*(s1->n) == *(s2->n));
48+
assert(*(s1.n) == *(s2->n));
4949
}

regression/contracts/invar_loop-entry_check/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--apply-loop-contracts
3+
--apply-loop-contracts _ --pointer-primitive-check
44
^EXIT=0$
55
^SIGNAL=0$
66
^\[main.1\] .* Check loop invariant before entry: SUCCESS$
@@ -11,7 +11,7 @@ main.c
1111
^\[main.assertion.2\] .* assertion x2 == z2: SUCCESS$
1212
^\[main.5\] .* Check loop invariant before entry: SUCCESS$
1313
^\[main.6\] .* Check that loop invariant is preserved: SUCCESS$
14-
^\[main.assertion.3\] .* assertion \*\(s1->n\) == \*\(s2->n\): SUCCESS$
14+
^\[main.assertion.3\] .* assertion \*\(s1\.n\) == \*\(s2->n\): SUCCESS$
1515
^VERIFICATION SUCCESSFUL$
1616
--
1717
--

src/goto-instrument/contracts/assigns.cpp

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ Date: July 2021
1717

1818
#include <langapi/language_util.h>
1919

20-
#include <util/arith_tools.h>
21-
#include <util/c_types.h>
2220
#include <util/pointer_offset_size.h>
2321
#include <util/pointer_predicates.h>
2422

23+
#include "utils.h"
24+
2525
assigns_clauset::targett::targett(
2626
const assigns_clauset &parent,
2727
const exprt &expr)
@@ -46,27 +46,16 @@ exprt assigns_clauset::targett::normalize(const exprt &expr)
4646
exprt assigns_clauset::targett::generate_containment_check(
4747
const address_of_exprt &lhs_address) const
4848
{
49-
exprt::operandst address_validity;
50-
exprt::operandst containment_check;
51-
52-
// __CPROVER_w_ok(target, sizeof(target))
53-
address_validity.push_back(w_ok_exprt(
54-
address,
55-
size_of_expr(dereference_exprt(address).type(), parent.ns).value()));
56-
57-
// __CPROVER_w_ok(lhs, sizeof(lhs))
58-
address_validity.push_back(w_ok_exprt(
59-
lhs_address,
60-
size_of_expr(dereference_exprt(lhs_address).type(), parent.ns).value()));
49+
const auto address_validity = and_exprt{
50+
all_dereferences_are_valid(dereference_exprt{address}, parent.ns),
51+
all_dereferences_are_valid(dereference_exprt{lhs_address}, parent.ns)};
6152

62-
// __CPROVER_same_object(lhs, target)
53+
exprt::operandst containment_check;
6354
containment_check.push_back(same_object(lhs_address, address));
6455

6556
// If assigns target was a dereference, comparing objects is enough
66-
// and the resulting condition will be
67-
// __CPROVER_w_ok(target, sizeof(target))
68-
// && __CPROVER_w_ok(lhs, sizeof(lhs))
69-
// ==> __CPROVER_same_object(lhs, target)
57+
// and the resulting condition will be:
58+
// VALID(self) && VALID(lhs) ==> __CPROVER_same_object(lhs, self)
7059
if(id != ID_dereference)
7160
{
7261
const auto lhs_offset = pointer_offset(lhs_address);
@@ -89,19 +78,17 @@ exprt assigns_clauset::targett::generate_containment_check(
8978
own_offset);
9079

9180
// (sizeof(lhs) + __CPROVER_offset(lhs)) <=
92-
// (sizeof(target) + __CPROVER_offset(target))
81+
// (sizeof(self) + __CPROVER_offset(self))
9382
containment_check.push_back(
9483
binary_relation_exprt(lhs_region, ID_le, own_region));
9584
}
9685

97-
// __CPROVER_w_ok(target, sizeof(target))
98-
// && __CPROVER_w_ok(lhs, sizeof(lhs))
99-
// ==> __CPROVER_same_object(lhs, target)
100-
// && __CPROVER_offset(lhs) >= __CPROVER_offset(target)
101-
// && (sizeof(lhs) + __CPROVER_offset(lhs)) <=
102-
// (sizeof(target) + __CPROVER_offset(target))
103-
return binary_relation_exprt(
104-
conjunction(address_validity), ID_implies, conjunction(containment_check));
86+
// VALID(self) && VALID(lhs)
87+
// ==> __CPROVER_same_object(lhs, self)
88+
// && __CPROVER_offset(lhs) >= __CPROVER_offset(self)
89+
// && (sizeof(lhs) + __CPROVER_offset(lhs)) <=
90+
// (sizeof(self) + __CPROVER_offset(self))
91+
return or_exprt{not_exprt{address_validity}, conjunction(containment_check)};
10592
}
10693

10794
assigns_clauset::assigns_clauset(
@@ -150,12 +137,13 @@ goto_programt assigns_clauset::generate_havoc_code() const
150137
modifiest modifies;
151138
for(const auto &target : global_write_set)
152139
modifies.insert(target.address.object());
153-
154140
for(const auto &target : local_write_set)
155141
modifies.insert(target.address.object());
156142

157143
goto_programt havoc_statements;
158-
append_havoc_code(location, modifies, havoc_statements);
144+
havoc_if_validt havoc_gen(modifies, ns);
145+
havoc_gen.append_full_havoc_code(location, havoc_statements);
146+
159147
return havoc_statements;
160148
}
161149

src/goto-instrument/contracts/contracts.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Date: February 2016
3333
#include <util/mathematical_types.h>
3434
#include <util/message.h>
3535
#include <util/pointer_offset_size.h>
36+
#include <util/pointer_predicates.h>
3637
#include <util/replace_symbol.h>
3738

3839
#include "assigns.h"
@@ -150,7 +151,8 @@ void code_contractst::check_apply_loop_contracts(
150151
}
151152

152153
// havoc the variables that may be modified
153-
append_havoc_code(loop_head->source_location, modifies, havoc_code);
154+
havoc_utilst havoc_gen(modifies);
155+
havoc_gen.append_full_havoc_code(loop_head->source_location, havoc_code);
154156

155157
// Generate: assume(invariant) just after havocing
156158
// We use a block scope to create a temporary assumption,
@@ -366,6 +368,11 @@ void code_contractst::replace_history_parameter(
366368

367369
if(it == parameter2history.end())
368370
{
371+
// 0. Create a skip target to jump to, if the parameter is invalid
372+
goto_programt skip_program;
373+
const auto skip_target =
374+
skip_program.add(goto_programt::make_skip(location));
375+
369376
// 1. Create a temporary symbol expression that represents the
370377
// history variable
371378
symbol_exprt tmp_symbol =
@@ -376,14 +383,23 @@ void code_contractst::replace_history_parameter(
376383
parameter2history[parameter] = tmp_symbol;
377384

378385
// 3. Add the required instructions to the instructions list
379-
// 3.1 Declare the newly created temporary variable
386+
// 3.1. Declare the newly created temporary variable
380387
history.add(goto_programt::make_decl(tmp_symbol, location));
381388

382-
// 3.2 Add an assignment such that the value pointed to by the new
389+
// 3.2. Skip storing the history if the expression is invalid
390+
history.add(goto_programt::make_goto(
391+
skip_target,
392+
not_exprt{all_dereferences_are_valid(parameter, ns)},
393+
location));
394+
395+
// 3.3. Add an assignment such that the value pointed to by the new
383396
// temporary variable is equal to the value of the corresponding
384397
// parameter
385398
history.add(
386399
goto_programt::make_assignment(tmp_symbol, parameter, location));
400+
401+
// 3.4. Add a skip target
402+
history.destructive_append(skip_program);
387403
}
388404

389405
expr = parameter2history[parameter];

src/goto-instrument/contracts/utils.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,64 @@ Date: September 2021
1010

1111
#include "utils.h"
1212

13+
#include <util/pointer_expr.h>
14+
#include <util/pointer_predicates.h>
15+
16+
static void append_safe_havoc_code_for_expr(
17+
const source_locationt location,
18+
const namespacet &ns,
19+
const exprt &expr,
20+
goto_programt &dest,
21+
const std::function<void()> &havoc_code_impl)
22+
{
23+
goto_programt skip_program;
24+
const auto skip_target = skip_program.add(goto_programt::make_skip(location));
25+
26+
// skip havocing only if all pointer derefs in the expression are valid
27+
// (to avoid spurious pointer deref errors)
28+
dest.add(goto_programt::make_goto(
29+
skip_target, not_exprt{all_dereferences_are_valid(expr, ns)}, location));
30+
31+
havoc_code_impl();
32+
33+
// add the final skip target
34+
dest.destructive_append(skip_program);
35+
}
36+
37+
void havoc_if_validt::append_object_havoc_code_for_expr(
38+
const source_locationt location,
39+
const exprt &expr,
40+
goto_programt &dest) const
41+
{
42+
append_safe_havoc_code_for_expr(location, ns, expr, dest, [&]() {
43+
havoc_utilst::append_object_havoc_code_for_expr(location, expr, dest);
44+
});
45+
}
46+
47+
void havoc_if_validt::append_scalar_havoc_code_for_expr(
48+
const source_locationt location,
49+
const exprt &expr,
50+
goto_programt &dest) const
51+
{
52+
append_safe_havoc_code_for_expr(location, ns, expr, dest, [&]() {
53+
havoc_utilst::append_scalar_havoc_code_for_expr(location, expr, dest);
54+
});
55+
}
56+
57+
exprt all_dereferences_are_valid(const exprt &expr, const namespacet &ns)
58+
{
59+
exprt::operandst validity_checks;
60+
61+
if(expr.id() == ID_dereference)
62+
validity_checks.push_back(
63+
good_pointer_def(to_dereference_expr(expr).pointer(), ns));
64+
65+
for(const auto &op : expr.operands())
66+
validity_checks.push_back(all_dereferences_are_valid(op, ns));
67+
68+
return conjunction(validity_checks);
69+
}
70+
1371
exprt generate_lexicographic_less_than_check(
1472
const std::vector<symbol_exprt> &lhs,
1573
const std::vector<symbol_exprt> &rhs)

src/goto-instrument/contracts/utils.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,43 @@ Date: September 2021
1313

1414
#include <vector>
1515

16+
#include <goto-instrument/havoc_utils.h>
17+
1618
#include <goto-programs/goto_model.h>
1719

20+
/// \brief A class that overrides the low-level havocing functions in the base
21+
/// utility class, to havoc only when expressions point to valid memory,
22+
/// i.e. if all dereferences within an expression are valid
23+
class havoc_if_validt : public havoc_utilst
24+
{
25+
public:
26+
havoc_if_validt(const modifiest &mod, const namespacet &ns)
27+
: havoc_utilst(mod), ns(ns)
28+
{
29+
}
30+
31+
void append_object_havoc_code_for_expr(
32+
const source_locationt location,
33+
const exprt &expr,
34+
goto_programt &dest) const override;
35+
36+
void append_scalar_havoc_code_for_expr(
37+
const source_locationt location,
38+
const exprt &expr,
39+
goto_programt &dest) const override;
40+
41+
protected:
42+
const namespacet &ns;
43+
};
44+
45+
/// \brief Generate a validity check over all dereferences in an expression
46+
///
47+
/// \param expr The expression that contains dereferences to be validated
48+
/// \param ns The namespace that defines all symbols appearing in `expr`
49+
/// \return A conjunctive expression that checks validity of all pointers
50+
/// that are dereferenced within `expr`
51+
exprt all_dereferences_are_valid(const exprt &expr, const namespacet &ns);
52+
1853
/// \brief Generate a lexicographic less-than comparison over ordered tuples
1954
///
2055
/// This function creates an expression representing a lexicographic less-than
@@ -37,7 +72,7 @@ exprt generate_lexicographic_less_than_check(
3772
/// After insertion, `target` is advanced by the size of the `payload`,
3873
/// and `payload` is destroyed.
3974
///
40-
/// \param program The destination program for inserting the `payload`
75+
/// \param destination The destination program for inserting the `payload`
4176
/// \param target The instruction iterator at which to insert the `payload`
4277
/// \param payload The goto program to be inserted into the `destination`
4378
void insert_before_swap_and_advance(

0 commit comments

Comments
 (0)