Skip to content

Commit 8cc423c

Browse files
committed
Honour update bound when updating struct members
We must not unconditionally use bytes extracted from the update value when the size of the update value is not constant: the bytes extracted may be out-of-bounds and thus non-deterministic.
1 parent c01d2d5 commit 8cc423c

File tree

2 files changed

+75
-13
lines changed

2 files changed

+75
-13
lines changed

regression/cbmc/byte_update12/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ int main()
1313
__CPROVER_assume(x == sizeof(int));
1414
A[0] = 42;
1515
struct S s;
16+
s.j = 1;
1617
memcpy(&s, A, x);
1718
__CPROVER_assert((s.i & 0xFF) == 42, "lowest byte is 42");
19+
__CPROVER_assert(s.j == 1, "s.j is unaffected by upate");
1820
}

src/solvers/lowering/byte_operators.cpp

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,6 +1751,10 @@ static exprt lower_byte_update_struct(
17511751
const optionalt<exprt> &non_const_update_bound,
17521752
const namespacet &ns)
17531753
{
1754+
const irep_idt extract_opcode = src.id() == ID_byte_update_little_endian
1755+
? ID_byte_extract_little_endian
1756+
: ID_byte_extract_big_endian;
1757+
17541758
exprt::operandst elements;
17551759
elements.reserve(struct_type.components().size());
17561760

@@ -1789,10 +1793,6 @@ static exprt lower_byte_update_struct(
17891793
// indiviual struct members and instead turn the operand-to-be-updated
17901794
// into a byte array, which we know how to update even if the offset is
17911795
// non-constant.
1792-
const irep_idt extract_opcode = src.id() == ID_byte_update_little_endian
1793-
? ID_byte_extract_little_endian
1794-
: ID_byte_extract_big_endian;
1795-
17961796
auto src_size_opt = size_of_expr(src.type(), ns);
17971797
CHECK_RETURN(src_size_opt.has_value());
17981798

@@ -1816,6 +1816,10 @@ static exprt lower_byte_update_struct(
18161816
ns);
18171817
}
18181818

1819+
// We now need to figure out how many bytes to consume from the update
1820+
// value. If the size of the update is unknown, then we need to leave some
1821+
// of this work to a back-end solver via the non_const_update_bound branch
1822+
// below.
18191823
mp_integer update_elements = (*element_width + 7) / 8;
18201824
std::size_t first = 0;
18211825
if(*offset_bytes < 0)
@@ -1835,13 +1839,50 @@ static exprt lower_byte_update_struct(
18351839
"members before the update should be handled above");
18361840
}
18371841

1842+
// Now that we have an idea on how many bytes we need from the update value,
1843+
// determine the exact range [first, end) in the update value, and create
1844+
// that sequence of bytes (via instantiate_byte_array).
18381845
std::size_t end = first + numeric_cast_v<std::size_t>(update_elements);
18391846
if(value_as_byte_array.id() == ID_array)
18401847
end = std::min(end, value_as_byte_array.operands().size());
18411848
exprt::operandst update_values =
18421849
instantiate_byte_array(value_as_byte_array, first, end, ns);
18431850
const std::size_t update_size = update_values.size();
18441851

1852+
// With an upper bound on the size of the update established, construct the
1853+
// actual update expression. If the exact size of the update is unknown,
1854+
// make the size expression conditional.
1855+
exprt update_size_expr = from_integer(update_size, size_type());
1856+
array_exprt update_expr{std::move(update_values),
1857+
array_typet{bv_typet{8}, update_size_expr}};
1858+
optionalt<exprt> member_update_bound;
1859+
if(non_const_update_bound.has_value())
1860+
{
1861+
// The size of the update is not constant, and thus is to be symbolically
1862+
// bound; first see how many bytes we have left in the update:
1863+
// non_const_update_bound > first ? non_const_update_bound - first: 0
1864+
const exprt remaining_update_bytes = typecast_exprt::conditional_cast(
1865+
if_exprt{
1866+
binary_predicate_exprt{
1867+
*non_const_update_bound,
1868+
ID_gt,
1869+
from_integer(first, non_const_update_bound->type())},
1870+
minus_exprt{*non_const_update_bound,
1871+
from_integer(first, non_const_update_bound->type())},
1872+
from_integer(0, non_const_update_bound->type())},
1873+
size_type());
1874+
// Now take the minimum of update-bytes-left and the previously computed
1875+
// size of the member to be updated:
1876+
update_size_expr = if_exprt{
1877+
binary_predicate_exprt{update_size_expr, ID_lt, remaining_update_bytes},
1878+
update_size_expr,
1879+
remaining_update_bytes};
1880+
simplify(update_size_expr, ns);
1881+
member_update_bound = std::move(update_size_expr);
1882+
}
1883+
1884+
// We have established the bytes to use for the update, but now need to
1885+
// account for sub-byte members.
18451886
const std::size_t shift =
18461887
numeric_cast_v<std::size_t>(member_offset_bits % 8);
18471888
const std::size_t element_bits_int =
@@ -1858,20 +1899,17 @@ static exprt lower_byte_update_struct(
18581899
member.op0().swap(member.op1());
18591900
}
18601901

1861-
byte_update_exprt bu{
1862-
src.id(),
1863-
std::move(member),
1864-
offset,
1865-
array_exprt{
1866-
std::move(update_values),
1867-
array_typet{bv_typet{8}, from_integer(update_size, size_type())}}};
1902+
// Finally construct the updated member.
1903+
byte_update_exprt bu{src.id(), std::move(member), offset, update_expr};
1904+
exprt updated_element =
1905+
lower_byte_update(bu, update_expr, member_update_bound, ns);
18681906

18691907
if(shift == 0)
1870-
elements.push_back(lower_byte_operators(bu, ns));
1908+
elements.push_back(updated_element);
18711909
else
18721910
{
18731911
elements.push_back(typecast_exprt::conditional_cast(
1874-
extractbits_exprt{lower_byte_operators(bu, ns),
1912+
extractbits_exprt{updated_element,
18751913
element_bits_int - 1 + (little_endian ? shift : 0),
18761914
(little_endian ? shift : 0),
18771915
bv_typet{element_bits_int}},
@@ -2022,6 +2060,28 @@ static exprt lower_byte_update(
20222060
instantiate_byte_array(value_as_byte_array, 0, (type_bits + 7) / 8, ns);
20232061
}
20242062

2063+
if(non_const_update_bound.has_value())
2064+
{
2065+
const exprt src_as_bytes = unpack_rec(
2066+
src.op(),
2067+
src.id() == ID_byte_update_little_endian,
2068+
mp_integer{0},
2069+
mp_integer{update_bytes.size()},
2070+
ns);
2071+
CHECK_RETURN(src_as_bytes.id() == ID_array);
2072+
CHECK_RETURN(src_as_bytes.operands().size() == update_bytes.size());
2073+
for(std::size_t i = 0; i < update_bytes.size(); ++i)
2074+
{
2075+
update_bytes[i] =
2076+
if_exprt{binary_predicate_exprt{
2077+
from_integer(i, non_const_update_bound->type()),
2078+
ID_lt,
2079+
*non_const_update_bound},
2080+
update_bytes[i],
2081+
src_as_bytes.operands()[i]};
2082+
}
2083+
}
2084+
20252085
const std::size_t update_size = update_bytes.size();
20262086
const std::size_t width = std::max(type_bits, update_size * 8);
20272087

0 commit comments

Comments
 (0)