rkyv/collections/index_map/
validation.rs

1//! Validation implementation for ArchivedIndexMap.
2
3use crate::{
4    collections::{
5        hash_index::validation::HashIndexError,
6        index_map::ArchivedIndexMap,
7        util::{validation::ArchivedEntryError, Entry},
8        ArchivedHashIndex,
9    },
10    validation::ArchiveContext,
11    Archived, RelPtr,
12};
13use bytecheck::{CheckBytes, Error, SliceCheckError};
14use core::{
15    alloc::{Layout, LayoutError},
16    convert::Infallible,
17    fmt,
18    hash::Hash,
19    ptr,
20};
21
22/// Errors that can occur while checking an archived index map.
23#[derive(Debug)]
24pub enum IndexMapError<K, V, C> {
25    /// An error occurred while checking the hash index
26    HashIndexError(HashIndexError<C>),
27    /// An error occurred while checking the layouts of displacements or entries
28    LayoutError(LayoutError),
29    /// A pivot indexes outside of the entries array
30    PivotOutOfBounds {
31        /// The index of the pivot when iterating
32        index: usize,
33        /// The pivot value that was invalid
34        pivot: usize,
35    },
36    /// An error occurred while checking the entries
37    CheckEntryError(SliceCheckError<ArchivedEntryError<K, V>>),
38    /// A key is not located at the correct position
39    ///
40    /// This can either be due to the key being invalid for the hash index, or the pivot for the key
41    /// not pointing to it.
42    InvalidKeyPosition {
43        /// The index of the key when iterating
44        index: usize,
45    },
46    /// A bounds error occurred
47    ContextError(C),
48}
49
50impl<K: fmt::Display, V: fmt::Display, E: fmt::Display> fmt::Display for IndexMapError<K, V, E> {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match self {
53            IndexMapError::HashIndexError(e) => write!(f, "hash index check error: {}", e),
54            IndexMapError::LayoutError(e) => write!(f, "layout error: {}", e),
55            IndexMapError::PivotOutOfBounds { index, pivot } => {
56                write!(f, "pivot out of bounds: {} at index {}", pivot, index)
57            }
58            IndexMapError::CheckEntryError(e) => write!(f, "entry check error: {}", e),
59            IndexMapError::InvalidKeyPosition { index } => {
60                write!(f, "invalid key position: at index {}", index)
61            }
62            IndexMapError::ContextError(e) => e.fmt(f),
63        }
64    }
65}
66
67#[cfg(feature = "std")]
68const _: () = {
69    use std::error::Error;
70
71    impl<K, V, C> Error for IndexMapError<K, V, C>
72    where
73        K: Error + 'static,
74        V: Error + 'static,
75        C: Error + 'static,
76    {
77        fn source(&self) -> Option<&(dyn Error + 'static)> {
78            match self {
79                IndexMapError::HashIndexError(e) => Some(e as &dyn Error),
80                IndexMapError::LayoutError(e) => Some(e as &dyn Error),
81                IndexMapError::PivotOutOfBounds { .. } => None,
82                IndexMapError::CheckEntryError(e) => Some(e as &dyn Error),
83                IndexMapError::InvalidKeyPosition { .. } => None,
84                IndexMapError::ContextError(e) => Some(e as &dyn Error),
85            }
86        }
87    }
88};
89
90impl<K, V, C> From<Infallible> for IndexMapError<K, V, C> {
91    fn from(_: Infallible) -> Self {
92        unsafe { core::hint::unreachable_unchecked() }
93    }
94}
95
96impl<K, V, C> From<SliceCheckError<Infallible>> for IndexMapError<K, V, C> {
97    #[inline]
98    fn from(_: SliceCheckError<Infallible>) -> Self {
99        unsafe { core::hint::unreachable_unchecked() }
100    }
101}
102
103impl<K, V, C> From<HashIndexError<C>> for IndexMapError<K, V, C> {
104    #[inline]
105    fn from(e: HashIndexError<C>) -> Self {
106        Self::HashIndexError(e)
107    }
108}
109
110impl<K, V, C> From<LayoutError> for IndexMapError<K, V, C> {
111    #[inline]
112    fn from(e: LayoutError) -> Self {
113        Self::LayoutError(e)
114    }
115}
116
117impl<K, V, C> From<SliceCheckError<ArchivedEntryError<K, V>>> for IndexMapError<K, V, C> {
118    #[inline]
119    fn from(e: SliceCheckError<ArchivedEntryError<K, V>>) -> Self {
120        Self::CheckEntryError(e)
121    }
122}
123
124impl<K, V, C> CheckBytes<C> for ArchivedIndexMap<K, V>
125where
126    K: CheckBytes<C> + Eq + Hash,
127    V: CheckBytes<C>,
128    C: ArchiveContext + ?Sized,
129    C::Error: Error,
130{
131    type Error = IndexMapError<K::Error, V::Error, C::Error>;
132
133    unsafe fn check_bytes<'a>(
134        value: *const Self,
135        context: &mut C,
136    ) -> Result<&'a Self, Self::Error> {
137        let index = ArchivedHashIndex::check_bytes(ptr::addr_of!((*value).index), context)?;
138
139        // Entries
140        Layout::array::<Entry<K, V>>(index.len())?;
141        let entries_rel_ptr = RelPtr::manual_check_bytes(ptr::addr_of!((*value).entries), context)?;
142        let entries_ptr = context
143            .check_subtree_ptr::<[Entry<K, V>]>(
144                entries_rel_ptr.base(),
145                entries_rel_ptr.offset(),
146                index.len(),
147            )
148            .map_err(IndexMapError::ContextError)?;
149
150        let range = context
151            .push_prefix_subtree(entries_ptr)
152            .map_err(IndexMapError::ContextError)?;
153        let entries = <[Entry<K, V>]>::check_bytes(entries_ptr, context)?;
154        context
155            .pop_prefix_range(range)
156            .map_err(IndexMapError::ContextError)?;
157
158        // Pivots
159        Layout::array::<Archived<usize>>(index.len())?;
160        let pivots_rel_ptr = RelPtr::manual_check_bytes(ptr::addr_of!((*value).pivots), context)?;
161        let pivots_ptr = context
162            .check_subtree_ptr::<[Archived<usize>]>(
163                pivots_rel_ptr.base(),
164                pivots_rel_ptr.offset(),
165                index.len(),
166            )
167            .map_err(IndexMapError::ContextError)?;
168
169        let range = context
170            .push_prefix_subtree(pivots_ptr)
171            .map_err(IndexMapError::ContextError)?;
172        let pivots = <[Archived<usize>]>::check_bytes(pivots_ptr, context)?;
173        context
174            .pop_prefix_range(range)
175            .map_err(IndexMapError::ContextError)?;
176
177        for (i, pivot) in pivots.iter().enumerate() {
178            let pivot = from_archived!(*pivot) as usize;
179            if pivot >= index.len() {
180                return Err(IndexMapError::PivotOutOfBounds { index: i, pivot });
181            }
182        }
183
184        for (i, entry) in entries.iter().enumerate() {
185            if let Some(pivot_index) = index.index(&entry.key) {
186                let pivot = from_archived!(pivots[pivot_index]) as usize;
187                if pivot != i {
188                    return Err(IndexMapError::InvalidKeyPosition { index: i });
189                }
190            } else {
191                return Err(IndexMapError::InvalidKeyPosition { index: i });
192            }
193        }
194
195        Ok(&*value)
196    }
197}