Skip to content

Commit 179c18f

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 179c18f

File tree

5 files changed

+54
-88
lines changed

5 files changed

+54
-88
lines changed

regression/contracts/assigns_validity_pointer_04/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz
3+
--enforce-contract foo --replace-call-with-contract bar --replace-call-with-contract baz _ --pointer-primitive-check
44
^EXIT=0$
55
^SIGNAL=0$
66
^VERIFICATION SUCCESSFUL$

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: 39 additions & 73 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+
: address(address_of_exprt(normalize(expr))),
26+
expr(expr),
27+
id(expr.id()),
28+
parent(parent)
2629
{
2730
}
2831

@@ -38,39 +41,40 @@ 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 address_of_exprt &lhs_address) const
4246
{
4347
exprt::operandst condition;
44-
const auto lhs_ptr =
45-
(lhs.id() == ID_address_of) ? lhs : address_of_exprt(lhs);
4648

4749
// __CPROVER_w_ok(target, sizeof(target))
4850
condition.push_back(w_ok_exprt(
49-
expr, size_of_expr(dereference_exprt(expr).type(), parent.ns).value()));
51+
address,
52+
size_of_expr(dereference_exprt(address).type(), parent.ns).value()));
5053

5154
// __CPROVER_same_object(lhs, target)
52-
condition.push_back(same_object(lhs_ptr, expr));
55+
condition.push_back(same_object(lhs_address, address));
5356

5457
// If assigns target was a dereference, comparing objects is enough
5558
if(id == ID_dereference)
5659
{
5760
return conjunction(condition);
5861
}
5962

60-
const auto lhs_offset = pointer_offset(lhs_ptr);
61-
const auto own_offset = pointer_offset(expr);
63+
const auto lhs_offset = pointer_offset(lhs_address);
64+
const auto own_offset = pointer_offset(address);
6265

6366
// __CPROVER_offset(lhs) >= __CPROVER_offset(target)
6467
condition.push_back(binary_relation_exprt(lhs_offset, ID_ge, own_offset));
6568

6669
const auto lhs_region = plus_exprt(
6770
typecast_exprt::conditional_cast(
68-
size_of_expr(lhs.type(), parent.ns).value(), lhs_offset.type()),
71+
size_of_expr(lhs_address.object().type(), parent.ns).value(),
72+
lhs_offset.type()),
6973
lhs_offset);
7074

7175
const exprt own_region = plus_exprt(
7276
typecast_exprt::conditional_cast(
73-
size_of_expr(dereference_exprt(expr).type(), parent.ns).value(),
77+
size_of_expr(address.object().type(), parent.ns).value(),
7478
own_offset.type()),
7579
own_offset);
7680

@@ -81,12 +85,6 @@ exprt assigns_clauset::targett::generate_alias_check(const exprt &lhs) const
8185
return conjunction(condition);
8286
}
8387

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-
9088
assigns_clauset::assigns_clauset(
9189
const exprt &expr,
9290
const messaget &log,
@@ -101,8 +99,7 @@ assigns_clauset::assigns_clauset(
10199

102100
void assigns_clauset::add_target(const exprt &target_expr)
103101
{
104-
auto insertion_succeeded =
105-
targets.emplace(*this, targett::normalize(target_expr)).second;
102+
auto insertion_succeeded = targets.emplace(*this, target_expr).second;
106103

107104
if(!insertion_succeeded)
108105
{
@@ -117,87 +114,56 @@ goto_programt assigns_clauset::generate_havoc_code() const
117114
{
118115
modifiest modifies;
119116
for(const auto &target : targets)
120-
modifies.insert(to_address_of_expr(target.expr).object());
117+
modifies.insert(target.address.object());
121118

122119
goto_programt havoc_statements;
123120
append_havoc_code(expr.source_location(), modifies, havoc_statements);
124121
return havoc_statements;
125122
}
126123

127-
exprt assigns_clauset::generate_alias_check(const exprt &lhs) const
124+
exprt assigns_clauset::generate_containment_check(const exprt &lhs) const
128125
{
129126
// If write set is empty, no assignment is allowed.
130127
if(targets.empty())
131-
{
132128
return false_exprt();
133-
}
129+
130+
const auto lhs_address = address_of_exprt(targett::normalize(lhs));
134131

135132
exprt::operandst condition;
136133
for(const auto &target : targets)
137134
{
138-
condition.push_back(target.generate_alias_check(lhs));
135+
condition.push_back(target.generate_containment_check(lhs_address));
139136
}
140137
return disjunction(condition);
141138
}
142139

143-
exprt assigns_clauset::generate_compatibility_check(
144-
const assigns_clauset &called_assigns) const
140+
exprt assigns_clauset::generate_subset_check(
141+
const assigns_clauset &subassigns) const
145142
{
146-
if(called_assigns.targets.empty())
147-
{
143+
if(subassigns.targets.empty())
148144
return true_exprt();
149-
}
150145

151-
bool first_clause = true;
152146
exprt result = true_exprt();
153-
for(const auto &called_target : called_assigns.targets)
147+
for(const auto &subtarget : subassigns.targets)
154148
{
155-
bool first_iter = true;
156-
exprt current_target_compatible = false_exprt();
149+
// TODO: Optimize the implication generated due to the validity check.
150+
// In some cases, e.g. when `subtarget` is known to be `NULL`,
151+
// the implication can be skipped entirely. See #6105 for more details.
152+
auto validity_check =
153+
w_ok_exprt(subtarget.address, from_integer(0, unsigned_int_type()));
154+
155+
exprt::operandst current_subtarget_found_conditions;
157156
for(const auto &target : targets)
158157
{
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);
158+
current_subtarget_found_conditions.push_back(
159+
target.generate_containment_check(subtarget.address));
200160
}
161+
162+
auto current_subtarget_found = or_exprt(
163+
not_exprt(validity_check),
164+
disjunction(current_subtarget_found_conditions));
165+
166+
result = and_exprt(result, current_subtarget_found);
201167
}
202168

203169
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 address_of_exprt &) const;
3534

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

49-
const exprt expr;
48+
const address_of_exprt address;
49+
const exprt &expr;
5050
const irep_idt &id;
5151
const assigns_clauset &parent;
5252
};
@@ -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: 8 additions & 8 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");
@@ -762,8 +762,8 @@ void code_contractst::instrument_call_statement(
762762
to_symbol_expr(instruction_iterator->call_lhs()).get_identifier()) ==
763763
freely_assignable_symbols.end())
764764
{
765-
const auto alias_expr =
766-
assigns_clause.generate_alias_check(instruction_iterator->call_lhs());
765+
const auto alias_expr = assigns_clause.generate_containment_check(
766+
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)