Skip to content

Commit 3b0c6d1

Browse files
Handle unsized 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. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 5059d48 commit 3b0c6d1

4 files changed

Lines changed: 92 additions & 37 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: 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.

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

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

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

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,42 +1597,23 @@ 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)
1601+
// When the array element type has no byte size (e.g., arrays of unbounded
1602+
// arrays used in heap models), object_descriptor_exprt::build cannot compute
1603+
// byte offsets and produces an ID_unknown offset. Fall back to a simple
1604+
// index-based lower-bound check.
1605+
std::optional<object_descriptor_exprt> ode;
1606+
if(!size_of_expr(expr.type(), ns).has_value())
16041607
{
1605-
// we undo typecasts to signedbv
1606-
if(
1607-
index.id() == ID_typecast &&
1608-
to_typecast_expr(index).op().type().id() == ID_unsignedbv)
1609-
{
1610-
// ok
1611-
}
1612-
else
1608+
if(index.type().id() != ID_unsignedbv && index.type().id() != ID_natural)
16131609
{
16141610
const auto i = numeric_cast<mp_integer>(index);
16151611

16161612
if(!i.has_value() || *i < 0)
16171613
{
1618-
exprt effective_offset = ode.offset();
1619-
1620-
if(ode.root_object().id() == ID_dereference)
1621-
{
1622-
exprt p_offset =
1623-
pointer_offset(to_dereference_expr(ode.root_object()).pointer());
1624-
1625-
effective_offset = plus_exprt{
1626-
p_offset,
1627-
typecast_exprt::conditional_cast(
1628-
effective_offset, p_offset.type())};
1629-
}
1630-
1631-
exprt zero = from_integer(0, effective_offset.type());
1614+
exprt zero = from_integer(0, index.type());
16321615

1633-
// the final offset must not be negative
1634-
binary_relation_exprt inequality(
1635-
effective_offset, ID_ge, std::move(zero));
1616+
binary_relation_exprt inequality(index, ID_ge, std::move(zero));
16361617

16371618
add_guarded_property(
16381619
inequality,
@@ -1645,15 +1626,66 @@ void goto_check_ct::bounds_check_index(
16451626
}
16461627
}
16471628
}
1629+
else
1630+
{
1631+
ode.emplace();
1632+
ode->build(expr, ns);
1633+
1634+
if(index.type().id() != ID_unsignedbv)
1635+
{
1636+
// we undo typecasts to signedbv
1637+
if(
1638+
index.id() == ID_typecast &&
1639+
to_typecast_expr(index).op().type().id() == ID_unsignedbv)
1640+
{
1641+
// ok
1642+
}
1643+
else
1644+
{
1645+
const auto i = numeric_cast<mp_integer>(index);
1646+
1647+
if(!i.has_value() || *i < 0)
1648+
{
1649+
exprt effective_offset = ode->offset();
1650+
1651+
if(ode->root_object().id() == ID_dereference)
1652+
{
1653+
exprt p_offset =
1654+
pointer_offset(to_dereference_expr(ode->root_object()).pointer());
1655+
1656+
effective_offset = plus_exprt{
1657+
p_offset,
1658+
typecast_exprt::conditional_cast(
1659+
effective_offset, p_offset.type())};
1660+
}
1661+
1662+
exprt zero = from_integer(0, effective_offset.type());
1663+
1664+
// the final offset must not be negative
1665+
binary_relation_exprt inequality(
1666+
effective_offset, ID_ge, std::move(zero));
1667+
1668+
add_guarded_property(
1669+
inequality,
1670+
name + " lower bound",
1671+
"array bounds",
1672+
true, // fatal
1673+
expr.find_source_location(),
1674+
expr,
1675+
guard);
1676+
}
1677+
}
1678+
}
1679+
}
16481680

1649-
if(ode.root_object().id() == ID_dereference)
1681+
if(ode && ode->root_object().id() == ID_dereference)
16501682
{
1651-
const exprt &pointer = to_dereference_expr(ode.root_object()).pointer();
1683+
const exprt &pointer = to_dereference_expr(ode->root_object()).pointer();
16521684

16531685
const plus_exprt effective_offset{
1654-
ode.offset(),
1686+
ode->offset(),
16551687
typecast_exprt::conditional_cast(
1656-
pointer_offset(pointer), ode.offset().type())};
1688+
pointer_offset(pointer), ode->offset().type())};
16571689

16581690
binary_relation_exprt inequality{
16591691
effective_offset,
@@ -1664,7 +1696,7 @@ void goto_check_ct::bounds_check_index(
16641696
exprt in_bounds_of_some_explicit_allocation =
16651697
is_in_bounds_of_some_explicit_allocation(
16661698
pointer,
1667-
plus_exprt{ode.offset(), from_integer(1, ode.offset().type())});
1699+
plus_exprt{ode->offset(), from_integer(1, ode->offset().type())});
16681700

16691701
or_exprt precond(
16701702
std::move(in_bounds_of_some_explicit_allocation), inequality);
@@ -1694,7 +1726,7 @@ void goto_check_ct::bounds_check_index(
16941726
{
16951727
}
16961728
else if(
1697-
expr.array().id() == ID_member &&
1729+
ode && expr.array().id() == ID_member &&
16981730
(size == 0 || array_type.get_bool(ID_C_flexible_array_member)))
16991731
{
17001732
// a variable sized struct member
@@ -1706,13 +1738,13 @@ void goto_check_ct::bounds_check_index(
17061738
// array (with the same element type) that would not make the structure
17071739
// larger than the object being accessed; [...]
17081740
const auto type_size_opt =
1709-
pointer_offset_size(ode.root_object().type(), ns);
1741+
pointer_offset_size(ode->root_object().type(), ns);
17101742
CHECK_RETURN(type_size_opt.has_value());
17111743

17121744
binary_relation_exprt inequality(
1713-
ode.offset(),
1745+
ode->offset(),
17141746
ID_lt,
1715-
from_integer(type_size_opt.value(), ode.offset().type()));
1747+
from_integer(type_size_opt.value(), ode->offset().type()));
17161748

17171749
add_guarded_property(
17181750
inequality,

0 commit comments

Comments
 (0)