Skip to content

Commit d4e61f8

Browse files
committed
Byte-extract lowering: do not blindly use offsets
When unpacking an array we must not create 0 bytes up until an offset when the source array can never be as large. Doing so when within a struct would spuriously produce a larger member. The test case highlighted a problem in the SMT2 back-end: the first operand of a struct was not correctly flattened and instead used default expression conversion (which is only ok when the array theory is never being used). To avoid this problem creeping in again, factor out the operand conversion into a lambda and invoke that lambda each time. Fixes: #7654
1 parent 5cd2d1b commit d4e61f8

File tree

5 files changed

+86
-47
lines changed

5 files changed

+86
-47
lines changed

regression/cbmc/byte_extract2/main.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <assert.h>
2+
3+
#define SIZE 1
4+
5+
struct s
6+
{
7+
char a1[1];
8+
unsigned int a2_len;
9+
char a2[SIZE];
10+
};
11+
12+
void main()
13+
{
14+
struct s s1;
15+
char buf[SIZE];
16+
17+
__CPROVER_assume(s1.a2_len <= SIZE);
18+
__CPROVER_assume(s1.a2_len >= SIZE);
19+
char src_n[s1.a2_len];
20+
__CPROVER_array_copy(src_n, s1.a2);
21+
assert(src_n[0] == s1.a2[0]);
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE broken-cprover-smt-backend
2+
main.c
3+
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^warning: ignoring
9+
--
10+
The CPROVER SMT2 back-end does not support quantifiers, which are brought into
11+
this example via an array comprehension.

src/solvers/smt2/smt2_conv.cpp

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,49 +3280,37 @@ void smt2_convt::convert_struct(const struct_exprt &expr)
32803280
}
32813281
else
32823282
{
3283-
if(components.size()==1)
3284-
{
3285-
const exprt &op = expr.op0();
3283+
auto convert_operand = [this](const exprt &op) {
32863284
// may need to flatten array-theory arrays in there
32873285
if(op.type().id() == ID_array && use_array_theory(op))
32883286
flatten_array(op);
32893287
else if(op.type().id() == ID_bool)
32903288
flatten2bv(op);
32913289
else
32923290
convert_expr(op);
3293-
}
3294-
else
3291+
};
3292+
3293+
// SMT-LIB 2 concat is binary only
3294+
std::size_t n_concat = 0;
3295+
for(std::size_t i = components.size(); i > 1; i--)
32953296
{
3296-
// SMT-LIB 2 concat is binary only
3297-
std::size_t n_concat = 0;
3298-
for(std::size_t i=components.size(); i>1; i--)
3297+
if(is_zero_width(components[i - 1].type(), ns))
3298+
continue;
3299+
else if(i > 2 || !is_zero_width(components[0].type(), ns))
32993300
{
3300-
if(is_zero_width(components[i - 1].type(), ns))
3301-
continue;
3302-
else if(i > 2 || !is_zero_width(components[0].type(), ns))
3303-
{
3304-
++n_concat;
3305-
out << "(concat ";
3306-
}
3307-
3308-
exprt op=expr.operands()[i-1];
3309-
3310-
// may need to flatten array-theory arrays in there
3311-
if(op.type().id() == ID_array && use_array_theory(op))
3312-
flatten_array(op);
3313-
else if(op.type().id() == ID_bool)
3314-
flatten2bv(op);
3315-
else
3316-
convert_expr(op);
3317-
3318-
out << " ";
3301+
++n_concat;
3302+
out << "(concat ";
33193303
}
33203304

3321-
if(!is_zero_width(components[0].type(), ns))
3322-
convert_expr(expr.op0());
3305+
convert_operand(expr.operands()[i - 1]);
33233306

3324-
out << std::string(n_concat, ')');
3307+
out << " ";
33253308
}
3309+
3310+
if(!is_zero_width(components[0].type(), ns))
3311+
convert_operand(expr.op0());
3312+
3313+
out << std::string(n_concat, ')');
33263314
}
33273315
}
33283316

src/solvers/smt2/smt2_parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,7 +1207,7 @@ void smt2_parsert::setup_expressions()
12071207

12081208
// add the widths
12091209
auto op_width = make_range(op).map(
1210-
[](const exprt &o) { return to_unsignedbv_type(o.type()).get_width(); });
1210+
[](const exprt &o) { return to_bitvector_type(o.type()).get_width(); });
12111211

12121212
const std::size_t total_width =
12131213
std::accumulate(op_width.begin(), op_width.end(), 0);

src/util/lower_byte_operators.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -522,9 +522,10 @@ static exprt unpack_array_vector_no_known_bounds(
522522

523523
/// Rewrite an array or vector into its individual bytes.
524524
/// \param src: array/vector to unpack
525-
/// \param src_size: array/vector size; if not a constant and thus not set,
526-
/// \p max_bytes must be a known constant value to build an array expression,
527-
/// otherwise we fall back to building and array comprehension
525+
/// \param src_size: number of elements in \p src, if the array/vector size is
526+
/// constant; if the array/vector size is not constant (and this argument thus
527+
/// not set), \p max_bytes needs to be a known constant value to build an
528+
/// array expression, else we fall back to building an array comprehension
528529
/// \param element_bits: bit width of array/vector elements
529530
/// \param little_endian: true, iff assumed endianness is little-endian
530531
/// \param offset_bytes: if set, bytes prior to this offset will be filled
@@ -576,7 +577,9 @@ static exprt unpack_array_vector(
576577
first_element = *offset_bytes / el_bytes;
577578
// insert offset_bytes-many nil bytes into the output array
578579
byte_operands.resize(
579-
numeric_cast_v<std::size_t>(*offset_bytes - (*offset_bytes % el_bytes)),
580+
numeric_cast_v<std::size_t>(std::min(
581+
*offset_bytes - (*offset_bytes % el_bytes),
582+
*num_elements * el_bytes)),
580583
from_integer(0, bv_typet{bits_per_byte}));
581584
}
582585
}
@@ -777,24 +780,39 @@ static array_exprt unpack_struct(
777780
!bit_fields.has_value(),
778781
"all preceding members should have been processed");
779782

780-
exprt sub = unpack_rec(
781-
member,
782-
little_endian,
783-
offset_in_member,
784-
max_bytes_left,
785-
bits_per_byte,
786-
ns,
787-
true);
783+
if(
784+
component_bits.has_value() && offset_in_member.has_value() &&
785+
*offset_in_member * bits_per_byte >= *component_bits)
786+
{
787+
// we won't actually need this component, fill in zeros instead of
788+
// computing an unpacking
789+
byte_operands.resize(
790+
byte_operands.size() +
791+
numeric_cast_v<std::size_t>(*component_bits / bits_per_byte),
792+
from_integer(0, bv_typet{bits_per_byte}));
793+
}
794+
else
795+
{
796+
exprt sub = unpack_rec(
797+
member,
798+
little_endian,
799+
offset_in_member,
800+
max_bytes_left,
801+
bits_per_byte,
802+
ns,
803+
true);
788804

789-
byte_operands.insert(
790-
byte_operands.end(),
791-
std::make_move_iterator(sub.operands().begin()),
792-
std::make_move_iterator(sub.operands().end()));
805+
byte_operands.insert(
806+
byte_operands.end(),
807+
std::make_move_iterator(sub.operands().begin()),
808+
std::make_move_iterator(sub.operands().end()));
809+
}
793810

794811
if(component_bits.has_value())
795812
member_offset_bits += *component_bits;
796813
}
797814

815+
// any remaining bit fields?
798816
if(bit_fields.has_value())
799817
{
800818
process_bit_fields(

0 commit comments

Comments
 (0)