SerdeAnyMap: add unsafe_stable_anymap feature that uses type_name instead of TypeId::of (#1952)

* Test: Use type_name instead of type_id in AnyMap

* Hide behind 'unsafe_stable_anymap' feature

* nostd?

* rename fn

* cleanup a bit
This commit is contained in:
Dominik Maier 2024-03-19 20:15:31 +01:00 committed by GitHub
parent 5eab4fb78b
commit 2efa747292
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 144 additions and 122 deletions

View File

@ -63,6 +63,13 @@ xxh3 = ["xxhash-rust"]
#! ### SerdeAny features #! ### SerdeAny features
## With this feature, the AnyMap uses [`type_name`](https://doc.rust-lang.org/std/any/fn.type_name.html)
## instead of [`TypeId::of`](https://doc.rust-lang.org/std/any/struct.TypeId.html#method.of) for deserialization.
## With this feature, stored state may remain deserializable across multiple compilations of LibAFL.
## This is **unsafe** and may lead to type confusions. Only use when you know what you are doing/ you have tests in place.
## The rust doc specifically states that "multiple types may map to the same type name"!
unsafe_stable_anymap = []
## Automatically register all `#[derive(SerdeAny)]` types at startup. ## Automatically register all `#[derive(SerdeAny)]` types at startup.
serdeany_autoreg = ["ctor"] serdeany_autoreg = ["ctor"]

View File

@ -1,11 +1,54 @@
//! Poor-rust-man's downcasts for stuff we send over the wire (or shared maps) //! Poor-rust-man's downcasts for stuff we send over the wire (or shared maps)
use alloc::boxed::Box; use alloc::boxed::Box;
#[cfg(feature = "unsafe_stable_anymap")]
use alloc::string::{String, ToString};
#[cfg(feature = "unsafe_stable_anymap")]
use core::any::type_name;
#[cfg(not(feature = "unsafe_stable_anymap"))]
use core::any::TypeId;
use core::{any::Any, fmt::Debug}; use core::{any::Any, fmt::Debug};
use serde::{de::DeserializeSeed, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de::DeserializeSeed, Deserialize, Deserializer, Serialize, Serializer};
pub use serdeany_registry::*; pub use serdeany_registry::*;
#[cfg(not(feature = "unsafe_stable_anymap"))]
use crate::anymap::unpack_type_id;
/// The type of a stored type in this anymap (`u128`)
#[cfg(not(feature = "unsafe_stable_anymap"))]
pub type TypeRepr = u128;
/// The type of a stored type in this anymap (`String`)
#[cfg(feature = "unsafe_stable_anymap")]
pub type TypeRepr = String;
#[cfg(not(feature = "unsafe_stable_anymap"))]
fn type_repr<T>() -> TypeRepr
where
T: 'static,
{
unpack_type_id(TypeId::of::<T>())
}
#[cfg(not(feature = "unsafe_stable_anymap"))]
fn type_repr_owned<T>() -> TypeRepr
where
T: 'static,
{
unpack_type_id(TypeId::of::<T>())
}
#[cfg(feature = "unsafe_stable_anymap")]
fn type_repr_owned<T>() -> TypeRepr {
type_name::<T>().to_string()
}
#[cfg(feature = "unsafe_stable_anymap")]
fn type_repr<T>() -> &'static str {
type_name::<T>()
}
/// A (de)serializable Any trait /// A (de)serializable Any trait
pub trait SerdeAny: Any + erased_serde::Serialize + Debug { pub trait SerdeAny: Any + erased_serde::Serialize + Debug {
/// returns this as Any trait /// returns this as Any trait
@ -67,19 +110,18 @@ where
pub mod serdeany_registry { pub mod serdeany_registry {
use alloc::boxed::Box; use alloc::boxed::Box;
use core::{any::TypeId, fmt, hash::BuildHasherDefault}; use core::{fmt, hash::BuildHasherDefault};
use hashbrown::{ use hashbrown::{
hash_map::{Keys, Values, ValuesMut}, hash_map::{Values, ValuesMut},
HashMap, HashMap,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::SerdeAny; use super::{SerdeAny, TypeRepr};
use crate::{ use crate::{
anymap::{pack_type_id, unpack_type_id},
hash_std, hash_std,
serdeany::{DeserializeCallback, DeserializeCallbackSeed}, serdeany::{type_repr, type_repr_owned, DeserializeCallback, DeserializeCallbackSeed},
Error, Error,
}; };
@ -98,7 +140,7 @@ pub mod serdeany_registry {
where where
V: serde::de::SeqAccess<'de>, V: serde::de::SeqAccess<'de>,
{ {
let id: u128 = visitor.next_element()?.unwrap(); let id: TypeRepr = visitor.next_element()?.unwrap();
let cb = unsafe { let cb = unsafe {
*REGISTRY *REGISTRY
.deserializers .deserializers
@ -115,7 +157,8 @@ pub mod serdeany_registry {
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
struct Registry { struct Registry {
deserializers: Option<HashMap<u128, DeserializeCallback<dyn crate::serdeany::SerdeAny>>>, deserializers:
Option<HashMap<TypeRepr, DeserializeCallback<dyn crate::serdeany::SerdeAny>>>,
finalized: bool, finalized: bool,
} }
@ -128,7 +171,7 @@ pub mod serdeany_registry {
assert!(!self.finalized, "Registry is already finalized!"); assert!(!self.finalized, "Registry is already finalized!");
let deserializers = self.deserializers.get_or_insert_with(HashMap::default); let deserializers = self.deserializers.get_or_insert_with(HashMap::default);
deserializers.insert(unpack_type_id(TypeId::of::<T>()), |de| { deserializers.insert(type_repr_owned::<T>(), |de| {
Ok(Box::new(erased_serde::deserialize::<T>(de)?)) Ok(Box::new(erased_serde::deserialize::<T>(de)?))
}); });
} }
@ -181,7 +224,7 @@ pub mod serdeany_registry {
#[allow(clippy::unsafe_derive_deserialize)] #[allow(clippy::unsafe_derive_deserialize)]
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SerdeAnyMap { pub struct SerdeAnyMap {
map: HashMap<u128, Box<dyn SerdeAny>>, map: HashMap<TypeRepr, Box<dyn SerdeAny>>,
} }
// Cloning by serializing and deserializing. It ain't fast, but it's honest work. // Cloning by serializing and deserializing. It ain't fast, but it's honest work.
@ -218,8 +261,12 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
self.map self.map
.get(&unpack_type_id(TypeId::of::<T>())) .get(type_repr)
.map(|x| x.as_ref().as_any().downcast_ref::<T>().unwrap()) .map(|x| x.as_ref().as_any().downcast_ref::<T>().unwrap())
} }
@ -230,8 +277,12 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
self.map self.map
.get_mut(&unpack_type_id(TypeId::of::<T>())) .get_mut(type_repr)
.map(|x| x.as_mut().as_any_mut().downcast_mut::<T>().unwrap()) .map(|x| x.as_mut().as_any_mut().downcast_mut::<T>().unwrap())
} }
@ -242,8 +293,12 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
self.map self.map
.remove(&unpack_type_id(TypeId::of::<T>())) .remove(type_repr)
.map(|x| x.as_any_boxed().downcast::<T>().unwrap()) .map(|x| x.as_any_boxed().downcast::<T>().unwrap())
} }
@ -262,38 +317,42 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
self.entry::<T>().insert(value); self.raw_entry_mut::<T>()
.insert(type_repr_owned::<T>(), value);
} }
/// Get an entry to an element in this map. /// Get an entry to an element in this map.
#[inline] #[inline]
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
pub fn entry<T>( pub fn raw_entry_mut<T>(
&mut self, &mut self,
) -> hashbrown::hash_map::Entry< ) -> hashbrown::hash_map::RawEntryMut<
'_, '_,
u128, TypeRepr,
Box<dyn SerdeAny + 'static>, Box<dyn SerdeAny + 'static>,
BuildHasherDefault<ahash::AHasher>, BuildHasherDefault<ahash::AHasher>,
> >
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let id = unpack_type_id(TypeId::of::<T>()); let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
assert!( assert!(
unsafe { unsafe {
REGISTRY REGISTRY
.deserializers .deserializers
.as_ref() .as_ref()
.expect("Empty types registry") .expect("Empty types registry")
.get(&id) .get(type_repr)
.is_some() .is_some()
}, },
"Type {} was inserted without registration! Call RegistryBuilder::register::<{}>() or use serde_autoreg.", "Type {} was inserted without registration! Call RegistryBuilder::register::<{}>() or use serde_autoreg.",
core::any::type_name::<T>(), core::any::type_name::<T>(),
core::any::type_name::<T>() core::any::type_name::<T>()
); );
self.map.entry(id) self.map.raw_entry_mut().from_key(type_repr)
} }
/// Gets a value by type, or inserts it using the given construction function `default` /// Gets a value by type, or inserts it using the given construction function `default`
@ -309,8 +368,10 @@ pub mod serdeany_registry {
where where
T: SerdeAny + 'static, T: SerdeAny + 'static,
{ {
let ret = self.entry::<T>().or_insert_with(|| default()); let ret = self
ret.as_mut().as_any_mut().downcast_mut::<T>().unwrap() .raw_entry_mut::<T>()
.or_insert_with(|| (type_repr_owned::<T>(), default()));
ret.1.as_any_mut().downcast_mut::<T>().unwrap()
} }
/// Returns the count of elements in this map. /// Returns the count of elements in this map.
@ -333,7 +394,11 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
self.map.contains_key(&unpack_type_id(TypeId::of::<T>())) let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
self.map.contains_key(type_repr)
} }
/// Create a new [`SerdeAnyMap`]. /// Create a new [`SerdeAnyMap`].
@ -356,7 +421,7 @@ pub mod serdeany_registry {
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct NamedSerdeAnyMap { pub struct NamedSerdeAnyMap {
map: HashMap<u128, HashMap<u64, Box<dyn crate::serdeany::SerdeAny>>>, map: HashMap<TypeRepr, HashMap<u64, Box<dyn crate::serdeany::SerdeAny>>>,
} }
// Cloning by serializing and deserializing. It ain't fast, but it's honest work. // Cloning by serializing and deserializing. It ain't fast, but it's honest work.
@ -377,7 +442,11 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
match self.map.get(&unpack_type_id(TypeId::of::<T>())) { let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
match self.map.get(type_repr) {
None => None, None => None,
Some(h) => h Some(h) => h
.get(&hash_std(name.as_bytes())) .get(&hash_std(name.as_bytes()))
@ -392,7 +461,11 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
match self.map.get_mut(&unpack_type_id(TypeId::of::<T>())) { let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
match self.map.get_mut(type_repr) {
None => None, None => None,
Some(h) => h Some(h) => h
.remove(&hash_std(name.as_bytes())) .remove(&hash_std(name.as_bytes()))
@ -400,29 +473,18 @@ pub mod serdeany_registry {
} }
} }
/// Get an element of a given type contained in this map by [`TypeId`]. /// Get an element of a given type contained in this map by type `T`, as mut.
#[must_use]
#[allow(unused_qualifications)]
#[inline]
pub fn by_typeid(
&self,
name: &str,
typeid: &TypeId,
) -> Option<&dyn crate::serdeany::SerdeAny> {
match self.map.get(&unpack_type_id(*typeid)) {
None => None,
Some(h) => h.get(&hash_std(name.as_bytes())).map(AsRef::as_ref),
}
}
/// Get an element of a given type contained in this map by [`TypeId`], as mut.
#[must_use] #[must_use]
#[inline] #[inline]
pub fn get_mut<T>(&mut self, name: &str) -> Option<&mut T> pub fn get_mut<T>(&mut self, name: &str) -> Option<&mut T>
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
match self.map.get_mut(&unpack_type_id(TypeId::of::<T>())) { let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
match self.map.get_mut(type_repr) {
None => None, None => None,
Some(h) => h Some(h) => h
.get_mut(&hash_std(name.as_bytes())) .get_mut(&hash_std(name.as_bytes()))
@ -430,20 +492,6 @@ pub mod serdeany_registry {
} }
} }
/// Get an element of a given type contained in this map by [`TypeId`], as mut.
#[must_use]
#[inline]
pub fn by_typeid_mut(
&mut self,
name: &str,
typeid: &TypeId,
) -> Option<&mut dyn crate::serdeany::SerdeAny> {
match self.map.get_mut(&unpack_type_id(*typeid)) {
None => None,
Some(h) => h.get_mut(&hash_std(name.as_bytes())).map(AsMut::as_mut),
}
}
/// Get all elements of a type contained in this map. /// Get all elements of a type contained in this map.
#[must_use] #[must_use]
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
@ -460,34 +508,17 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
#[allow(clippy::manual_map)] #[allow(clippy::manual_map)]
match self.map.get(&unpack_type_id(TypeId::of::<T>())) { match self.map.get(type_repr) {
None => None, None => None,
Some(h) => Some(h.values().map(|x| x.as_any().downcast_ref::<T>().unwrap())), Some(h) => Some(h.values().map(|x| x.as_any().downcast_ref::<T>().unwrap())),
} }
} }
/// Get all elements of a given type contained in this map by [`TypeId`].
#[must_use]
#[allow(unused_qualifications)]
#[inline]
#[allow(clippy::type_complexity)]
pub fn all_by_typeid(
&self,
typeid: &TypeId,
) -> Option<
core::iter::Map<
Values<'_, u64, Box<dyn crate::serdeany::SerdeAny>>,
fn(&Box<dyn crate::serdeany::SerdeAny>) -> &dyn crate::serdeany::SerdeAny,
>,
> {
#[allow(clippy::manual_map)]
match self.map.get(&unpack_type_id(*typeid)) {
None => None,
Some(h) => Some(h.values().map(|x| x.as_ref())),
}
}
/// Get all elements contained in this map, as mut. /// Get all elements contained in this map, as mut.
#[inline] #[inline]
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
@ -503,8 +534,12 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
#[allow(clippy::manual_map)] #[allow(clippy::manual_map)]
match self.map.get_mut(&unpack_type_id(TypeId::of::<T>())) { match self.map.get_mut(type_repr) {
None => None, None => None,
Some(h) => Some( Some(h) => Some(
h.values_mut() h.values_mut()
@ -513,51 +548,18 @@ pub mod serdeany_registry {
} }
} }
/// Get all [`TypeId`]`s` contained in this map, as mut.
#[inline]
#[allow(unused_qualifications)]
#[allow(clippy::type_complexity)]
pub fn all_by_typeid_mut(
&mut self,
typeid: &TypeId,
) -> Option<
core::iter::Map<
ValuesMut<'_, u64, Box<dyn crate::serdeany::SerdeAny>>,
fn(&mut Box<dyn crate::serdeany::SerdeAny>) -> &mut dyn crate::serdeany::SerdeAny,
>,
> {
#[allow(clippy::manual_map)]
match self.map.get_mut(&unpack_type_id(*typeid)) {
None => None,
Some(h) => Some(h.values_mut().map(|x| x.as_mut())),
}
}
/// Get all [`TypeId`]`s` contained in this map.
#[inline]
#[allow(unused_qualifications)]
#[allow(clippy::type_complexity)]
pub fn all_typeids(
&self,
) -> core::iter::Map<
Keys<'_, u128, HashMap<u64, Box<dyn crate::serdeany::SerdeAny>>>,
fn(&u128) -> TypeId,
> {
self.map.keys().map(|x| pack_type_id(*x))
}
/// Run `func` for each element in this map. /// Run `func` for each element in this map.
#[inline] #[inline]
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
pub fn for_each< pub fn for_each<
F: FnMut(&TypeId, &Box<dyn crate::serdeany::SerdeAny>) -> Result<(), Error>, F: FnMut(&TypeRepr, &Box<dyn crate::serdeany::SerdeAny>) -> Result<(), Error>,
>( >(
&self, &self,
func: &mut F, func: &mut F,
) -> Result<(), Error> { ) -> Result<(), Error> {
for (id, h) in &self.map { for (id, h) in &self.map {
for x in h.values() { for x in h.values() {
func(&pack_type_id(*id), x)?; func(id, x)?;
} }
} }
Ok(()) Ok(())
@ -566,14 +568,14 @@ pub mod serdeany_registry {
/// Run `func` for each element in this map, getting a mutable borrow. /// Run `func` for each element in this map, getting a mutable borrow.
#[inline] #[inline]
pub fn for_each_mut< pub fn for_each_mut<
F: FnMut(&TypeId, &mut Box<dyn crate::serdeany::SerdeAny>) -> Result<(), Error>, F: FnMut(&TypeRepr, &mut Box<dyn crate::serdeany::SerdeAny>) -> Result<(), Error>,
>( >(
&mut self, &mut self,
func: &mut F, func: &mut F,
) -> Result<(), Error> { ) -> Result<(), Error> {
for (id, h) in &mut self.map { for (id, h) in &mut self.map {
for x in h.values_mut() { for x in h.values_mut() {
func(&pack_type_id(*id), x)?; func(id, x)?;
} }
} }
Ok(()) Ok(())
@ -604,14 +606,17 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
let id = unpack_type_id(TypeId::of::<T>()); let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
assert!( assert!(
unsafe { unsafe {
REGISTRY REGISTRY
.deserializers .deserializers
.as_ref() .as_ref()
.expect("Empty types registry") .expect("Empty types registry")
.get(&id) .get(type_repr)
.is_some() .is_some()
}, },
"Type {} was inserted without registration! Call RegistryBuilder::register::<{}>() or use serde_autoreg.", "Type {} was inserted without registration! Call RegistryBuilder::register::<{}>() or use serde_autoreg.",
@ -619,8 +624,10 @@ pub mod serdeany_registry {
core::any::type_name::<T>() core::any::type_name::<T>()
); );
self.map self.map
.entry(id) .raw_entry_mut()
.or_default() .from_key(type_repr)
.or_insert_with(|| (type_repr_owned::<T>(), HashMap::default()))
.1
.entry(hash_std(name.as_bytes())) .entry(hash_std(name.as_bytes()))
} }
@ -666,7 +673,11 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
self.map.contains_key(&unpack_type_id(TypeId::of::<T>())) let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
self.map.contains_key(type_repr)
} }
/// Returns if the element by a given `name` is contained in this map. /// Returns if the element by a given `name` is contained in this map.
@ -676,7 +687,11 @@ pub mod serdeany_registry {
where where
T: crate::serdeany::SerdeAny, T: crate::serdeany::SerdeAny,
{ {
match self.map.get(&unpack_type_id(TypeId::of::<T>())) { let type_repr = type_repr::<T>();
#[cfg(not(feature = "unsafe_stable_anymap"))]
let type_repr = &type_repr;
match self.map.get(type_repr) {
None => false, None => false,
Some(h) => h.contains_key(&hash_std(name.as_bytes())), Some(h) => h.contains_key(&hash_std(name.as_bytes())),
} }