Skip to content

Commit 2834e84

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 2834e84

File tree

5 files changed

+61
-90
lines changed

5 files changed

+61
-90
lines changed

regression/contracts/assigns_validity_pointer_04/test.desc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
CORE
1+
KNOWNBUG
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$
@@ -13,8 +13,13 @@ ASSUME .*::tmp_if_expr
1313
ASSUME \*z = 7
1414
--
1515
--
16-
Verification:
1716
This test checks support for a malloced pointer that is assigned to by
1817
a function (bar and baz). Both functions bar and baz are being replaced by
1918
their function contracts, while the calling function foo is being checked
2019
(by enforcing it's function contracts).
20+
21+
BUG: `z` is being assigned to in `foo`, but is not in `foo`s assigns clause!
22+
This test is expected to pass but it should not.
23+
It somehow used to (and still does on *nix), but that seems buggy.
24+
Most likely the bug is related to `freely_assignable_symbols`,
25+
which would be properly fixed in a subsequent PR.

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)