Skip to content

Commit b796cbc

Browse files
Handle non-byte-sized array element types in bounds_check_index
When the array element type has no byte size (e.g., arrays of __CPROVER_integer), object_descriptor_exprt::build cannot compute byte offsets. Fall back to a simple index-based lower-bound check instead of crashing. The upper bound check uses the array size directly and does not require byte offsets. A DATA_INVARIANT ensures that arrays with non-byte-sized element types are not accessed via pointer dereference, making explicit the assumption that these arise only from internal modeling constructs. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 4eb741f commit b796cbc

6 files changed

Lines changed: 176 additions & 41 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
int main()
2+
{
3+
__CPROVER_integer arr[3];
4+
__CPROVER_integer i;
5+
__CPROVER_assume(i >= 0 && i < 3);
6+
__CPROVER_integer x = arr[i];
7+
__CPROVER_assert(1, "reachable");
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE smt-backend broken-cprover-smt-backend no-new-smt
2+
main.c
3+
--bounds-check --smt2
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verify that bounds checking arrays with mathematical integer element types
10+
succeeds when using an SMT backend.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--show-goto-functions
4+
^[[:space:]]*ASSERT.*lower bound in arr\[.*i\]$
5+
^[[:space:]]*ASSERT.*upper bound in arr\[.*i\]$
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
^warning: ignoring
10+
--
11+
Bounds checking arrays with mathematical integer element types must not crash.
12+
The element type has no byte size, so the byte-offset-based lower-bound check
13+
is replaced by a simple index-based check. Both lower and upper bound checks
14+
must be generated.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE broken-cprover-smt-backend no-new-smt
2+
main.c
3+
--bounds-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Verify that bounds checking arrays with mathematical integer element types
10+
completes verification successfully.

regression/validate-trace-xml-schema/check.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
['Bool', 'bool3.desc'],
7272
['Empty_struct3', 'test.desc'],
7373
# uses show-goto-functions
74+
['bounds_check_integer_index1', 'test.desc'],
7475
['reachability-slice', 'test.desc'],
7576
['reachability-slice', 'test2.desc'],
7677
['reachability-slice', 'test3.desc'],

src/ansi-c/goto-conversion/goto_check_c.cpp

Lines changed: 133 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,63 +1597,137 @@ void goto_check_ct::bounds_check_index(
15971597
std::string name = array_name(expr.array());
15981598

15991599
const exprt &index = expr.index();
1600-
object_descriptor_exprt ode;
1601-
ode.build(expr, ns);
16021600

1603-
if(index.type().id() != ID_unsignedbv)
1604-
{
1605-
// we undo typecasts to signedbv
1601+
// When the array element type has no byte size (e.g., __CPROVER_integer or
1602+
// arrays of unbounded arrays used in heap models),
1603+
// object_descriptor_exprt::build cannot compute byte offsets and produces an
1604+
// ID_unknown offset. Fall back to a simple index-based lower-bound check. We
1605+
// strip typecasts from integer/natural types to bitvector types so that the
1606+
// resulting assertions stay in the integer domain and can be handled by SMT
1607+
// solvers.
1608+
std::optional<object_descriptor_exprt> ode;
1609+
// effective_index is only used in the non-byte-sized path; it strips
1610+
// bitvector casts from integer/natural types to keep assertions in the
1611+
// integer domain.
1612+
exprt effective_index = index;
1613+
if(!size_of_expr(expr.type(), ns).has_value())
1614+
{
1615+
// Arrays with non-byte-sized element types arise from internal modeling
1616+
// constructs and should not be accessed via pointer dereference. Walk
1617+
// through member/index/typecast to find the root object.
1618+
const exprt *root = &expr.array();
1619+
while(root->id() == ID_member)
1620+
root = &to_member_expr(*root).compound();
1621+
while(root->id() == ID_index)
1622+
root = &to_index_expr(*root).array();
1623+
while(root->id() == ID_typecast)
1624+
root = &to_typecast_expr(*root).op();
1625+
DATA_INVARIANT(
1626+
root->id() != ID_dereference,
1627+
"arrays with non-byte-sized element types should not be accessed via "
1628+
"pointer dereference");
16061629
if(
1607-
index.id() == ID_typecast &&
1608-
to_typecast_expr(index).op().type().id() == ID_unsignedbv)
1630+
effective_index.id() == ID_typecast &&
1631+
(to_typecast_expr(effective_index).op().type().id() == ID_integer ||
1632+
to_typecast_expr(effective_index).op().type().id() == ID_natural))
16091633
{
1610-
// ok
1634+
effective_index = to_typecast_expr(effective_index).op();
16111635
}
1612-
else
1613-
{
1614-
const auto i = numeric_cast<mp_integer>(index);
16151636

1616-
if(!i.has_value() || *i < 0)
1637+
if(
1638+
effective_index.type().id() != ID_unsignedbv &&
1639+
effective_index.type().id() != ID_natural)
1640+
{
1641+
// we undo typecasts to signedbv
1642+
if(
1643+
effective_index.id() == ID_typecast &&
1644+
to_typecast_expr(effective_index).op().type().id() == ID_unsignedbv)
1645+
{
1646+
// ok
1647+
}
1648+
else
16171649
{
1618-
exprt effective_offset = ode.offset();
1650+
const auto i = numeric_cast<mp_integer>(effective_index);
16191651

1620-
if(ode.root_object().id() == ID_dereference)
1652+
if(!i.has_value() || *i < 0)
16211653
{
1622-
exprt p_offset =
1623-
pointer_offset(to_dereference_expr(ode.root_object()).pointer());
1654+
exprt zero = from_integer(0, effective_index.type());
16241655

1625-
effective_offset = plus_exprt{
1626-
p_offset,
1627-
typecast_exprt::conditional_cast(
1628-
effective_offset, p_offset.type())};
1656+
binary_relation_exprt inequality(
1657+
effective_index, ID_ge, std::move(zero));
1658+
1659+
add_guarded_property(
1660+
inequality,
1661+
name + " lower bound",
1662+
"array bounds",
1663+
true, // fatal
1664+
expr.find_source_location(),
1665+
expr,
1666+
guard);
16291667
}
1668+
}
1669+
}
1670+
}
1671+
else
1672+
{
1673+
ode.emplace();
1674+
ode->build(expr, ns);
16301675

1631-
exprt zero = from_integer(0, effective_offset.type());
1676+
if(index.type().id() != ID_unsignedbv)
1677+
{
1678+
// we undo typecasts to signedbv
1679+
if(
1680+
index.id() == ID_typecast &&
1681+
to_typecast_expr(index).op().type().id() == ID_unsignedbv)
1682+
{
1683+
// ok
1684+
}
1685+
else
1686+
{
1687+
const auto i = numeric_cast<mp_integer>(index);
16321688

1633-
// the final offset must not be negative
1634-
binary_relation_exprt inequality(
1635-
effective_offset, ID_ge, std::move(zero));
1689+
if(!i.has_value() || *i < 0)
1690+
{
1691+
exprt effective_offset = ode->offset();
16361692

1637-
add_guarded_property(
1638-
inequality,
1639-
name + " lower bound",
1640-
"array bounds",
1641-
true, // fatal
1642-
expr.find_source_location(),
1643-
expr,
1644-
guard);
1693+
if(ode->root_object().id() == ID_dereference)
1694+
{
1695+
exprt p_offset =
1696+
pointer_offset(to_dereference_expr(ode->root_object()).pointer());
1697+
1698+
effective_offset = plus_exprt{
1699+
p_offset,
1700+
typecast_exprt::conditional_cast(
1701+
effective_offset, p_offset.type())};
1702+
}
1703+
1704+
exprt zero = from_integer(0, effective_offset.type());
1705+
1706+
// the final offset must not be negative
1707+
binary_relation_exprt inequality(
1708+
effective_offset, ID_ge, std::move(zero));
1709+
1710+
add_guarded_property(
1711+
inequality,
1712+
name + " lower bound",
1713+
"array bounds",
1714+
true, // fatal
1715+
expr.find_source_location(),
1716+
expr,
1717+
guard);
1718+
}
16451719
}
16461720
}
16471721
}
16481722

1649-
if(ode.root_object().id() == ID_dereference)
1723+
if(ode && ode->root_object().id() == ID_dereference)
16501724
{
1651-
const exprt &pointer = to_dereference_expr(ode.root_object()).pointer();
1725+
const exprt &pointer = to_dereference_expr(ode->root_object()).pointer();
16521726

16531727
const plus_exprt effective_offset{
1654-
ode.offset(),
1728+
ode->offset(),
16551729
typecast_exprt::conditional_cast(
1656-
pointer_offset(pointer), ode.offset().type())};
1730+
pointer_offset(pointer), ode->offset().type())};
16571731

16581732
binary_relation_exprt inequality{
16591733
effective_offset,
@@ -1664,7 +1738,7 @@ void goto_check_ct::bounds_check_index(
16641738
exprt in_bounds_of_some_explicit_allocation =
16651739
is_in_bounds_of_some_explicit_allocation(
16661740
pointer,
1667-
plus_exprt{ode.offset(), from_integer(1, ode.offset().type())});
1741+
plus_exprt{ode->offset(), from_integer(1, ode->offset().type())});
16681742

16691743
or_exprt precond(
16701744
std::move(in_bounds_of_some_explicit_allocation), inequality);
@@ -1694,7 +1768,7 @@ void goto_check_ct::bounds_check_index(
16941768
{
16951769
}
16961770
else if(
1697-
expr.array().id() == ID_member &&
1771+
ode && expr.array().id() == ID_member &&
16981772
(size == 0 || array_type.get_bool(ID_C_flexible_array_member)))
16991773
{
17001774
// a variable sized struct member
@@ -1706,13 +1780,31 @@ void goto_check_ct::bounds_check_index(
17061780
// array (with the same element type) that would not make the structure
17071781
// larger than the object being accessed; [...]
17081782
const auto type_size_opt =
1709-
pointer_offset_size(ode.root_object().type(), ns);
1783+
pointer_offset_size(ode->root_object().type(), ns);
17101784
CHECK_RETURN(type_size_opt.has_value());
17111785

17121786
binary_relation_exprt inequality(
1713-
ode.offset(),
1787+
ode->offset(),
1788+
ID_lt,
1789+
from_integer(type_size_opt.value(), ode->offset().type()));
1790+
1791+
add_guarded_property(
1792+
inequality,
1793+
name + " upper bound",
1794+
"array bounds",
1795+
true, // fatal
1796+
expr.find_source_location(),
1797+
expr,
1798+
guard);
1799+
}
1800+
else if(!ode.has_value())
1801+
{
1802+
// non-byte-sized element type: use effective_index (which has bitvector
1803+
// casts stripped) and cast the size to match
1804+
binary_relation_exprt inequality{
1805+
effective_index,
17141806
ID_lt,
1715-
from_integer(type_size_opt.value(), ode.offset().type()));
1807+
typecast_exprt::conditional_cast(size, effective_index.type())};
17161808

17171809
add_guarded_property(
17181810
inequality,

0 commit comments

Comments
 (0)