@@ -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