Skip to content

Commit cafe331

Browse files
Merge pull request #7787 from thomasspriggs/tas/smt_struct_with_member
Add incremental SMT support for struct with and member expressions
2 parents ba0c1bb + c4ccf66 commit cafe331

File tree

11 files changed

+478
-56
lines changed

11 files changed

+478
-56
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <assert.h>
2+
3+
#define ARRAY_SIZE 10000
4+
5+
int main()
6+
{
7+
struct my_structt
8+
{
9+
int eggs;
10+
int ham;
11+
};
12+
struct my_structt struct_array[ARRAY_SIZE];
13+
int x;
14+
__CPROVER_assume(x > 0 && x < ARRAY_SIZE);
15+
struct_array[x].eggs = 3;
16+
assert(struct_array[x].eggs + struct_array[x].ham != 10);
17+
assert(struct_array[x].eggs + struct_array[x].ham != 11);
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
large_array_of_struct_nondet_index.c
3+
--trace
4+
Passing problem to incremental SMT2 solving
5+
^EXIT=10$
6+
^SIGNAL=0$
7+
line 16 assertion struct_array\[x\]\.eggs \+ struct_array\[x\]\.ham != 10\: FAILURE
8+
line 17 assertion struct_array\[x\]\.eggs \+ struct_array\[x\]\.ham != 11\: FAILURE
9+
\{\s*\.eggs=\d+,\s*\.ham=7\s*\}
10+
\{\s*\.eggs=\d+,\s*\.ham=8\s*\}
11+
x=\d{1,4}\s
12+
struct_array\[\(signed (long )+int\)x\]\.eggs=3
13+
--
14+
--
15+
This test covers support for examples with large arrays of structs using nondet
16+
indexes including trace generation. This combination of features is chosen in
17+
order to avoid array cell sensitivity or struct field sensitivity simplifying
18+
away the relevant `member_exprt` and `with_exprt` expressions.

regression/cbmc/array-cell-sensitivity9/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE
1+
CORE new-smt-backend
22
test.c
33
--show-vcc
44
main::1::array!0@1#2\[\[0\]\]..x =

regression/cbmc/array-cell-sensitivity9/test_execution.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE
1+
CORE new-smt-backend
22
test.c
33

44
^VERIFICATION SUCCESSFUL$

regression/cbmc/field-sensitivity-trace-wrong-counterexample-1/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE
1+
CORE new-smt-backend
22
test.c
33
--trace
44
^EXIT=10$

regression/cbmc/simplify_singleton_interval_7690/test_smt2.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CORE smt-backend
1+
CORE smt-backend new-smt-backend
22
singleton_interval_in_assume_7690.c
33
--pointer-check
44
^\[stk_push\.pointer_dereference\.17] line \d+ dereference failure: pointer outside object bounds in stk-\>elems\[\(signed (long|long long) int\)stk-\>top\]: SUCCESS$

src/solvers/smt2_incremental/encoding/struct_encoding.cpp

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@
55
#include <util/bitvector_expr.h>
66
#include <util/bitvector_types.h>
77
#include <util/make_unique.h>
8+
#include <util/namespace.h>
9+
#include <util/range.h>
10+
#include <util/simplify_expr.h>
811

912
#include <solvers/flattening/boolbv_width.h>
1013

14+
#include <algorithm>
15+
#include <numeric>
1116
#include <queue>
1217

1318
struct_encodingt::struct_encodingt(const namespacet &ns)
14-
: boolbv_width{util_make_unique<boolbv_widtht>(ns)}
19+
: boolbv_width{util_make_unique<boolbv_widtht>(ns)}, ns{ns}
1520
{
1621
}
1722

1823
struct_encodingt::struct_encodingt(const struct_encodingt &other)
19-
: boolbv_width{util_make_unique<boolbv_widtht>(*other.boolbv_width)}
24+
: boolbv_width{util_make_unique<boolbv_widtht>(*other.boolbv_width)},
25+
ns{other.ns}
2026
{
2127
}
2228

@@ -45,6 +51,57 @@ typet struct_encodingt::encode(typet type) const
4551
return type;
4652
}
4753

54+
/// \brief Extracts the component/field names and new values from a `with_exprt`
55+
/// which expresses a new struct value where one or more members of a
56+
/// struct have had their values substituted with new values.
57+
/// \note This is implemented using direct access to the operands and other
58+
/// underlying irept interfaces, because the interface for `with_exprt`
59+
/// only supports a single `where` / `new_value` pair and does not
60+
/// support extracting the name from the `where` operand.
61+
static std::unordered_map<irep_idt, exprt>
62+
extricate_updates(const with_exprt &struct_expr)
63+
{
64+
std::unordered_map<irep_idt, exprt> pairs;
65+
auto current_operand = struct_expr.operands().begin();
66+
// Skip the struct being updated in order to begin with the pairs.
67+
current_operand++;
68+
while(current_operand != struct_expr.operands().end())
69+
{
70+
INVARIANT(
71+
current_operand->id() == ID_member_name,
72+
"operand is expected to be the name of a member");
73+
auto name = current_operand->find(ID_component_name).id();
74+
++current_operand;
75+
INVARIANT(
76+
current_operand != struct_expr.operands().end(),
77+
"every name is expected to be followed by a paired value");
78+
pairs[name] = *current_operand;
79+
++current_operand;
80+
}
81+
POSTCONDITION(!pairs.empty());
82+
return pairs;
83+
}
84+
85+
static exprt encode(const with_exprt &with, const namespacet &ns)
86+
{
87+
const auto tag_type = type_checked_cast<struct_tag_typet>(with.type());
88+
const auto struct_type =
89+
type_checked_cast<struct_typet>(ns.follow(with.type()));
90+
const auto updates = extricate_updates(with);
91+
const auto components =
92+
make_range(struct_type.components())
93+
.map([&](const struct_union_typet::componentt &component) -> exprt {
94+
const auto &update = updates.find(component.get_name());
95+
if(update != updates.end())
96+
return update->second;
97+
else
98+
return member_exprt{
99+
with.old(), component.get_name(), component.type()};
100+
})
101+
.collect<exprt::operandst>();
102+
return struct_exprt{components, tag_type};
103+
}
104+
48105
static exprt encode(const struct_exprt &struct_expr)
49106
{
50107
INVARIANT(
@@ -55,6 +112,49 @@ static exprt encode(const struct_exprt &struct_expr)
55112
return concatenation_exprt{struct_expr.operands(), struct_expr.type()};
56113
}
57114

115+
static std::size_t count_trailing_bit_width(
116+
const struct_typet &type,
117+
const irep_idt name,
118+
const boolbv_widtht &boolbv_width)
119+
{
120+
auto member_component_rit = std::find_if(
121+
type.components().rbegin(),
122+
type.components().rend(),
123+
[&](const struct_union_typet::componentt &component) {
124+
return component.get_name() == name;
125+
});
126+
INVARIANT(
127+
member_component_rit != type.components().rend(),
128+
"Definition of struct type should include named component.");
129+
const auto trailing_widths =
130+
make_range(type.components().rbegin(), member_component_rit)
131+
.map([&](const struct_union_typet::componentt &component) -> std::size_t {
132+
return boolbv_width(component.type());
133+
});
134+
return std::accumulate(
135+
trailing_widths.begin(), trailing_widths.end(), std::size_t{0});
136+
}
137+
138+
/// The member expression selects the value of a field from a struct. The
139+
/// struct is encoded as a single bitvector where the first field is stored
140+
/// in the highest bits. The second field is stored in the next highest set of
141+
/// bits and so on. As offsets are indexed from the lowest bit, any field can be
142+
/// selected by extracting the range of bits starting from an offset based on
143+
/// the combined width of the fields which follow the field being selected.
144+
exprt struct_encodingt::encode_member(const member_exprt &member_expr) const
145+
{
146+
const auto &struct_type = type_checked_cast<struct_typet>(
147+
ns.get().follow(member_expr.compound().type()));
148+
const std::size_t offset_bits = count_trailing_bit_width(
149+
struct_type, member_expr.get_component_name(), *boolbv_width);
150+
const auto member_bits_width = (*boolbv_width)(member_expr.type());
151+
return extractbits_exprt{
152+
member_expr.compound(),
153+
offset_bits + member_bits_width - 1,
154+
offset_bits,
155+
member_expr.type()};
156+
}
157+
58158
exprt struct_encodingt::encode(exprt expr) const
59159
{
60160
std::queue<exprt *> work_queue;
@@ -63,11 +163,46 @@ exprt struct_encodingt::encode(exprt expr) const
63163
{
64164
exprt &current = *work_queue.front();
65165
work_queue.pop();
166+
// Note that "with" expressions are handled before type encoding in order to
167+
// facilitate checking that they are applied to structs rather than arrays.
168+
if(const auto with_expr = expr_try_dynamic_cast<with_exprt>(current))
169+
if(can_cast_type<struct_tag_typet>(current.type()))
170+
current = ::encode(*with_expr, ns);
66171
current.type() = encode(current.type());
67172
if(const auto struct_expr = expr_try_dynamic_cast<struct_exprt>(current))
68173
current = ::encode(*struct_expr);
174+
if(const auto member_expr = expr_try_dynamic_cast<member_exprt>(current))
175+
current = encode_member(*member_expr);
69176
for(auto &operand : current.operands())
70177
work_queue.push(&operand);
71178
}
72179
return expr;
73180
}
181+
182+
exprt struct_encodingt::decode(
183+
const exprt &encoded,
184+
const struct_tag_typet &original_type) const
185+
{
186+
// The algorithm below works by extracting each of the separate fields from
187+
// the combined encoded value using a `member_exprt` which is itself encoded
188+
// into a `bit_extract_exprt`. These separated fields can then be combined
189+
// using a `struct_exprt`. This is expected to duplicate the input encoded
190+
// expression for each of the fields. However for the case where the input
191+
// encoded expression is a `constant_exprt`, expression simplification will be
192+
// able simplify away the duplicated portions of the constant and the bit
193+
// extraction expressions. This yields a clean struct expression where each
194+
// field is a separate constant containing the data solely relevant to that
195+
// field.
196+
INVARIANT(
197+
can_cast_type<bv_typet>(encoded.type()),
198+
"Structs are expected to be encoded into bit vectors.");
199+
const struct_typet definition = ns.get().follow_tag(original_type);
200+
exprt::operandst encoded_fields;
201+
for(const auto &component : definition.components())
202+
{
203+
encoded_fields.push_back(typecast_exprt::conditional_cast(
204+
encode(member_exprt{typecast_exprt{encoded, original_type}, component}),
205+
component.type()));
206+
}
207+
return simplify_expr(struct_exprt{encoded_fields, original_type}, ns);
208+
}

src/solvers/smt2_incremental/encoding/struct_encoding.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
class namespacet;
1212
class boolbv_widtht;
13+
class member_exprt;
14+
class struct_tag_typet;
1315

1416
/// Encodes struct types/values into non-struct expressions/types.
1517
class struct_encodingt final
@@ -21,9 +23,16 @@ class struct_encodingt final
2123

2224
typet encode(typet type) const;
2325
exprt encode(exprt expr) const;
26+
/// Reconstructs a struct expression of the \p original_type using the data
27+
/// from the bit vector \p encoded expression.
28+
exprt
29+
decode(const exprt &encoded, const struct_tag_typet &original_type) const;
2430

2531
private:
2632
std::unique_ptr<boolbv_widtht> boolbv_width;
33+
std::reference_wrapper<const namespacet> ns;
34+
35+
exprt encode_member(const member_exprt &member_expr) const;
2736
};
2837

2938
#endif // CPROVER_SOLVERS_SMT2_INCREMENTAL_STRUCT_ENCODING_H

0 commit comments

Comments
 (0)