Skip to main content

rkyv/validation/shared/
validator.rs

1//! Validators add validation capabilities by wrapping and extending basic
2//! validators.
3
4use core::{any::TypeId, error::Error, fmt, hash::BuildHasherDefault};
5#[cfg(feature = "std")]
6use std::collections::hash_map;
7
8#[cfg(not(feature = "std"))]
9use hashbrown::hash_map;
10use rancor::{fail, Source};
11
12use crate::{
13    erased::{ErasedPtr, Metadata},
14    hash::FxHasher64,
15    validation::{shared::ValidationState, SharedContext},
16};
17
18#[derive(Debug)]
19struct SharedValidationState {
20    type_id: TypeId,
21    metadata: Metadata,
22    is_finished: bool,
23}
24
25/// A validator that can verify shared pointers.
26#[derive(Debug, Default)]
27pub struct SharedValidator {
28    shared: hash_map::HashMap<
29        usize,
30        SharedValidationState,
31        BuildHasherDefault<FxHasher64>,
32    >,
33}
34
35impl SharedValidator {
36    /// Creates a new shared pointer validator.
37    #[inline]
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Creates a new shared pointer validator with specific capacity.
43    #[inline]
44    pub fn with_capacity(capacity: usize) -> Self {
45        Self {
46            shared: hash_map::HashMap::with_capacity_and_hasher(
47                capacity,
48                Default::default(),
49            ),
50        }
51    }
52}
53
54#[derive(Debug)]
55struct TypeMismatch {
56    previous: TypeId,
57    current: TypeId,
58}
59
60impl fmt::Display for TypeMismatch {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(
63            f,
64            "the same memory region has been claimed as two different types: \
65             {:?} and {:?}",
66            self.previous, self.current,
67        )
68    }
69}
70
71impl Error for TypeMismatch {}
72
73#[derive(Debug)]
74struct MetadataMismatch;
75
76impl fmt::Display for MetadataMismatch {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(
79            f,
80            "the same memory region has been claimed as the same type with
81            different pointer metadata (e.g. slice length)",
82        )
83    }
84}
85
86impl Error for MetadataMismatch {}
87
88#[derive(Debug)]
89struct NotStarted;
90
91impl fmt::Display for NotStarted {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "shared pointer was not started validation")
94    }
95}
96
97impl Error for NotStarted {}
98
99#[derive(Debug)]
100struct AlreadyFinished;
101
102impl fmt::Display for AlreadyFinished {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        write!(f, "shared pointer was already finished validation")
105    }
106}
107
108impl Error for AlreadyFinished {}
109
110impl<E: Source> SharedContext<E> for SharedValidator {
111    fn start_shared(
112        &mut self,
113        shared_type_id: TypeId,
114        ptr: ErasedPtr,
115        metadata_is_eq: unsafe fn(Metadata, Metadata) -> bool,
116    ) -> Result<ValidationState, E> {
117        match self.shared.entry(ptr.data_address() as usize) {
118            hash_map::Entry::Vacant(vacant) => {
119                vacant.insert(SharedValidationState {
120                    type_id: shared_type_id,
121                    metadata: ptr.metadata(),
122                    is_finished: false,
123                });
124                Ok(ValidationState::Started)
125            }
126            hash_map::Entry::Occupied(occupied) => {
127                let state = occupied.get();
128                if state.type_id != shared_type_id {
129                    fail!(TypeMismatch {
130                        previous: state.type_id,
131                        current: shared_type_id,
132                    });
133                }
134
135                let is_same_metadata =
136                    unsafe { metadata_is_eq(ptr.metadata(), state.metadata) };
137                if !is_same_metadata {
138                    fail!(MetadataMismatch);
139                }
140
141                if !state.is_finished {
142                    Ok(ValidationState::Pending)
143                } else {
144                    Ok(ValidationState::Finished)
145                }
146            }
147        }
148    }
149
150    fn finish_shared(
151        &mut self,
152        _shared_type_id: TypeId,
153        ptr: ErasedPtr,
154    ) -> Result<(), E> {
155        match self.shared.entry(ptr.data_address() as usize) {
156            hash_map::Entry::Vacant(_) => fail!(NotStarted),
157            hash_map::Entry::Occupied(mut occupied) => {
158                let state = occupied.get_mut();
159
160                if state.is_finished {
161                    fail!(AlreadyFinished);
162                }
163
164                state.is_finished = true;
165                Ok(())
166            }
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    #[cfg(not(any(
174        feature = "pointer_width_16",
175        feature = "pointer_width_64"
176    )))]
177    #[test]
178    fn conflicting_metadata() {
179        use rancor::Error;
180
181        use crate::{
182            alloc::rc::Rc, api::high::access, util::Align, Archive, Serialize,
183        };
184
185        #[expect(dead_code)]
186        #[derive(Archive, Serialize)]
187        #[rkyv(crate, derive(Debug))]
188        struct Test {
189            a: Rc<[u8]>,
190            b: Rc<[u8]>,
191        }
192
193        // Invalid archive (mismatched metadata)
194        let synthetic_buf = Align([
195            // Shared slice
196            1u8, 2u8, 3u8, 4u8, // First Rc
197            0xfc, 0xff, 0xff, 0xff, // points 4 bytes backward
198            4u8, 0u8, 0u8, 0u8, // slice is 4 bytes long
199            // Second Rc
200            0xf4, 0xff, 0xff, 0xff, // points 12 bytes backward
201            2u8, 0u8, 0u8, 0u8, // slice is 2 bytes long
202        ]);
203
204        let result = access::<ArchivedTest, Error>(&*synthetic_buf);
205        assert_source!(result.unwrap_err(), super::MetadataMismatch);
206    }
207}