Skip to content

Commit bc5ca1d

Browse files
committed
[WIP] KnownLayout::validate_size_align
TODO: Tests
1 parent e7a36dc commit bc5ca1d

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

src/lib.rs

+123
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ pub struct DstLayout {
225225
_trailing_slice_elem_size: Option<usize>,
226226
}
227227

228+
enum _CastType {
229+
_Prefix,
230+
_Suffix,
231+
}
232+
228233
impl DstLayout {
229234
/// Constructs a `DstLayout` which describes `T`.
230235
///
@@ -251,6 +256,124 @@ impl DstLayout {
251256
_trailing_slice_elem_size: Some(mem::size_of::<T>()),
252257
}
253258
}
259+
260+
/// TODO
261+
///
262+
/// The caller is responsible for ensuring that `addr + bytes_len` does not
263+
/// overflow `usize`.
264+
///
265+
/// # Panics
266+
///
267+
/// If `addr + bytes_len` overflows `usize`, `validate_cast` may panic, or
268+
/// it may return incorrect results. No guarantees are made about when
269+
/// `validate_cast` will panic. The caller should not rely on
270+
/// `validate_cast` panicking in any particular condition, even if
271+
/// `debug_assertions` are enabled.
272+
const fn _validate_cast(
273+
&self,
274+
addr: usize,
275+
bytes_len: usize,
276+
cast_type: _CastType,
277+
) -> Option<(usize, usize, usize)> {
278+
let base_size = self._base_layout.size();
279+
280+
// LEMMA 0: max_slice_bytes + base_size == bytes_len
281+
//
282+
// LEMMA 1: base_size <= bytes_len:
283+
// - If `base_size > bytes_len`, `bytes_len.checked_sub(base_size)`
284+
// returns `None`, and we return.
285+
let Some(max_slice_bytes) = bytes_len.checked_sub(base_size) else {
286+
return None;
287+
};
288+
289+
let (elems, self_bytes) = if let Some(elem_size) = self._trailing_slice_elem_size {
290+
let Some(elem_size) = NonZeroUsize::new(elem_size) else {
291+
panic!("attempted to cast to slice type with zero-sized element");
292+
};
293+
294+
// Guaranteed not to divide by 0 because `elem_size` is a
295+
// `NonZeroUsize`.
296+
//
297+
// LEMMA 2: elems * elem_size <= max_slice_bytes
298+
#[allow(clippy::arithmetic_side_effects)]
299+
let elems = max_slice_bytes / elem_size.get();
300+
301+
// Guaranteed not to overflow:
302+
// - elems * elem_size <= max_slice_bytes (lemma 2)
303+
// - max_slice_bytes <= usize::MAX (max_slice_bytes: usize)
304+
// - elems * elem_size <= usize::MAX (substitution)
305+
//
306+
// LEMMA 3: slice_bytes <= max_slice_bytes:
307+
// - max_slice_bytes + base_size == bytes_len (lemma 0)
308+
// - base_size == bytes_len - max_slice_bytes
309+
// - elems * elem_size <= max_slice_bytes (lemma 2)
310+
// - slice_bytes <= max_slice_bytes (substitution)
311+
#[allow(clippy::arithmetic_side_effects)]
312+
let slice_bytes = elems * elem_size.get();
313+
314+
// Guaranteed not to overflow:
315+
// - max_slice_bytes + base_size == bytes_len (lemma 0)
316+
// - slice_bytes <= max_slice_bytes (lemma 3)
317+
// - slice_bytes + base_size <= bytes_len (substitution)
318+
// - bytes_len <= usize::MAX (bytes_len: usize)
319+
// - slice_bytes + base_size <= usize::MAX (substitution)
320+
//
321+
// LEMMA 3: self_bytes <= bytes_len: TODO
322+
#[allow(clippy::arithmetic_side_effects)]
323+
let self_bytes = base_size + slice_bytes;
324+
325+
(elems, self_bytes)
326+
} else {
327+
(0, base_size)
328+
};
329+
330+
// LEMMA 4: self_bytes <= bytes_len:
331+
// - `if` branch returns `self_bytes`; lemma 3 guarantees `self_bytes <=
332+
// bytes_len`
333+
// - `else` branch returns `base_size`; lemma 1 guarantees `base_size <=
334+
// bytes_len`
335+
336+
// `self_addr` indicates where in the given byte range the `Self` will
337+
// start. If we're doing a prefix cast, it starts at the beginning. If
338+
// we're doing a suffix cast, it starts after whatever bytes are
339+
// remaining.
340+
let (self_addr, split_at) = match cast_type {
341+
_CastType::_Prefix => (addr, self_bytes),
342+
_CastType::_Suffix => {
343+
// Guaranteed not to underflow because `self_bytes <= bytes_len`
344+
// (lemma 4).
345+
//
346+
// LEMMA 5: split_at = bytes_len - self_bytes
347+
#[allow(clippy::arithmetic_side_effects)]
348+
let split_at = bytes_len - self_bytes;
349+
350+
// Guaranteed not to overflow:
351+
// - addr + bytes_len <= usize::MAX (method precondition)
352+
// - split_at == bytes_len - self_bytes (lemma 5)
353+
// - addr + split_at == addr + bytes_len - self_bytes (substitution)
354+
// - addr + split_at <= addr + bytes_len
355+
// - addr + split_at <= usize::MAX (substitution)
356+
#[allow(clippy::arithmetic_side_effects)]
357+
let self_addr = addr + split_at;
358+
359+
(self_addr, split_at)
360+
}
361+
};
362+
363+
// Guaranteed not to divide by 0 because `.align()` guarantees that it
364+
// returns a non-zero value.
365+
#[allow(clippy::arithmetic_side_effects)]
366+
if self_addr % self._base_layout.align() != 0 {
367+
return None;
368+
}
369+
370+
// Guaranteed not to underflow because `self_bytes <= bytes_len` (lemma
371+
// 4).
372+
#[allow(clippy::arithmetic_side_effects)]
373+
let prefix_suffix_bytes = bytes_len - self_bytes;
374+
375+
Some((elems, split_at, prefix_suffix_bytes))
376+
}
254377
}
255378

256379
/// A trait which carries information about a type's layout that is used by the

0 commit comments

Comments
 (0)