Skip to content

Commit 6916359

Browse files
author
Remi Delmas
committed
Function contracts: preliminary support for guarded assigns clause targets.
Guarded targets specify memory locations that can be assigned if and only if a condition holds when a function is invoked. - Add a guard expression to the internal representation of assigns clause targets - Translate guard expressions to clean GOTO instructions using `goto-convert::clean_expr` - Change the validity condition to `guard && all_deref_valid(target) && rw_ok(target, size)` for assigns clause checking - Pattern match `__CPROVER_POINTER_OBJECT(guard ? target : NULL)` and map them to guarded targets
1 parent 22c020b commit 6916359

File tree

4 files changed

+282
-19
lines changed

4 files changed

+282
-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: 137 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,30 @@ 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>
24+
#include <util/expr_util.h>
2325
#include <util/pointer_offset_size.h>
2426
#include <util/pointer_predicates.h>
2527

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

74+
/// Result type for guarded assigns target pattern matching
75+
using if_match_resultt = optionalt<std::pair<exprt, exprt>>;
76+
77+
/// \brief Matches an expression with ID_NULL or ID_constant "0".
78+
/// \param expr The candidate expression.
79+
/// \returns True iff the match is successful.
80+
bool match_NULL_or_zero(const exprt &expr)
81+
{
82+
const auto &clean_expr = skip_typecast(expr);
83+
// FIXME clean up once #6532 lands.
84+
return clean_expr.id() == ID_NULL ||
85+
(clean_expr.id() == ID_constant &&
86+
id2string(to_constant_expr(clean_expr).get_value()) == "0");
87+
}
88+
89+
/// \brief Matches an expression of the form `guard ? target : (NULL|0)`
90+
/// \param expr The candidate expression.
91+
/// \returns A pair `(guard,target)` iff the match is successful.
92+
if_match_resultt match_if(const exprt &expr)
93+
{
94+
const auto &clean_expr = skip_typecast(expr);
95+
if(clean_expr.id() == ID_if)
96+
{
97+
const auto &if_expr = to_if_expr(clean_expr);
98+
if(!match_NULL_or_zero(if_expr.false_case()))
99+
return {};
100+
101+
return {{if_expr.cond(), if_expr.true_case()}};
102+
}
103+
else
104+
return {};
105+
}
106+
107+
/// Pattern match the given expression for a guarded pattern
108+
/// and builds guarded_slice expression as follows:
109+
///
110+
/// ```
111+
/// case expr of
112+
/// | guard ? target : NULL ->
113+
/// {guard,
114+
/// target,
115+
/// address_of{target},
116+
/// size_of_expr{target}}
117+
/// | __CPROVER_POINTER_OBJECT(guard ? target : NULL) ->
118+
/// {guard,
119+
/// __CPROVER_POINTER_OBJECT(target),
120+
/// address_of{target-offset(target)},
121+
/// object_size{target}}
122+
/// | other ->
123+
/// {true,
124+
/// expr,
125+
/// address_of{expr},
126+
/// object_size{expr}}
127+
/// ```
128+
static const guarded_slicet
129+
normalize_to_guarded_slice(const exprt &expr, const namespacet &ns)
130+
{
131+
if(expr.id() == ID_pointer_object)
132+
{
133+
if_match_resultt match_opt = match_if(expr.operands().at(0));
134+
if(match_opt.has_value())
135+
{
136+
const auto &match = match_opt.value();
137+
const auto &new_expr = pointer_object(match.second);
138+
const auto slice = normalize_to_slice(new_expr, ns);
139+
return {match.first, new_expr, slice.first, slice.second};
140+
}
141+
else
142+
{
143+
const auto slice = normalize_to_slice(expr, ns);
144+
return {true_exprt{}, expr, slice.first, slice.second};
145+
}
146+
}
147+
148+
if_match_resultt match_opt = match_if(expr);
149+
if(match_opt.has_value())
150+
{
151+
const auto &match = match_opt.value();
152+
const auto slice = normalize_to_slice(match.second, ns);
153+
return {match.first, match.second, slice.first, slice.second};
154+
}
155+
else
156+
{
157+
const auto slice = normalize_to_slice(expr, ns);
158+
return {true_exprt{}, expr, slice.first, slice.second};
159+
}
160+
}
161+
55162
const symbolt assigns_clauset::conditional_address_ranget::generate_new_symbol(
56163
const std::string &prefix,
57164
const typet &type,
@@ -71,15 +178,19 @@ assigns_clauset::conditional_address_ranget::conditional_address_ranget(
71178
: source_expr(expr),
72179
location(expr.source_location()),
73180
parent(parent),
74-
slice(normalize_to_slice(expr, parent.ns)),
181+
guarded_slice(normalize_to_guarded_slice(expr, parent.ns)),
75182
validity_condition_var(
76183
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())
184+
lower_bound_address_var(generate_new_symbol(
185+
"__car_lb",
186+
guarded_slice.start_adress.type(),
187+
location)
188+
.symbol_expr()),
189+
upper_bound_address_var(generate_new_symbol(
190+
"__car_ub",
191+
guarded_slice.start_adress.type(),
192+
location)
193+
.symbol_expr())
83194
{
84195
}
85196

@@ -93,6 +204,17 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
93204
source_locationt location_no_checks = location;
94205
disable_pointer_checks(location_no_checks);
95206

207+
null_message_handlert null_handler;
208+
209+
// clean up side effects from the guard expression
210+
cleanert cleaner(parent.symbol_table, null_handler);
211+
exprt clean_guard(guarded_slice.guard);
212+
const auto &mode = parent.symbol_table.lookup_ref(parent.function_name).mode;
213+
cleaner.clean(clean_guard, instructions, mode);
214+
215+
// we want checks on the guard because it is user code
216+
clean_guard.add_source_location() = location;
217+
96218
instructions.add(
97219
goto_programt::make_decl(validity_condition_var, location_no_checks));
98220
instructions.add(
@@ -102,28 +224,30 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
102224

103225
instructions.add(goto_programt::make_assignment(
104226
lower_bound_address_var,
105-
null_pointer_exprt{to_pointer_type(slice.first.type())},
227+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
106228
location_no_checks));
107229
instructions.add(goto_programt::make_assignment(
108230
upper_bound_address_var,
109-
null_pointer_exprt{to_pointer_type(slice.first.type())},
231+
null_pointer_exprt{to_pointer_type(guarded_slice.start_adress.type())},
110232
location_no_checks));
111233

112234
goto_programt skip_program;
113235
const auto skip_target =
114236
skip_program.add(goto_programt::make_skip(location_no_checks));
115237

116238
const auto validity_check_expr =
117-
and_exprt{all_dereferences_are_valid(source_expr, parent.ns),
118-
w_ok_exprt{slice.first, slice.second}};
239+
and_exprt{clean_guard,
240+
all_dereferences_are_valid(guarded_slice.expr, parent.ns),
241+
w_ok_exprt{guarded_slice.start_adress, guarded_slice.size}};
242+
119243
instructions.add(goto_programt::make_assignment(
120244
validity_condition_var, validity_check_expr, location_no_checks));
121245

122246
instructions.add(goto_programt::make_goto(
123247
skip_target, not_exprt{validity_condition_var}, location_no_checks));
124248

125249
instructions.add(goto_programt::make_assignment(
126-
lower_bound_address_var, slice.first, location_no_checks));
250+
lower_bound_address_var, guarded_slice.start_adress, location_no_checks));
127251

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

134258
instructions.add(goto_programt::make_assignment(
135259
upper_bound_address_var,
136-
plus_exprt{slice.first, slice.second},
260+
plus_exprt{guarded_slice.start_adress, guarded_slice.size},
137261
location_overflow_check));
138262
instructions.destructive_append(skip_program);
139263

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)