Skip to content

Commit 28ae8ce

Browse files
author
Remi Delmas
committed
Function contracts: preliminary support for guarded assigns clause targets.
- We add a guard expression to the internal representation of assigns clause targets, The validity condition expression of a target becomes `guard && all_deref_valid(target) && rw_ok(target, size)`. - We add support for targets of the form `__CPROVER_POINTER_OBJECT(guard ? target : NULL)` - We take care of removing any side effects still left in the guard.
1 parent 22c020b commit 28ae8ce

File tree

4 files changed

+286
-19
lines changed

4 files changed

+286
-19
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#include <stdbool.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
typedef struct CTX
6+
{
7+
int data[16];
8+
} CTX;
9+
10+
union low_level_t {
11+
CTX md5;
12+
};
13+
14+
typedef struct HIGH_LEVEL_MD
15+
{
16+
unsigned long flags;
17+
} HIGH_LEVEL_MD;
18+
19+
typedef struct HIGH_LEVEL_MD_CTX
20+
{
21+
HIGH_LEVEL_MD *digest;
22+
unsigned long flags;
23+
} HIGH_LEVEL_MD_CTX;
24+
25+
struct high_level_digest_t
26+
{
27+
HIGH_LEVEL_MD_CTX *ctx;
28+
};
29+
30+
struct high_level_t
31+
{
32+
struct high_level_digest_t first;
33+
struct high_level_digest_t second;
34+
};
35+
36+
typedef struct state_t
37+
{
38+
union {
39+
union low_level_t low_level;
40+
struct high_level_t high_level;
41+
} digest;
42+
} state_t;
43+
44+
bool nondet_bool();
45+
46+
bool is_high_level()
47+
{
48+
static bool latch = false;
49+
static bool once = false;
50+
if(!once)
51+
{
52+
latch = nondet_bool();
53+
once = true;
54+
}
55+
return latch;
56+
}
57+
58+
bool assign_second()
59+
{
60+
static bool latch = false;
61+
static bool once = false;
62+
if(!once)
63+
{
64+
latch = nondet_bool();
65+
once = true;
66+
}
67+
return latch;
68+
}
69+
70+
int update(state_t *state)
71+
// clang-format off
72+
__CPROVER_requires(__CPROVER_is_fresh(state, sizeof(*state)))
73+
__CPROVER_requires(
74+
is_high_level() ==>
75+
__CPROVER_is_fresh(state->digest.high_level.first.ctx,
76+
sizeof(*(state->digest.high_level.first.ctx))) &&
77+
__CPROVER_is_fresh(state->digest.high_level.first.ctx->digest,
78+
sizeof(*(state->digest.high_level.first.ctx->digest))) &&
79+
__CPROVER_is_fresh(state->digest.high_level.second.ctx,
80+
sizeof(*(state->digest.high_level.second.ctx))) &&
81+
__CPROVER_is_fresh(state->digest.high_level.second.ctx->digest,
82+
sizeof(*(state->digest.high_level.second.ctx->digest)))
83+
)
84+
__CPROVER_assigns(
85+
__CPROVER_POINTER_OBJECT(is_high_level() ? state->digest.high_level.first.ctx->digest : NULL),
86+
state->digest.high_level.first.ctx->flags,
87+
state->digest.high_level.second.ctx->flags
88+
)
89+
// clang-format on
90+
{
91+
if(is_high_level())
92+
{
93+
__CPROVER_havoc_object(state->digest.high_level.first.ctx->digest);
94+
__CPROVER_havoc_object(state->digest.high_level.second.ctx->digest);
95+
state->digest.high_level.first.ctx->flags = 0;
96+
state->digest.high_level.second.ctx->flags = 0;
97+
}
98+
return 0;
99+
}
100+
101+
int main()
102+
{
103+
state_t *state;
104+
update(state);
105+
return 0;
106+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--enforce-contract update _ --pointer-check --pointer-overflow-check --signed-overflow-check --unsigned-overflow-check --conversion-check
4+
^\[update.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)state->digest.high_level.first.ctx->digest\) is assignable: SUCCESS
5+
^\[update.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)state->digest.high_level.second.ctx->digest\) is assignable: FAILURE
6+
^VERIFICATION FAILED$
7+
^EXIT=10$
8+
^SIGNAL=0$
9+
--
10+
--
11+
Tests that guarded assignments to whole objects are supported
12+
using the notation __CPROVER_POINTER_OBJECT(guard ? target : NULL).

src/goto-instrument/contracts/assigns.cpp

Lines changed: 141 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,29 @@ Date: July 2021
1818

1919
#include <langapi/language_util.h>
2020

21+
#include <goto-programs/goto_convert_class.h>
2122
#include <util/arith_tools.h>
2223
#include <util/c_types.h>
2324
#include <util/pointer_offset_size.h>
2425
#include <util/pointer_predicates.h>
2526

27+
/// Allows to clean expressions of side effects.
28+
class cleanert : public goto_convertt
29+
{
30+
public:
31+
cleanert(
32+
symbol_table_baset &_symbol_table,
33+
message_handlert &_message_handler)
34+
: goto_convertt(_symbol_table, _message_handler)
35+
{
36+
}
37+
38+
void clean(exprt &guard, goto_programt &dest, const irep_idt &mode)
39+
{
40+
goto_convertt::clean_expr(guard, dest, mode, true);
41+
}
42+
};
43+
2644
static const slicet normalize_to_slice(const exprt &expr, const namespacet &ns)
2745
{
2846
// FIXME: Refactor these checks out to a common function that can be
@@ -52,6 +70,99 @@ static const slicet normalize_to_slice(const exprt &expr, const namespacet &ns)
5270
UNREACHABLE;
5371
}
5472

73+
/// Result type for guarded assigns target pattern matching
74+
using if_match_resultt = optionalt<std::pair<exprt, exprt>>;
75+
76+
/// Matches an expression with ID_NULL or ID_constant and value "0",
77+
/// modulo some intermediate ID_typecasts
78+
bool match_NULL_or_zero(const exprt &expr)
79+
{
80+
if(expr.id() == ID_typecast)
81+
return match_NULL_or_zero(expr.operands().front());
82+
else
83+
return expr.id() == ID_NULL ||
84+
(expr.id() == ID_constant &&
85+
id2string(to_constant_expr(expr).get_value()) == "0");
86+
}
87+
88+
/// Matches an expression of the form `guard ? target : (NULL|0)`
89+
/// modulo typecast wrapping the expression, the guard expresion
90+
/// and the NULL|0 expression.
91+
/// \param expr the candidate expression
92+
/// \returns A pair`(guard,target)` if successful.
93+
if_match_resultt match_if(const exprt &expr)
94+
{
95+
if(expr.id() == ID_typecast)
96+
{
97+
return match_if(expr.operands().front());
98+
}
99+
else if(expr.id() == ID_if)
100+
{
101+
if(!match_NULL_or_zero(expr.operands().at(2)))
102+
return {};
103+
104+
return {{expr.operands().at(0), expr.operands().at(1)}};
105+
}
106+
else
107+
return {};
108+
}
109+
110+
/// Pattern match the given expression for a guarded pattern
111+
/// and builds guarded_slice expression as follows:
112+
///
113+
/// ```
114+
/// case expr of
115+
/// | guard ? target : NULL ->
116+
/// {guard,
117+
/// target,
118+
/// address_of{target},
119+
/// size_of_expr{target}}
120+
/// | __CPROVER_POINTER_OBJECT(guard ? target : NULL) ->
121+
/// {guard,
122+
/// __CPROVER_POINTER_OBJECT(target),
123+
/// address_of{target-offset(target)},
124+
/// object_size{target}}
125+
/// | other ->
126+
/// {true,
127+
/// expr,
128+
/// address_of{expr},
129+
/// object_size{expr}}
130+
/// ```
131+
static const guarded_slicet
132+
normalize_to_guarded_slice(const exprt &expr, const namespacet &ns)
133+
{
134+
if(expr.id() == ID_pointer_object)
135+
{
136+
if_match_resultt match_opt = match_if(expr.operands().at(0));
137+
if(match_opt.has_value())
138+
{
139+
const auto &match = match_opt.value();
140+
const auto &new_expr = pointer_object(match.second);
141+
const auto &slice = normalize_to_slice(new_expr, ns);
142+
return {match.first, new_expr, slice.first, slice.second};
143+
}
144+
else
145+
{
146+
const auto &slice = normalize_to_slice(expr, ns);
147+
goto_programt empty_program;
148+
return {true_exprt{}, expr, slice.first, slice.second};
149+
}
150+
}
151+
152+
if_match_resultt match_opt = match_if(expr);
153+
if(match_opt.has_value())
154+
{
155+
const auto &match = match_opt.value();
156+
const auto &slice = normalize_to_slice(match.second, ns);
157+
return {match.first, match.second, slice.first, slice.second};
158+
}
159+
else
160+
{
161+
const auto &slice = normalize_to_slice(expr, ns);
162+
return {true_exprt{}, expr, slice.first, slice.second};
163+
}
164+
}
165+
55166
const symbolt assigns_clauset::conditional_address_ranget::generate_new_symbol(
56167
const std::string &prefix,
57168
const typet &type,
@@ -71,15 +182,19 @@ assigns_clauset::conditional_address_ranget::conditional_address_ranget(
71182
: source_expr(expr),
72183
location(expr.source_location()),
73184
parent(parent),
74-
slice(normalize_to_slice(expr, parent.ns)),
185+
guarded_slice(normalize_to_guarded_slice(expr, parent.ns)),
75186
validity_condition_var(
76187
generate_new_symbol("__car_valid", bool_typet(), location).symbol_expr()),
77-
lower_bound_address_var(
78-
generate_new_symbol("__car_lb", slice.first.type(), location)
79-
.symbol_expr()),
80-
upper_bound_address_var(
81-
generate_new_symbol("__car_ub", slice.first.type(), location)
82-
.symbol_expr())
188+
lower_bound_address_var(generate_new_symbol(
189+
"__car_lb",
190+
guarded_slice.start_adress.type(),
191+
location)
192+
.symbol_expr()),
193+
upper_bound_address_var(generate_new_symbol(
194+
"__car_ub",
195+
guarded_slice.start_adress.type(),
196+
location)
197+
.symbol_expr())
83198
{
84199
}
85200

@@ -93,6 +208,17 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
93208
source_locationt location_no_checks = location;
94209
disable_pointer_checks(location_no_checks);
95210

211+
null_message_handlert null_handler;
212+
213+
// clean up side effects from the guard expression
214+
// we want checks on the guard evaluation instructions because it is user code
215+
cleanert cleaner(parent.symbol_table, null_handler);
216+
exprt clean_guard(guarded_slice.guard);
217+
const auto &mode = parent.symbol_table.lookup_ref(parent.function_name).mode;
218+
cleaner.clean(clean_guard, instructions, mode);
219+
for(auto &inst : instructions.instructions)
220+
inst.source_location_nonconst() = location;
221+
96222
instructions.add(
97223
goto_programt::make_decl(validity_condition_var, location_no_checks));
98224
instructions.add(
@@ -102,28 +228,30 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
102228

103229
instructions.add(goto_programt::make_assignment(
104230
lower_bound_address_var,
105-
null_pointer_exprt{to_pointer_type(slice.first.type())},
231+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
106232
location_no_checks));
107233
instructions.add(goto_programt::make_assignment(
108234
upper_bound_address_var,
109-
null_pointer_exprt{to_pointer_type(slice.first.type())},
235+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
110236
location_no_checks));
111237

112238
goto_programt skip_program;
113239
const auto skip_target =
114240
skip_program.add(goto_programt::make_skip(location_no_checks));
115241

116242
const auto validity_check_expr =
117-
and_exprt{all_dereferences_are_valid(source_expr, parent.ns),
118-
w_ok_exprt{slice.first, slice.second}};
243+
and_exprt{clean_guard,
244+
all_dereferences_are_valid(guarded_slice.expr, parent.ns),
245+
w_ok_exprt{guarded_slice.start_adress, guarded_slice.size}};
246+
119247
instructions.add(goto_programt::make_assignment(
120248
validity_condition_var, validity_check_expr, location_no_checks));
121249

122250
instructions.add(goto_programt::make_goto(
123251
skip_target, not_exprt{validity_condition_var}, location_no_checks));
124252

125253
instructions.add(goto_programt::make_assignment(
126-
lower_bound_address_var, slice.first, location_no_checks));
254+
lower_bound_address_var, guarded_slice.start_adress, location_no_checks));
127255

128256
// the computation of the CAR upper bound can overflow into the object ID bits
129257
// of the pointer with very large allocation sizes.
@@ -133,7 +261,7 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
133261

134262
instructions.add(goto_programt::make_assignment(
135263
upper_bound_address_var,
136-
plus_exprt{slice.first, slice.second},
264+
plus_exprt{guarded_slice.start_adress, guarded_slice.size},
137265
location_overflow_check));
138266
instructions.destructive_append(skip_program);
139267

src/goto-instrument/contracts/assigns.h

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ Date: July 2021
2222

2323
typedef std::pair<const exprt, const exprt> slicet;
2424

25+
/// A guarded slice of memory
26+
typedef struct guarded_slicet
27+
{
28+
guarded_slicet(exprt _guard, exprt _expr, exprt _start_address, exprt _size)
29+
: guard(_guard), expr(_expr), start_adress(_start_address), size(_size)
30+
{
31+
}
32+
33+
/// Guard expression (may contain side effects)
34+
const exprt guard;
35+
36+
/// Target expression
37+
const exprt expr;
38+
39+
/// Start address of the target
40+
const exprt start_adress;
41+
42+
/// Size of the target
43+
const exprt size;
44+
} guarded_slicet;
45+
2546
/// \brief A class for representing assigns clauses in code contracts
2647
class assigns_clauset
2748
{
@@ -56,7 +77,7 @@ class assigns_clauset
5677
const source_locationt &location;
5778
const assigns_clauset &parent;
5879

59-
const slicet slice;
80+
const guarded_slicet guarded_slice;
6081
const symbol_exprt validity_condition_var;
6182
const symbol_exprt lower_bound_address_var;
6283
const symbol_exprt upper_bound_address_var;
@@ -78,11 +99,11 @@ class assigns_clauset
7899
write_sett;
79100

80101
assigns_clauset(
81-
const exprt::operandst &,
82-
const messaget &,
83-
const namespacet &,
84-
const irep_idt &,
85-
symbol_tablet &);
102+
const exprt::operandst &assigns,
103+
const messaget &log,
104+
const namespacet &ns,
105+
const irep_idt &function_name,
106+
symbol_tablet &symbol_table);
86107

87108
write_sett::const_iterator add_to_write_set(const exprt &);
88109
void remove_from_write_set(const exprt &);

0 commit comments

Comments
 (0)