Skip to content

Commit 66fe11e

Browse files
Fix sub-byte bit placement in boolbv convert_byte_update
When byte_update writes a sub-byte value (e.g., a 1-bit bitvector) at a byte-aligned offset, the value must be placed at the high end of the affected byte, matching the semantics of lower_byte_update which concatenates {value, remaining_low_bits}. The boolbv convert_byte_update was placing the value at bit 0 (the low end) instead. For little-endian, this means the value was written to the LSB instead of the MSB of the byte. For big-endian, the endianness map already reverses bits so that bit 0 maps to the MSB, making the existing code accidentally correct. Generalise the fix to handle any non-byte-aligned update width: the trailing partial byte's bits are shifted to the high end for little-endian, matching lower_byte_update's padding semantics. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent cfd7295 commit 66fe11e

5 files changed

Lines changed: 59 additions & 3 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
int main()
2+
{
3+
unsigned x;
4+
__CPROVER_bitvector[1] *p = (__CPROVER_bitvector[1] *)&x;
5+
*p = 0;
6+
__CPROVER_assert((x & 0x80u) == 0u, "MSB of byte 0 is cleared");
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
symbolic.c
3+
--little-endian --no-simplify
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^warning: ignoring
9+
--
10+
Same as test.desc but with a symbolic (constrained-to-0) byte offset,
11+
exercising the variable-offset branch of convert_byte_update.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
int main()
2+
{
3+
unsigned x;
4+
unsigned offset;
5+
__CPROVER_assume(offset == 0);
6+
__CPROVER_bitvector[1] *p = (__CPROVER_bitvector[1] *)((char *)&x + offset);
7+
*p = 0;
8+
__CPROVER_assert((x & 0x80u) == 0u, "MSB of byte 0 is cleared");
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--little-endian --no-simplify
4+
^VERIFICATION SUCCESSFUL$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
^warning: ignoring
9+
--
10+
Writing a 1-bit zero through a __CPROVER_bitvector[1] pointer must
11+
clear the MSB of the first byte, not the LSB. The --no-simplify flag
12+
is needed to exercise the boolbv convert_byte_update path directly.
13+
This test uses a constant offset (byte 0).

src/solvers/flattening/boolbv_byte_update.cpp

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ bvt boolbvt::convert_byte_update(const byte_update_exprt &expr)
4141
if(update_width>bv.size())
4242
update_width=bv.size();
4343

44+
// When the update value is not a multiple of the byte width,
45+
// lower_byte_update places the value at the high end of the last partial
46+
// byte (concatenating {value, remaining_low_bits}). For little-endian the
47+
// high end is at higher bit indices, so we shift the trailing partial
48+
// byte's bits up. For big-endian the endianness map already places bit 0
49+
// at the MSB, so no shift is needed.
50+
const std::size_t tail_bits = update_width % byte_width;
51+
const std::size_t tail_shift =
52+
little_endian && tail_bits != 0 ? byte_width - tail_bits : 0;
53+
54+
// For a given bit index i (0-based within the update value), compute the
55+
// additional offset to place trailing partial-byte bits at the high end.
56+
auto bit_shift = [&](std::size_t i) -> std::size_t {
57+
return (tail_shift != 0 && i >= update_width - tail_bits) ? tail_shift : 0;
58+
};
59+
4460
// see if the byte number is constant
4561

4662
const auto index = numeric_cast<mp_integer>(offset_expr);
@@ -62,7 +78,7 @@ bvt boolbvt::convert_byte_update(const byte_update_exprt &expr)
6278

6379
for(std::size_t i = 0; i < update_width; i++)
6480
{
65-
size_t index_op = map_op.map_bit(offset_i + i);
81+
size_t index_op = map_op.map_bit(offset_i + bit_shift(i) + i);
6682
size_t index_value = map_value.map_bit(i);
6783

6884
INVARIANT(
@@ -90,9 +106,9 @@ bvt boolbvt::convert_byte_update(const byte_update_exprt &expr)
90106
endianness_mapt map_value = endianness_map(value.type(), little_endian);
91107

92108
for(std::size_t bit=0; bit<update_width; bit++)
93-
if(offset+bit<bv.size())
109+
if(offset + bit_shift(bit) + bit < bv.size())
94110
{
95-
std::size_t bv_o=map_op.map_bit(offset+bit);
111+
std::size_t bv_o = map_op.map_bit(offset + bit_shift(bit) + bit);
96112
std::size_t value_bv_o=map_value.map_bit(bit);
97113

98114
bv[bv_o]=prop.lselect(equal, value_bv[value_bv_o], bv[bv_o]);

0 commit comments

Comments
 (0)