Skip to content

Commit d80695b

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 d80695b

File tree

4 files changed

+285
-19
lines changed

4 files changed

+285
-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: 140 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,98 @@ 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 intermediary typecasts.
90+
/// \param expr the candidate expression
91+
/// \returns A pair `(guard,target)` if successful.
92+
if_match_resultt match_if(const exprt &expr)
93+
{
94+
if(expr.id() == ID_typecast)
95+
{
96+
return match_if(expr.operands().front());
97+
}
98+
else if(expr.id() == ID_if)
99+
{
100+
if(!match_NULL_or_zero(expr.operands().at(2)))
101+
return {};
102+
103+
return {{expr.operands().at(0), expr.operands().at(1)}};
104+
}
105+
else
106+
return {};
107+
}
108+
109+
/// Pattern match the given expression for a guarded pattern
110+
/// and builds guarded_slice expression as follows:
111+
///
112+
/// ```
113+
/// case expr of
114+
/// | guard ? target : NULL ->
115+
/// {guard,
116+
/// target,
117+
/// address_of{target},
118+
/// size_of_expr{target}}
119+
/// | __CPROVER_POINTER_OBJECT(guard ? target : NULL) ->
120+
/// {guard,
121+
/// __CPROVER_POINTER_OBJECT(target),
122+
/// address_of{target-offset(target)},
123+
/// object_size{target}}
124+
/// | other ->
125+
/// {true,
126+
/// expr,
127+
/// address_of{expr},
128+
/// object_size{expr}}
129+
/// ```
130+
static const guarded_slicet
131+
normalize_to_guarded_slice(const exprt &expr, const namespacet &ns)
132+
{
133+
if(expr.id() == ID_pointer_object)
134+
{
135+
if_match_resultt match_opt = match_if(expr.operands().at(0));
136+
if(match_opt.has_value())
137+
{
138+
const auto &match = match_opt.value();
139+
const auto &new_expr = pointer_object(match.second);
140+
const auto &slice = normalize_to_slice(new_expr, ns);
141+
return {match.first, new_expr, slice.first, slice.second};
142+
}
143+
else
144+
{
145+
const auto &slice = normalize_to_slice(expr, ns);
146+
goto_programt empty_program;
147+
return {true_exprt{}, expr, slice.first, slice.second};
148+
}
149+
}
150+
151+
if_match_resultt match_opt = match_if(expr);
152+
if(match_opt.has_value())
153+
{
154+
const auto &match = match_opt.value();
155+
const auto &slice = normalize_to_slice(match.second, ns);
156+
return {match.first, match.second, slice.first, slice.second};
157+
}
158+
else
159+
{
160+
const auto &slice = normalize_to_slice(expr, ns);
161+
return {true_exprt{}, expr, slice.first, slice.second};
162+
}
163+
}
164+
55165
const symbolt assigns_clauset::conditional_address_ranget::generate_new_symbol(
56166
const std::string &prefix,
57167
const typet &type,
@@ -71,15 +181,19 @@ assigns_clauset::conditional_address_ranget::conditional_address_ranget(
71181
: source_expr(expr),
72182
location(expr.source_location()),
73183
parent(parent),
74-
slice(normalize_to_slice(expr, parent.ns)),
184+
guarded_slice(normalize_to_guarded_slice(expr, parent.ns)),
75185
validity_condition_var(
76186
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())
187+
lower_bound_address_var(generate_new_symbol(
188+
"__car_lb",
189+
guarded_slice.start_adress.type(),
190+
location)
191+
.symbol_expr()),
192+
upper_bound_address_var(generate_new_symbol(
193+
"__car_ub",
194+
guarded_slice.start_adress.type(),
195+
location)
196+
.symbol_expr())
83197
{
84198
}
85199

@@ -93,6 +207,17 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
93207
source_locationt location_no_checks = location;
94208
disable_pointer_checks(location_no_checks);
95209

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

103228
instructions.add(goto_programt::make_assignment(
104229
lower_bound_address_var,
105-
null_pointer_exprt{to_pointer_type(slice.first.type())},
230+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
106231
location_no_checks));
107232
instructions.add(goto_programt::make_assignment(
108233
upper_bound_address_var,
109-
null_pointer_exprt{to_pointer_type(slice.first.type())},
234+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
110235
location_no_checks));
111236

112237
goto_programt skip_program;
113238
const auto skip_target =
114239
skip_program.add(goto_programt::make_skip(location_no_checks));
115240

116241
const auto validity_check_expr =
117-
and_exprt{all_dereferences_are_valid(source_expr, parent.ns),
118-
w_ok_exprt{slice.first, slice.second}};
242+
and_exprt{clean_guard,
243+
all_dereferences_are_valid(guarded_slice.expr, parent.ns),
244+
w_ok_exprt{guarded_slice.start_adress, guarded_slice.size}};
245+
119246
instructions.add(goto_programt::make_assignment(
120247
validity_condition_var, validity_check_expr, location_no_checks));
121248

122249
instructions.add(goto_programt::make_goto(
123250
skip_target, not_exprt{validity_condition_var}, location_no_checks));
124251

125252
instructions.add(goto_programt::make_assignment(
126-
lower_bound_address_var, slice.first, location_no_checks));
253+
lower_bound_address_var, guarded_slice.start_adress, location_no_checks));
127254

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

134261
instructions.add(goto_programt::make_assignment(
135262
upper_bound_address_var,
136-
plus_exprt{slice.first, slice.second},
263+
plus_exprt{guarded_slice.start_adress, guarded_slice.size},
137264
location_overflow_check));
138265
instructions.destructive_append(skip_program);
139266

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)