Skip to content

Commit 0399995

Browse files
committed
Fix alias checks within assigns clause
assigns_clauset was calling `compatible_expression` (not `alias_expression`) on each target, and was thus only comparing the object IDs (not offsets and sizes). We fix this by removing `compatible_expression` entirely and calling `alias_expression` for each from assigns_clauset.
1 parent ddf9a04 commit 0399995

File tree

4 files changed

+47
-82
lines changed

4 files changed

+47
-82
lines changed

regression/contracts/test_aliasing_ensure_indirect/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ main.c
77
\[postcondition.\d+\] file main.c line \d+ Check ensures clause: SUCCESS
88
\[bar.\d+\] line \d+ Check that z is assignable: SUCCESS
99
\[foo.\d+\] line \d+ Check that \*x is assignable: SUCCESS
10-
\[foo.\d+\] line \d+ Check compatibility of assigns clause with the called function: SUCCESS
10+
\[foo.\d+\] line \d+ Check that callee's assigns clause is a subset of caller's: SUCCESS
1111
\[main.assertion.\d+\] line \d+ assertion \!\(n \< 4\): SUCCESS
1212
^VERIFICATION SUCCESSFUL$
1313
--

src/goto-instrument/contracts/assigns.cpp

Lines changed: 34 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ Date: July 2021
2222
assigns_clauset::targett::targett(
2323
const assigns_clauset &parent,
2424
const exprt &expr)
25-
: expr(address_of_exprt(expr)), id(expr.id()), parent(parent)
25+
: expr(expr),
26+
id(expr.id()),
27+
object_expr(address_of_exprt(normalize(expr))),
28+
parent(parent)
2629
{
2730
}
2831

@@ -38,18 +41,20 @@ exprt assigns_clauset::targett::normalize(const exprt &expr)
3841
return to_index_expr(object).array();
3942
}
4043

41-
exprt assigns_clauset::targett::generate_alias_check(const exprt &lhs) const
44+
exprt assigns_clauset::targett::generate_containment_check(
45+
const exprt &lhs) const
4246
{
4347
exprt::operandst condition;
4448
const auto lhs_ptr =
4549
(lhs.id() == ID_address_of) ? lhs : address_of_exprt(lhs);
4650

4751
// __CPROVER_w_ok(target, sizeof(target))
4852
condition.push_back(w_ok_exprt(
49-
expr, size_of_expr(dereference_exprt(expr).type(), parent.ns).value()));
53+
object_expr,
54+
size_of_expr(dereference_exprt(object_expr).type(), parent.ns).value()));
5055

5156
// __CPROVER_same_object(lhs, target)
52-
condition.push_back(same_object(lhs_ptr, expr));
57+
condition.push_back(same_object(lhs_ptr, object_expr));
5358

5459
// If assigns target was a dereference, comparing objects is enough
5560
if(id == ID_dereference)
@@ -58,7 +63,7 @@ exprt assigns_clauset::targett::generate_alias_check(const exprt &lhs) const
5863
}
5964

6065
const auto lhs_offset = pointer_offset(lhs_ptr);
61-
const auto own_offset = pointer_offset(expr);
66+
const auto own_offset = pointer_offset(object_expr);
6267

6368
// __CPROVER_offset(lhs) >= __CPROVER_offset(target)
6469
condition.push_back(binary_relation_exprt(lhs_offset, ID_ge, own_offset));
@@ -70,7 +75,7 @@ exprt assigns_clauset::targett::generate_alias_check(const exprt &lhs) const
7075

7176
const exprt own_region = plus_exprt(
7277
typecast_exprt::conditional_cast(
73-
size_of_expr(dereference_exprt(expr).type(), parent.ns).value(),
78+
size_of_expr(dereference_exprt(object_expr).type(), parent.ns).value(),
7479
own_offset.type()),
7580
own_offset);
7681

@@ -81,12 +86,6 @@ exprt assigns_clauset::targett::generate_alias_check(const exprt &lhs) const
8186
return conjunction(condition);
8287
}
8388

84-
exprt assigns_clauset::targett::generate_compatibility_check(
85-
const assigns_clauset::targett &other_target) const
86-
{
87-
return same_object(other_target.expr, expr);
88-
}
89-
9089
assigns_clauset::assigns_clauset(
9190
const exprt &expr,
9291
const messaget &log,
@@ -101,8 +100,7 @@ assigns_clauset::assigns_clauset(
101100

102101
void assigns_clauset::add_target(const exprt &target_expr)
103102
{
104-
auto insertion_succeeded =
105-
targets.emplace(*this, targett::normalize(target_expr)).second;
103+
auto insertion_succeeded = targets.emplace(*this, target_expr).second;
106104

107105
if(!insertion_succeeded)
108106
{
@@ -117,87 +115,54 @@ goto_programt assigns_clauset::generate_havoc_code() const
117115
{
118116
modifiest modifies;
119117
for(const auto &target : targets)
120-
modifies.insert(to_address_of_expr(target.expr).object());
118+
modifies.insert(target.object_expr.object());
121119

122120
goto_programt havoc_statements;
123121
append_havoc_code(expr.source_location(), modifies, havoc_statements);
124122
return havoc_statements;
125123
}
126124

127-
exprt assigns_clauset::generate_alias_check(const exprt &lhs) const
125+
exprt assigns_clauset::generate_containment_check(const exprt &lhs) const
128126
{
129127
// If write set is empty, no assignment is allowed.
130128
if(targets.empty())
131-
{
132129
return false_exprt();
133-
}
134130

135131
exprt::operandst condition;
136132
for(const auto &target : targets)
137133
{
138-
condition.push_back(target.generate_alias_check(lhs));
134+
condition.push_back(target.generate_containment_check(lhs));
139135
}
140136
return disjunction(condition);
141137
}
142138

143-
exprt assigns_clauset::generate_compatibility_check(
144-
const assigns_clauset &called_assigns) const
139+
exprt assigns_clauset::generate_subset_check(
140+
const assigns_clauset &subassigns) const
145141
{
146-
if(called_assigns.targets.empty())
147-
{
142+
if(subassigns.targets.empty())
148143
return true_exprt();
149-
}
150144

151-
bool first_clause = true;
152145
exprt result = true_exprt();
153-
for(const auto &called_target : called_assigns.targets)
146+
for(const auto &subtarget : subassigns.targets)
154147
{
155-
bool first_iter = true;
156-
exprt current_target_compatible = false_exprt();
148+
// TODO: Optimize the implication generated due to the validity check.
149+
// In some cases, e.g. when `subtarget` is known to be `NULL`,
150+
// the implication can be skipped entirely. See #6105 for more details.
151+
auto validity_check =
152+
w_ok_exprt(subtarget.object_expr, from_integer(0, unsigned_int_type()));
153+
154+
exprt::operandst current_subtarget_found_conditions;
157155
for(const auto &target : targets)
158156
{
159-
if(first_iter)
160-
{
161-
// TODO: Optimize the validation below and remove code duplication
162-
// See GitHub issue #6105 for further details
163-
164-
// Validating the called target through __CPROVER_w_ok() is
165-
// only useful when the called target is a dereference
166-
const auto &called_target_ptr = called_target.expr;
167-
if(
168-
to_address_of_expr(called_target_ptr).object().id() == ID_dereference)
169-
{
170-
// or_exprt is short-circuited, therefore
171-
// target.generate_compatibility_check(*called_target) would not be
172-
// checked on invalid called_targets.
173-
current_target_compatible = or_exprt(
174-
not_exprt(w_ok_exprt(
175-
called_target_ptr, from_integer(0, unsigned_int_type()))),
176-
target.generate_compatibility_check(called_target));
177-
}
178-
else
179-
{
180-
current_target_compatible =
181-
target.generate_compatibility_check(called_target);
182-
}
183-
first_iter = false;
184-
}
185-
else
186-
{
187-
current_target_compatible = or_exprt(
188-
current_target_compatible,
189-
target.generate_compatibility_check(called_target));
190-
}
191-
}
192-
if(first_clause)
193-
{
194-
result = current_target_compatible;
195-
first_clause = false;
196-
}
197-
else
198-
{
199-
result = and_exprt(result, current_target_compatible);
157+
current_subtarget_found_conditions.push_back(
158+
target.generate_containment_check(subtarget.expr));
200159
}
160+
161+
auto current_subtarget_found = or_exprt(
162+
not_exprt(validity_check),
163+
disjunction(current_subtarget_found_conditions));
164+
165+
result = and_exprt(result, current_subtarget_found);
201166
}
202167

203168
return result;

src/goto-instrument/contracts/assigns.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ class assigns_clauset
3030

3131
static exprt normalize(const exprt &);
3232

33-
exprt generate_alias_check(const exprt &) const;
34-
exprt generate_compatibility_check(const targett &) const;
33+
exprt generate_containment_check(const exprt &) const;
3534

3635
bool operator==(const targett &other) const
3736
{
@@ -46,8 +45,9 @@ class assigns_clauset
4645
}
4746
};
4847

49-
const exprt expr;
48+
const exprt &expr;
5049
const irep_idt &id;
50+
address_of_exprt object_expr;
5151
const assigns_clauset &parent;
5252
};
5353

@@ -56,8 +56,8 @@ class assigns_clauset
5656
void add_target(const exprt &);
5757

5858
goto_programt generate_havoc_code() const;
59-
exprt generate_alias_check(const exprt &) const;
60-
exprt generate_compatibility_check(const assigns_clauset &) const;
59+
exprt generate_containment_check(const exprt &) const;
60+
exprt generate_subset_check(const assigns_clauset &) const;
6161

6262
const exprt &expr;
6363
const messaget &log;

src/goto-instrument/contracts/contracts.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ void code_contractst::instrument_assign_statement(
699699
{
700700
goto_programt alias_assertion;
701701
alias_assertion.add(goto_programt::make_assertion(
702-
assigns_clause.generate_alias_check(lhs),
702+
assigns_clause.generate_containment_check(lhs),
703703
instruction_iterator->source_location));
704704
alias_assertion.instructions.back().source_location.set_comment(
705705
"Check that " + from_expr(ns, lhs.id(), lhs) + " is assignable");
@@ -763,7 +763,7 @@ void code_contractst::instrument_call_statement(
763763
freely_assignable_symbols.end())
764764
{
765765
const auto alias_expr =
766-
assigns_clause.generate_alias_check(instruction_iterator->call_lhs());
766+
assigns_clause.generate_containment_check(instruction_iterator->call_lhs());
767767

768768
goto_programt alias_assertion;
769769
alias_assertion.add(goto_programt::make_assertion(
@@ -801,15 +801,15 @@ void code_contractst::instrument_call_statement(
801801
}
802802
replace_formal_params(called_assigns);
803803

804-
// check compatibility of assigns clause with the called function
804+
// check subset relationship of assigns clause for called function
805805
assigns_clauset called_assigns_clause(called_assigns, log, ns);
806-
const auto compatibility_check =
807-
assigns_clause.generate_compatibility_check(called_assigns_clause);
806+
const auto subset_check =
807+
assigns_clause.generate_subset_check(called_assigns_clause);
808808
goto_programt alias_assertion;
809809
alias_assertion.add(goto_programt::make_assertion(
810-
compatibility_check, instruction_iterator->source_location));
810+
subset_check, instruction_iterator->source_location));
811811
alias_assertion.instructions.back().source_location.set_comment(
812-
"Check compatibility of assigns clause with the called function");
812+
"Check that callee's assigns clause is a subset of caller's");
813813
program.insert_before_swap(instruction_iterator, alias_assertion);
814814
++instruction_iterator;
815815
}

0 commit comments

Comments
 (0)