From 55bb4a1cc864347a0cf2874a803cc10581845768 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Mon, 6 Apr 2026 20:39:04 -0700 Subject: [PATCH] Add Clone support to AnyMap via type-erased clone functions Clone is not object-safe, so Box is not valid Rust. Instead, store a type-erased clone function pointer alongside each value that knows the concrete type and can clone through the trait object. This requires all types stored in AnyMap to implement Clone. Update the Clone bound on insert() and set_entry_metadata()/set_fd_metadata() in fd/mod.rs, and add #[derive(Clone)] to shim types that are stored as fd metadata: StdioStatusFlags, PipeStatusFlags, SocketOptions, SocketOFlags, SocketProxy. --- litebox/src/fd/mod.rs | 4 +- litebox/src/utilities/anymap.rs | 96 ++++++++++++++++++++++++-- litebox_shim_linux/src/lib.rs | 2 + litebox_shim_linux/src/syscalls/net.rs | 4 +- 4 files changed, 96 insertions(+), 10 deletions(-) diff --git a/litebox/src/fd/mod.rs b/litebox/src/fd/mod.rs index aeef757dd..d93da35b1 100644 --- a/litebox/src/fd/mod.rs +++ b/litebox/src/fd/mod.rs @@ -477,7 +477,7 @@ impl Descriptors { ) -> Option where Subsystem: FdEnabledSubsystem, - T: core::any::Any + Send + Sync, + T: core::any::Any + Clone + Send + Sync, { self.entries[fd.x.as_usize()?] .as_ref() @@ -506,7 +506,7 @@ impl Descriptors { ) -> Option where Subsystem: FdEnabledSubsystem, - T: core::any::Any + Send + Sync, + T: core::any::Any + Clone + Send + Sync, { self.entries[fd.x.as_usize()?] .as_mut() diff --git a/litebox/src/utilities/anymap.rs b/litebox/src/utilities/anymap.rs index 68af3f8a6..96253e8fb 100644 --- a/litebox/src/utilities/anymap.rs +++ b/litebox/src/utilities/anymap.rs @@ -18,14 +18,30 @@ use alloc::boxed::Box; use core::any::{Any, TypeId}; use hashbrown::HashMap; +/// Type-erased clone function stored alongside each value. +/// +/// We cannot use `Box` because `Clone` is not +/// object-safe (its `clone` method returns `Self`). Instead we store a +/// function pointer that knows the concrete type and can clone through the +/// trait object. +type CloneFn = fn(&(dyn Any + Send + Sync)) -> Box; + /// A safe store of exactly one value of any type `T`. pub(crate) struct AnyMap { // Invariant: the value at a particular typeid is guaranteed to be the correct type boxed up. - storage: HashMap>, + storage: HashMap, CloneFn)>, } const GUARANTEED: &str = "guaranteed correct type by invariant"; +/// Create a clone function for a specific concrete type. +fn make_clone_fn() -> CloneFn { + |val: &(dyn Any + Send + Sync)| -> Box { + let concrete = val.downcast_ref::().expect(GUARANTEED); + Box::new(concrete.clone()) + } +} + impl AnyMap { /// Create a new empty `AnyMap` pub(crate) fn new() -> Self { @@ -35,20 +51,26 @@ impl AnyMap { } /// Insert `v`, replacing and returning the old value if one existed already. - pub(crate) fn insert(&mut self, v: T) -> Option { - let old = self.storage.insert(TypeId::of::(), Box::new(v))?; - Some(*old.downcast().expect(GUARANTEED)) + /// + /// The `Clone` bound is required to capture a type-erased clone function + /// at insertion time. Read-only accessors (`get`, `get_mut`, `remove`) do + /// not require `Clone`. + pub(crate) fn insert(&mut self, v: T) -> Option { + let old = self + .storage + .insert(TypeId::of::(), (Box::new(v), make_clone_fn::()))?; + Some(*old.0.downcast().expect(GUARANTEED)) } /// Get a reference to a value of type `T` if it exists. pub(crate) fn get(&self) -> Option<&T> { - let v = self.storage.get(&TypeId::of::())?; + let v = &self.storage.get(&TypeId::of::())?.0; Some(v.downcast_ref().expect(GUARANTEED)) } /// Get a mutable reference to a value of type `T` if it exists. pub(crate) fn get_mut(&mut self) -> Option<&mut T> { - let v = self.storage.get_mut(&TypeId::of::())?; + let v = &mut self.storage.get_mut(&TypeId::of::())?.0; Some(v.downcast_mut().expect(GUARANTEED)) } @@ -58,7 +80,67 @@ impl AnyMap { )] /// Remove and return the value of type `T` if it exists. pub(crate) fn remove(&mut self) -> Option { - let v = self.storage.remove(&TypeId::of::())?; + let v = self.storage.remove(&TypeId::of::())?.0; Some(*v.downcast().expect(GUARANTEED)) } } + +impl Clone for AnyMap { + fn clone(&self) -> Self { + Self { + storage: self + .storage + .iter() + .map(|(&type_id, (val, clone_fn))| (type_id, (clone_fn(val.as_ref()), *clone_fn))) + .collect(), + } + } +} + +#[cfg(test)] +mod tests { + use alloc::string::String; + + use super::AnyMap; + + #[test] + fn insert_and_get() { + let mut map = AnyMap::new(); + assert!(map.insert(42u32).is_none()); + assert_eq!(map.get::(), Some(&42)); + } + + #[test] + fn clone_produces_independent_copy() { + let mut original = AnyMap::new(); + original.insert(10u32); + original.insert(String::from("hello")); + + let mut cloned = original.clone(); + + // Cloned values match. + assert_eq!(cloned.get::(), Some(&10)); + assert_eq!(cloned.get::().map(String::as_str), Some("hello")); + + // Mutating the clone does not affect the original. + *cloned.get_mut::().unwrap() = 99; + assert_eq!(original.get::(), Some(&10)); + assert_eq!(cloned.get::(), Some(&99)); + } + + #[test] + fn cloned_map_can_be_cloned_again() { + let mut map = AnyMap::new(); + map.insert(7u64); + let clone1 = map.clone(); + let clone2 = clone1.clone(); + assert_eq!(clone2.get::(), Some(&7)); + } + + #[test] + fn clone_empty_map() { + let map = AnyMap::new(); + let cloned = map.clone(); + assert_eq!(cloned.get::(), None); + } +} diff --git a/litebox_shim_linux/src/lib.rs b/litebox_shim_linux/src/lib.rs index 2834f7b72..2026a0f15 100644 --- a/litebox_shim_linux/src/lib.rs +++ b/litebox_shim_linux/src/lib.rs @@ -343,9 +343,11 @@ fn default_fs( } // Special override so that `GETFL` can return stdio-specific flags +#[derive(Clone)] pub(crate) struct StdioStatusFlags(litebox::fs::OFlags); /// Status flags for pipes +#[derive(Clone)] pub(crate) struct PipeStatusFlags(pub litebox::fs::OFlags); impl syscalls::file::FilesState { diff --git a/litebox_shim_linux/src/syscalls/net.rs b/litebox_shim_linux/src/syscalls/net.rs index d1b04e894..9192971c1 100644 --- a/litebox_shim_linux/src/syscalls/net.rs +++ b/litebox_shim_linux/src/syscalls/net.rs @@ -155,7 +155,7 @@ impl SocketAddress { } } -#[derive(Default)] +#[derive(Default, Clone)] pub(super) struct SocketOptions { pub(super) reuse_address: bool, pub(super) keep_alive: bool, @@ -171,7 +171,9 @@ pub(super) struct SocketOptions { pub(super) linger_timeout: Option, } +#[derive(Clone)] pub(crate) struct SocketOFlags(pub OFlags); +#[derive(Clone)] pub(crate) struct SocketProxy(pub Arc>); pub(super) enum SocketOptionValue {