diff --git a/Cargo.toml b/Cargo.toml index 4c77129..e7b26cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["codegen", "tools/create-data-file", "tools/dump-data-file"] [package] name = "data_bucket" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Handy-caT"] license = "MIT" @@ -11,7 +11,7 @@ repository = "https://github.com/pathscale/DataBucket" description = "DataBucket is container for WorkTable's data" [dependencies] -data_bucket_derive = { path = "codegen", version = "=0.3.15" } +data_bucket_derive = { path = "codegen", version = "=0.3.16" } eyre = "0.6.12" derive_more = { version = "1.0.0", features = ["from", "error", "display", "into"] } @@ -23,3 +23,10 @@ indexset = { package = "WorkTablesIndex", version = "=0.0.1", features = ["concu # indexset = { package = "wt-indexset", path = "../indexset", version = "0.12.10", features = ["concurrent", "cdc", "multimap"] } # indexset = { package = "wt-indexset", version = "0.12.12", features = ["concurrent", "cdc", "multimap"] } tokio = { version = "1", features = ["full"] } + +[features] +default = ["validate-reads"] +# Validate every disk read with bytecheck: a torn page becomes a named +# error instead of undefined behavior. Disable for latency-critical builds +# to compile every read back to unchecked access, exactly as before 0.4.1. +validate-reads = [] diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 17657c8..65884f9 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "data_bucket_derive" -version = "0.3.15" +version = "0.3.16" edition = "2021" authors = ["Handy-caT"] license = "MIT" diff --git a/codegen/src/persistable/generator/persistable_impl.rs b/codegen/src/persistable/generator/persistable_impl.rs index 1337cb6..ba0a259 100644 --- a/codegen/src/persistable/generator/persistable_impl.rs +++ b/codegen/src/persistable/generator/persistable_impl.rs @@ -47,7 +47,7 @@ impl Generator { rkyv::ser::Serializer, rkyv::ser::sharing::Share>, rkyv::rancor::Error>, >, - <#ident as rkyv::Archive>::Archived: rkyv::Deserialize<#ident, rkyv::api::high::HighDeserializer> #archived_bounds, + <#ident as rkyv::Archive>::Archived: rkyv::Deserialize<#ident, rkyv::api::high::HighDeserializer> + for<'a> rkyv::bytecheck::CheckBytes> #archived_bounds, } } else { quote! {} @@ -90,7 +90,7 @@ impl Generator { } fn from_bytes(bytes: &[u8], _version: u32) -> Self { - let archived = unsafe { rkyv::access_unchecked::<::Archived>(bytes) }; + let archived = data_bucket::access_archived::<::Archived>(bytes).expect("torn or corrupt page: the archived bytes fail validation"); rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid") } } @@ -214,7 +214,7 @@ impl Generator { quote! { let size_length = <#size_type as Default>::default().aligned_size(); let archived = - unsafe { rkyv::access_unchecked::<<#size_type as Archive>::Archived>(&bytes[offset..offset + size_length]) }; + data_bucket::access_archived::<<#size_type as Archive>::Archived>(&bytes[offset..offset + size_length]).expect("torn or corrupt page part: a size field fails validation"); let #size_ident = rkyv::deserialize::<#size_type, rkyv::rancor::Error>(archived).expect("data should be valid"); offset += size_length; @@ -240,7 +240,7 @@ impl Generator { let length = <#ty as Default>::default().aligned_size(); let mut v = rkyv::util::AlignedVec::<4>::new(); v.extend_from_slice(&bytes[offset..offset + length]); - let archived = unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) }; + let archived = data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation"); let #ident = rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid"); offset += length; } @@ -283,7 +283,7 @@ impl Generator { let mut v = rkyv::util::AlignedVec::<4>::new(); v.extend_from_slice(&bytes[offset..offset + values_len]); let archived = - unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) }; + data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation"); let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived) .expect("data should be valid"); offset += values_len; @@ -313,7 +313,7 @@ impl Generator { let mut v = rkyv::util::AlignedVec::<4>::new(); v.extend_from_slice(&bytes[offset..offset + values_len]); let archived = - unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) }; + data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation"); let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived) .expect("data should be valid"); offset += values_len; @@ -343,7 +343,7 @@ impl Generator { let mut v = rkyv::util::AlignedVec::<4>::new(); v.extend_from_slice(&bytes[offset..offset + values_len]); let archived = - unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) }; + data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation"); let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived) .expect("data should be valid"); offset += values_len; diff --git a/src/lib.rs b/src/lib.rs index ca8fd96..199b56d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ extern crate core; +// The Persistable derive emits paths through the crate name, and this crate +// uses its own derive: alias ourselves so the generated code resolves here too. +extern crate self as data_bucket; + pub mod link; pub mod page; pub mod persistence; @@ -19,4 +23,5 @@ pub use page::{ }; pub use persistence::{PersistableIndex, PersistableTable}; pub use space::Id as SpaceId; +pub use util::access_archived; pub use util::{align, align8, align_vec, Persistable, SizeMeasurable, VariableSizeMeasurable}; diff --git a/src/page/index/page.rs b/src/page/index/page.rs index 974c6d7..d2f0b24 100644 --- a/src/page/index/page.rs +++ b/src/page/index/page.rs @@ -79,7 +79,8 @@ where Strategy, Share>, rkyv::rancor::Error>, > + Send + Sync, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, { type Utility = SizedIndexPageUtility; @@ -93,11 +94,12 @@ where let mut size_bytes = vec![0u8; SizedIndexPageUtility::::size_size()]; file.read_exact(size_bytes.as_mut_slice()).await?; - let archived = unsafe { - rkyv::access_unchecked::<::Archived>( - &size_bytes[0..SizedIndexPageUtility::::size_size()], - ) - }; + // Validated: this length field steers how much of the page is read + // as index entries, so a torn page must fail here, loudly. + let archived = crate::access_archived::<::Archived>( + &size_bytes[0..SizedIndexPageUtility::::size_size()], + ) + .map_err(|error| eyre::eyre!("torn or corrupt index page size field: {error}"))?; let size = rkyv::deserialize::(archived).expect("data should be valid"); @@ -160,14 +162,21 @@ impl IndexPage { async fn read_value(file: &mut File) -> eyre::Result> where T: Archive, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { let mut bytes = vec![0u8; IndexPage::::index_values_value_size()]; file.read_exact(bytes.as_mut_slice()).await?; let mut v = AlignedVec::<4>::new(); v.extend_from_slice(bytes.as_slice()); - let archived = - unsafe { rkyv::access_unchecked::< as Archive>::Archived>(&v[..]) }; + // Validated: a torn index entry must be an error, not a dangling link. + let archived = crate::access_archived::< as Archive>::Archived>(&v[..]) + .map_err(|error| eyre::eyre!("torn or corrupt index entry: {error}"))?; Ok(rkyv::deserialize(archived).expect("data should be valid")) } @@ -179,7 +188,13 @@ impl IndexPage { ) -> eyre::Result> where T: Archive, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { seek_to_page_start(file, page_id.0).await?; let offset = Self::get_value_offset(size, index); @@ -215,7 +230,13 @@ impl IndexPage { + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { seek_to_page_start(file, page_id.0).await?; @@ -249,7 +270,13 @@ impl IndexPage { + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { seek_to_page_start(file, page_id.0).await?; diff --git a/src/page/index/page_for_unsized.rs b/src/page/index/page_for_unsized.rs index e7d657d..573bc15 100644 --- a/src/page/index/page_for_unsized.rs +++ b/src/page/index/page_for_unsized.rs @@ -64,7 +64,8 @@ where Strategy, Share>, rkyv::rancor::Error>, > + Send + Sync, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, { type Utility = UnsizedIndexPageUtility; @@ -78,20 +79,22 @@ where let mut slot_size_bytes = vec![0u8; UnsizedIndexPageUtility::::slots_size_size()]; file.read_exact(slot_size_bytes.as_mut_slice()).await?; - let archived = unsafe { - rkyv::access_unchecked::<::Archived>( - &slot_size_bytes[0..UnsizedIndexPageUtility::::slots_size_size()], - ) - }; + // Validated: these two length fields steer every later read of the + // page, so a torn page must fail here rather than misdirect them. + let archived = crate::access_archived::<::Archived>( + &slot_size_bytes[0..UnsizedIndexPageUtility::::slots_size_size()], + ) + .map_err(|error| eyre::eyre!("torn or corrupt unsized index page (slots size): {error}"))?; let slots_size = rkyv::deserialize::(archived).expect("data should be valid"); let mut node_id_size_bytes = vec![0u8; UnsizedIndexPageUtility::::node_id_size_size()]; file.read_exact(node_id_size_bytes.as_mut_slice()).await?; - let archived = unsafe { - rkyv::access_unchecked::<::Archived>( - &node_id_size_bytes[0..UnsizedIndexPageUtility::::node_id_size_size()], - ) - }; + let archived = crate::access_archived::<::Archived>( + &node_id_size_bytes[0..UnsizedIndexPageUtility::::node_id_size_size()], + ) + .map_err(|error| { + eyre::eyre!("torn or corrupt unsized index page (node id size): {error}") + })?; let node_id_size = rkyv::deserialize::(archived).expect("data should be valid"); @@ -122,7 +125,8 @@ where + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, { pub fn new(node_id: IndexValue) -> eyre::Result { let len = node_id.aligned_size() as u32; @@ -203,7 +207,10 @@ where + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { // We seek to page's end and will write values from tail. seek_to_page_start(file, page_id.0 + 1).await?; @@ -219,14 +226,21 @@ where async fn read_value(file: &mut File, len: u16) -> eyre::Result> where T: Archive, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { let mut bytes = vec![0u8; len as usize]; file.read_exact(bytes.as_mut_slice()).await?; let mut v = AlignedVec::<4>::new(); v.extend_from_slice(bytes.as_slice()); - let archived = - unsafe { rkyv::access_unchecked::< as Archive>::Archived>(&v[..]) }; + // Validated: a torn index entry must be an error, not a dangling link. + let archived = crate::access_archived::< as Archive>::Archived>(&v[..]) + .map_err(|error| eyre::eyre!("torn or corrupt unsized index entry: {error}"))?; Ok(rkyv::deserialize(archived).expect("data should be valid")) } @@ -238,7 +252,13 @@ where ) -> eyre::Result> where T: Archive, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { seek_to_page_start(file, page_id.0 + 1).await?; file.seek(SeekFrom::Current(-(offset as i64))).await?; @@ -276,7 +296,11 @@ where + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { fn as_bytes(&self) -> impl AsRef<[u8]> + Send { let data_length = DATA_LENGTH as usize; @@ -305,16 +329,19 @@ where } fn from_bytes(bytes: &[u8], _version: u32) -> Self { + // Validated throughout: `from_bytes` has no error channel, so a torn + // page becomes a named panic here instead of undefined behavior in + // whatever walks the misread entries later. let slots_size_bytes = &bytes[0..UnsizedIndexPageUtility::::slots_size_size()]; - let archived = - unsafe { rkyv::access_unchecked::<::Archived>(slots_size_bytes) }; + let archived = crate::access_archived::<::Archived>(slots_size_bytes) + .expect("torn or corrupt unsized index page: slots size fails validation"); let slots_size = rkyv::deserialize::(archived).expect("data should be valid"); let node_id_size_bytes = &bytes[UnsizedIndexPageUtility::::slots_size_size() ..UnsizedIndexPageUtility::::node_id_size_size() + UnsizedIndexPageUtility::::node_id_size_size()]; - let archived = - unsafe { rkyv::access_unchecked::<::Archived>(node_id_size_bytes) }; + let archived = crate::access_archived::<::Archived>(node_id_size_bytes) + .expect("torn or corrupt unsized index page: node id size fails validation"); let node_id_size = rkyv::deserialize::(archived).expect("data should be valid"); let utility_len = UnsizedIndexPageUtility::::persisted_size( @@ -327,9 +354,9 @@ where let offset = bytes.len() - *offset as usize; let len = *len as usize; let value_bytes = &bytes[offset..(offset + len)]; - let archived = unsafe { - rkyv::access_unchecked::< as Archive>::Archived>(value_bytes) - }; + let archived = + crate::access_archived::< as Archive>::Archived>(value_bytes) + .expect("torn or corrupt unsized index page: an entry fails validation"); let val = rkyv::deserialize::<_, rkyv::rancor::Error>(archived) .expect("data should be valid"); index_values.push(val) diff --git a/src/page/index/page_for_unsized_cdc_impl.rs b/src/page/index/page_for_unsized_cdc_impl.rs index 33e29d4..2ecc4c3 100644 --- a/src/page/index/page_for_unsized_cdc_impl.rs +++ b/src/page/index/page_for_unsized_cdc_impl.rs @@ -26,7 +26,8 @@ where + for<'a> Serialize< Strategy, Share>, rkyv::rancor::Error>, >, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, { pub fn apply_change_event(&mut self, event: ChangeEvent>) -> eyre::Result<()> { match event { diff --git a/src/page/index/table_of_contents_page.rs b/src/page/index/table_of_contents_page.rs index 5488d19..0ee6988 100644 --- a/src/page/index/table_of_contents_page.rs +++ b/src/page/index/table_of_contents_page.rs @@ -49,6 +49,10 @@ where >, ::Archived: rkyv::Deserialize> + Ord, + as rkyv::Archive>::Archived: + for<'a> rkyv::bytecheck::CheckBytes< + rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>, + >, { fn as_bytes(&self) -> impl AsRef<[u8]> { let records = self @@ -64,9 +68,11 @@ where rkyv::to_bytes::(&model).unwrap() } fn from_bytes(bytes: &[u8], _version: u32) -> Self { - let archived = unsafe { - rkyv::access_unchecked::< as Archive>::Archived>(bytes) - }; + // Validated: the table of contents is the map every other read + // trusts, so a torn one must fail loudly here. + let archived = + crate::access_archived::< as Archive>::Archived>(bytes) + .expect("torn or corrupt table of contents page: the bytes fail validation"); let model: TableOfContentsPagePersisted = rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid"); let records = BTreeMap::from_iter(model.records); diff --git a/src/page/iterators.rs b/src/page/iterators.rs index aa89a98..8795c23 100644 --- a/src/page/iterators.rs +++ b/src/page/iterators.rs @@ -44,13 +44,14 @@ impl<'a> LinksIterator<'a> { fn parse_links(buffer: &[u8]) -> Vec where T: Archive, + as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes>, [ArchivedIndexValue]: DeserializeUnsized<[IndexValue], Strategy> { - let archived = unsafe { - rkyv::access_unchecked::< as Archive>::Archived>( - &buffer[..], - ) - }; + // Validated: these bytes are read straight off disk, and a torn index + // page must fail loudly here rather than dangle links into nowhere. + let archived = + crate::access_archived::< as Archive>::Archived>(&buffer[..]) + .expect("torn or corrupt index page: the archived bytes fail validation"); let index_records = rkyv::deserialize::, rkyv::rancor::Error>(archived) .expect("data should be valid") diff --git a/src/page/space_info.rs b/src/page/space_info.rs index 34ed961..67158af 100644 --- a/src/page/space_info.rs +++ b/src/page/space_info.rs @@ -116,8 +116,8 @@ where rkyv::rancor::Error, >, >, - ::Archived: - rkyv::Deserialize>, + ::Archived: rkyv::Deserialize> + + for<'a> rkyv::bytecheck::CheckBytes>, { fn as_bytes(&self) -> impl AsRef<[u8]> + Send { let v2 = SpaceInfoPageV2 { diff --git a/src/page/util.rs b/src/page/util.rs index 654f291..617cf23 100644 --- a/src/page/util.rs +++ b/src/page/util.rs @@ -148,10 +148,12 @@ pub async fn update_at( pub async fn parse_general_header(file: &mut File) -> eyre::Result { let mut buffer = [0; GENERAL_HEADER_SIZE]; file.read_exact(&mut buffer).await?; - let archived = - unsafe { rkyv::access_unchecked::<::Archived>(&buffer[..]) }; - let header = - rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid"); + // Validated: a header torn by a mid-write death must surface as an error + // naming the page, not as undefined behavior in whatever reads it next. + let archived = crate::access_archived::<::Archived>(&buffer[..]) + .map_err(|error| eyre::eyre!("torn or corrupt page header: {error}"))?; + let header = rkyv::deserialize::<_, rkyv::rancor::Error>(archived) + .map_err(|error| eyre::eyre!("page header failed to deserialize: {error}"))?; Ok(header) } diff --git a/src/util/mod.rs b/src/util/mod.rs index 84a1a24..2b05af2 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,5 @@ mod persistable; mod sized; -pub use persistable::Persistable; +pub use persistable::{access_archived, Persistable}; pub use sized::{align, align8, align_vec, SizeMeasurable, VariableSizeMeasurable}; diff --git a/src/util/persistable.rs b/src/util/persistable.rs index 951caaf..d9f1680 100644 --- a/src/util/persistable.rs +++ b/src/util/persistable.rs @@ -1,5 +1,7 @@ use crate::SizeMeasurable; +use rkyv::api::high::HighValidator; +use rkyv::bytecheck::CheckBytes; use rkyv::de::Pool; use rkyv::rancor::Strategy; use rkyv::ser::allocator::ArenaHandle; @@ -13,6 +15,52 @@ pub trait Persistable { fn from_bytes(bytes: &[u8], version: u32) -> Self; } +/* + * The one switch every disk read goes through. These bytes come off disk, + * and a process that died mid-write (crash, SIGKILL, an undrained exit) + * leaves torn pages behind: unchecked access reads a torn page as an + * archived value whose relative pointers dangle anywhere, and the process + * dies of SIGBUS in whatever touches them next, usually mid-write, tearing + * the store further. With the default `validate-reads` feature the same + * bytes become a named error at the parse site instead, while the store on + * disk stays exactly as readable as it was. + * + * `validate-reads` is a default feature rather than unconditional because + * this crate also runs at nanosecond scale, where even background-task CPU + * is budgeted: `default-features = false` compiles every read back to the + * exact `access_unchecked` it was before, zero cost, caveat emptor. The + * CheckBytes bounds stay unconditional either way so the API surface does + * not shift under a feature flag; derived Archive types satisfy them for + * free. + */ +#[inline] +pub fn access_archived(bytes: &[u8]) -> Result<&A, rkyv::rancor::Error> +where + A: rkyv::Portable + for<'a> CheckBytes>, +{ + #[cfg(feature = "validate-reads")] + { + rkyv::access::(bytes) + } + #[cfg(not(feature = "validate-reads"))] + { + Ok(unsafe { rkyv::access_unchecked::(bytes) }) + } +} + +pub(crate) fn checked(bytes: &[u8]) -> T +where + T: Archive, + ::Archived: rkyv::Portable + + for<'a> CheckBytes> + + Deserialize>, +{ + let archived = access_archived::<::Archived>(bytes) + .expect("torn or corrupt page: the archived bytes fail validation"); + rkyv::deserialize::<_, rkyv::rancor::Error>(archived) + .expect("validated archive failed to deserialize") +} + impl Persistable for Vec where T: Archive @@ -21,15 +69,15 @@ where > + Default + SizeMeasurable + Clone, - ::Archived: Deserialize>, + ::Archived: Deserialize> + + for<'a> CheckBytes>, { fn as_bytes(&self) -> impl AsRef<[u8]> { rkyv::to_bytes::(self).unwrap() } fn from_bytes(bytes: &[u8], _version: u32) -> Self { - let archived = unsafe { rkyv::access_unchecked::<::Archived>(bytes) }; - rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid") + checked::(bytes) } } @@ -39,8 +87,7 @@ impl Persistable for u8 { } fn from_bytes(bytes: &[u8], _version: u32) -> Self { - let archived = unsafe { rkyv::access_unchecked::<::Archived>(bytes) }; - rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid") + checked::(bytes) } } @@ -50,7 +97,6 @@ impl Persistable for String { } fn from_bytes(bytes: &[u8], _version: u32) -> Self { - let archived = unsafe { rkyv::access_unchecked::<::Archived>(bytes) }; - rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid") + checked::(bytes) } }