Skip to content

Commit 2bfd906

Browse files
authored
[layout] Move Kani proofs from crate root (#2430)
All moved proofs test code from the `layout` module, and so belong there rather than in the crate root. gherrit-pr-id: I691b42ce8c0c3c6e5990e7684fc66f8f5dd73d85
1 parent 04cafb2 commit 2bfd906

File tree

2 files changed

+241
-239
lines changed

2 files changed

+241
-239
lines changed

src/layout.rs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,3 +1429,244 @@ mod tests {
14291429
validate_against_rust!(4, 16, 4);
14301430
}
14311431
}
1432+
1433+
#[cfg(kani)]
1434+
mod proofs {
1435+
use core::alloc::Layout;
1436+
1437+
use super::*;
1438+
1439+
impl kani::Arbitrary for DstLayout {
1440+
fn any() -> Self {
1441+
let align: NonZeroUsize = kani::any();
1442+
let size_info: SizeInfo = kani::any();
1443+
1444+
kani::assume(align.is_power_of_two());
1445+
kani::assume(align < DstLayout::THEORETICAL_MAX_ALIGN);
1446+
1447+
// For testing purposes, we most care about instantiations of
1448+
// `DstLayout` that can correspond to actual Rust types. We use
1449+
// `Layout` to verify that our `DstLayout` satisfies the validity
1450+
// conditions of Rust layouts.
1451+
kani::assume(
1452+
match size_info {
1453+
SizeInfo::Sized { size } => Layout::from_size_align(size, align.get()),
1454+
SizeInfo::SliceDst(TrailingSliceLayout { offset, elem_size: _ }) => {
1455+
// `SliceDst`` cannot encode an exact size, but we know
1456+
// it is at least `offset` bytes.
1457+
Layout::from_size_align(offset, align.get())
1458+
}
1459+
}
1460+
.is_ok(),
1461+
);
1462+
1463+
Self { align: align, size_info: size_info }
1464+
}
1465+
}
1466+
1467+
impl kani::Arbitrary for SizeInfo {
1468+
fn any() -> Self {
1469+
let is_sized: bool = kani::any();
1470+
1471+
match is_sized {
1472+
true => {
1473+
let size: usize = kani::any();
1474+
1475+
kani::assume(size <= isize::MAX as _);
1476+
1477+
SizeInfo::Sized { size }
1478+
}
1479+
false => SizeInfo::SliceDst(kani::any()),
1480+
}
1481+
}
1482+
}
1483+
1484+
impl kani::Arbitrary for TrailingSliceLayout {
1485+
fn any() -> Self {
1486+
let elem_size: usize = kani::any();
1487+
let offset: usize = kani::any();
1488+
1489+
kani::assume(elem_size < isize::MAX as _);
1490+
kani::assume(offset < isize::MAX as _);
1491+
1492+
TrailingSliceLayout { elem_size, offset }
1493+
}
1494+
}
1495+
1496+
#[kani::proof]
1497+
fn prove_dst_layout_extend() {
1498+
use crate::util::{max, min, padding_needed_for};
1499+
1500+
let base: DstLayout = kani::any();
1501+
let field: DstLayout = kani::any();
1502+
let packed: Option<NonZeroUsize> = kani::any();
1503+
1504+
if let Some(max_align) = packed {
1505+
kani::assume(max_align.is_power_of_two());
1506+
kani::assume(base.align <= max_align);
1507+
}
1508+
1509+
// The base can only be extended if it's sized.
1510+
kani::assume(matches!(base.size_info, SizeInfo::Sized { .. }));
1511+
let base_size = if let SizeInfo::Sized { size } = base.size_info {
1512+
size
1513+
} else {
1514+
unreachable!();
1515+
};
1516+
1517+
// Under the above conditions, `DstLayout::extend` will not panic.
1518+
let composite = base.extend(field, packed);
1519+
1520+
// The field's alignment is clamped by `max_align` (i.e., the
1521+
// `packed` attribute, if any) [1].
1522+
//
1523+
// [1] Per https://doc.rust-lang.org/reference/type-layout.html#the-alignment-modifiers:
1524+
//
1525+
// The alignments of each field, for the purpose of positioning
1526+
// fields, is the smaller of the specified alignment and the
1527+
// alignment of the field's type.
1528+
let field_align = min(field.align, packed.unwrap_or(DstLayout::THEORETICAL_MAX_ALIGN));
1529+
1530+
// The struct's alignment is the maximum of its previous alignment and
1531+
// `field_align`.
1532+
assert_eq!(composite.align, max(base.align, field_align));
1533+
1534+
// Compute the minimum amount of inter-field padding needed to
1535+
// satisfy the field's alignment, and offset of the trailing field.
1536+
// [1]
1537+
//
1538+
// [1] Per https://doc.rust-lang.org/reference/type-layout.html#the-alignment-modifiers:
1539+
//
1540+
// Inter-field padding is guaranteed to be the minimum required in
1541+
// order to satisfy each field's (possibly altered) alignment.
1542+
let padding = padding_needed_for(base_size, field_align);
1543+
let offset = base_size + padding;
1544+
1545+
// For testing purposes, we'll also construct `alloc::Layout`
1546+
// stand-ins for `DstLayout`, and show that `extend` behaves
1547+
// comparably on both types.
1548+
let base_analog = Layout::from_size_align(base_size, base.align.get()).unwrap();
1549+
1550+
match field.size_info {
1551+
SizeInfo::Sized { size: field_size } => {
1552+
if let SizeInfo::Sized { size: composite_size } = composite.size_info {
1553+
// If the trailing field is sized, the resulting layout will
1554+
// be sized. Its size will be the sum of the preceding
1555+
// layout, the size of the new field, and the size of
1556+
// inter-field padding between the two.
1557+
assert_eq!(composite_size, offset + field_size);
1558+
1559+
let field_analog =
1560+
Layout::from_size_align(field_size, field_align.get()).unwrap();
1561+
1562+
if let Ok((actual_composite, actual_offset)) = base_analog.extend(field_analog)
1563+
{
1564+
assert_eq!(actual_offset, offset);
1565+
assert_eq!(actual_composite.size(), composite_size);
1566+
assert_eq!(actual_composite.align(), composite.align.get());
1567+
} else {
1568+
// An error here reflects that composite of `base`
1569+
// and `field` cannot correspond to a real Rust type
1570+
// fragment, because such a fragment would violate
1571+
// the basic invariants of a valid Rust layout. At
1572+
// the time of writing, `DstLayout` is a little more
1573+
// permissive than `Layout`, so we don't assert
1574+
// anything in this branch (e.g., unreachability).
1575+
}
1576+
} else {
1577+
panic!("The composite of two sized layouts must be sized.")
1578+
}
1579+
}
1580+
SizeInfo::SliceDst(TrailingSliceLayout {
1581+
offset: field_offset,
1582+
elem_size: field_elem_size,
1583+
}) => {
1584+
if let SizeInfo::SliceDst(TrailingSliceLayout {
1585+
offset: composite_offset,
1586+
elem_size: composite_elem_size,
1587+
}) = composite.size_info
1588+
{
1589+
// The offset of the trailing slice component is the sum
1590+
// of the offset of the trailing field and the trailing
1591+
// slice offset within that field.
1592+
assert_eq!(composite_offset, offset + field_offset);
1593+
// The elem size is unchanged.
1594+
assert_eq!(composite_elem_size, field_elem_size);
1595+
1596+
let field_analog =
1597+
Layout::from_size_align(field_offset, field_align.get()).unwrap();
1598+
1599+
if let Ok((actual_composite, actual_offset)) = base_analog.extend(field_analog)
1600+
{
1601+
assert_eq!(actual_offset, offset);
1602+
assert_eq!(actual_composite.size(), composite_offset);
1603+
assert_eq!(actual_composite.align(), composite.align.get());
1604+
} else {
1605+
// An error here reflects that composite of `base`
1606+
// and `field` cannot correspond to a real Rust type
1607+
// fragment, because such a fragment would violate
1608+
// the basic invariants of a valid Rust layout. At
1609+
// the time of writing, `DstLayout` is a little more
1610+
// permissive than `Layout`, so we don't assert
1611+
// anything in this branch (e.g., unreachability).
1612+
}
1613+
} else {
1614+
panic!("The extension of a layout with a DST must result in a DST.")
1615+
}
1616+
}
1617+
}
1618+
}
1619+
1620+
#[kani::proof]
1621+
#[kani::should_panic]
1622+
fn prove_dst_layout_extend_dst_panics() {
1623+
let base: DstLayout = kani::any();
1624+
let field: DstLayout = kani::any();
1625+
let packed: Option<NonZeroUsize> = kani::any();
1626+
1627+
if let Some(max_align) = packed {
1628+
kani::assume(max_align.is_power_of_two());
1629+
kani::assume(base.align <= max_align);
1630+
}
1631+
1632+
kani::assume(matches!(base.size_info, SizeInfo::SliceDst(..)));
1633+
1634+
let _ = base.extend(field, packed);
1635+
}
1636+
1637+
#[kani::proof]
1638+
fn prove_dst_layout_pad_to_align() {
1639+
use crate::util::padding_needed_for;
1640+
1641+
let layout: DstLayout = kani::any();
1642+
1643+
let padded: DstLayout = layout.pad_to_align();
1644+
1645+
// Calling `pad_to_align` does not alter the `DstLayout`'s alignment.
1646+
assert_eq!(padded.align, layout.align);
1647+
1648+
if let SizeInfo::Sized { size: unpadded_size } = layout.size_info {
1649+
if let SizeInfo::Sized { size: padded_size } = padded.size_info {
1650+
// If the layout is sized, it will remain sized after padding is
1651+
// added. Its sum will be its unpadded size and the size of the
1652+
// trailing padding needed to satisfy its alignment
1653+
// requirements.
1654+
let padding = padding_needed_for(unpadded_size, layout.align);
1655+
assert_eq!(padded_size, unpadded_size + padding);
1656+
1657+
// Prove that calling `DstLayout::pad_to_align` behaves
1658+
// identically to `Layout::pad_to_align`.
1659+
let layout_analog =
1660+
Layout::from_size_align(unpadded_size, layout.align.get()).unwrap();
1661+
let padded_analog = layout_analog.pad_to_align();
1662+
assert_eq!(padded_analog.align(), layout.align.get());
1663+
assert_eq!(padded_analog.size(), padded_size);
1664+
} else {
1665+
panic!("The padding of a sized layout must result in a sized layout.")
1666+
}
1667+
} else {
1668+
// If the layout is a DST, padding cannot be statically added.
1669+
assert_eq!(padded.size_info, layout.size_info);
1670+
}
1671+
}
1672+
}

0 commit comments

Comments
 (0)