Skip to content

Commit f5aa3f3

Browse files
authored
Merge pull request #8 from Ivy-Interactive/tendril/00246-ReplaceRawPointersInBuildContextWithSafeAlternative
[00246] Replace raw pointers in BuildContext with safe alternative
2 parents 687241e + 898f675 commit f5aa3f3

5 files changed

Lines changed: 93 additions & 25 deletions

File tree

rusty/src/core/runtime.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,15 @@ impl Runtime {
8686
let mut ctx = BuildContext::with_view_id(store, Some(rebuild_tx), view_id);
8787
ctx.reset();
8888

89-
// Build the view
90-
let view_node = self.view_tree.get(&view_id).expect("view_id not in tree");
91-
// SAFETY: We need to borrow view immutably while store is borrowed mutably.
92-
// The view reference does not alias with the store reference.
93-
let view_ptr = &*view_node.view as *const dyn View;
94-
let element = unsafe { &*view_ptr }.build(&mut ctx);
89+
// Clone the Arc<dyn View> so we can borrow view immutably while
90+
// store is borrowed mutably — no raw pointer needed.
91+
let view_clone = self
92+
.view_tree
93+
.get(&view_id)
94+
.expect("view_id not in tree")
95+
.view
96+
.clone();
97+
let element = view_clone.build(&mut ctx);
9598

9699
let mut element = element;
97100
element.assign_ids(&mut ctx);

rusty/src/core/view_tree.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::collections::HashMap;
2+
use std::sync::Arc;
23

34
use crate::shared::ViewId;
45
use crate::views::view::View;
56

67
/// A node in the view tree, representing a single view with its parent/child relationships.
78
pub struct ViewNode {
89
pub view_id: ViewId,
9-
pub view: Box<dyn View>,
10+
pub view: Arc<dyn View>,
1011
pub parent: Option<ViewId>,
1112
pub children: Vec<ViewId>,
1213
}
@@ -28,7 +29,7 @@ impl ViewTree {
2829
let root_id = uuid::Uuid::new_v4();
2930
let root_node = ViewNode {
3031
view_id: root_id,
31-
view: root_view,
32+
view: Arc::from(root_view),
3233
parent: None,
3334
children: Vec::new(),
3435
};
@@ -47,7 +48,7 @@ impl ViewTree {
4748
let child_id = uuid::Uuid::new_v4();
4849
let child_node = ViewNode {
4950
view_id: child_id,
50-
view,
51+
view: Arc::from(view),
5152
parent: Some(parent_id),
5253
children: Vec::new(),
5354
};
@@ -67,7 +68,7 @@ impl ViewTree {
6768
) -> ViewId {
6869
let child_node = ViewNode {
6970
view_id: child_id,
70-
view,
71+
view: Arc::from(view),
7172
parent: Some(parent_id),
7273
children: Vec::new(),
7374
};

rusty/src/hooks/hook_store.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::any::Any;
22
use std::collections::HashMap;
3+
use std::sync::Arc;
34

45
use super::deps::DynEq;
56
use crate::views::view::EffectCleanup;
@@ -33,8 +34,9 @@ pub struct HookStore {
3334
pub effects: HashMap<usize, EffectEntry>,
3435
/// Cached memo values keyed by hook index.
3536
pub memos: HashMap<usize, MemoEntry>,
36-
/// Context values keyed by TypeId name (for use_context).
37-
pub contexts: HashMap<std::any::TypeId, Box<dyn Any + Send + Sync>>,
37+
/// Context values keyed by TypeId (for use_context). Uses `Arc` so ancestor
38+
/// context snapshots can be cheaply cloned without raw pointers.
39+
pub contexts: HashMap<std::any::TypeId, Arc<dyn Any + Send + Sync>>,
3840
}
3941

4042
impl HookStore {

rusty/src/hooks/use_context.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::views::view::BuildContext;
22
use std::any::TypeId;
3+
use std::sync::Arc;
34

45
/// Register a context value of type `T` for descendant views to access.
56
///
@@ -8,7 +9,7 @@ use std::any::TypeId;
89
/// any descendant that calls `use_context::<T>()`.
910
pub fn create_context<T: Send + Sync + Clone + 'static>(ctx: &mut BuildContext, value: T) {
1011
let type_id = TypeId::of::<T>();
11-
ctx.store.contexts.insert(type_id, Box::new(value));
12+
ctx.store.contexts.insert(type_id, Arc::new(value));
1213
}
1314

1415
/// Retrieve a context value of type `T` from the current view or an ancestor's context.
@@ -80,4 +81,62 @@ mod tests {
8081
let mut ctx = BuildContext::new(&mut store, None);
8182
let _: Theme = use_context(&mut ctx);
8283
}
84+
85+
#[test]
86+
fn test_ancestor_context_snapshot_isolation() {
87+
use crate::views::view::{Element, View};
88+
use crate::widgets::text::TextBlock;
89+
use std::sync::Mutex;
90+
91+
static CHILD_RESULT: Mutex<Option<String>> = Mutex::new(None);
92+
93+
struct ChildView;
94+
impl View for ChildView {
95+
fn build(&self, ctx: &mut BuildContext) -> Element {
96+
let theme: Theme = use_context(ctx);
97+
*CHILD_RESULT.lock().unwrap() = Some(theme.primary_color.clone());
98+
Element::Widget(Box::new(TextBlock::new(&theme.primary_color)))
99+
}
100+
}
101+
102+
let mut parent_store = HookStore::new();
103+
let parent_view_id = uuid::Uuid::new_v4();
104+
let mut ctx = BuildContext::with_view_id(&mut parent_store, None, parent_view_id);
105+
106+
// Parent creates context with "blue"
107+
create_context(
108+
&mut ctx,
109+
Theme {
110+
primary_color: "blue".to_string(),
111+
},
112+
);
113+
114+
// Child reads the context — gets a snapshot
115+
let (_element, _child_id, _child_store) = ctx.child_view(ChildView, None);
116+
let child_saw = CHILD_RESULT.lock().unwrap().take().unwrap();
117+
assert_eq!(child_saw, "blue");
118+
119+
// Parent modifies its context to "red" after child was built
120+
create_context(
121+
&mut ctx,
122+
Theme {
123+
primary_color: "red".to_string(),
124+
},
125+
);
126+
127+
// Verify parent's store changed
128+
let parent_theme = ctx
129+
.store
130+
.contexts
131+
.get(&TypeId::of::<Theme>())
132+
.unwrap()
133+
.downcast_ref::<Theme>()
134+
.unwrap();
135+
assert_eq!(parent_theme.primary_color, "red");
136+
137+
// Build another child — it should see "red" since snapshot is taken at child_view() time
138+
let (_element2, _child_id2, _child_store2) = ctx.child_view(ChildView, None);
139+
let child2_saw = CHILD_RESULT.lock().unwrap().take().unwrap();
140+
assert_eq!(child2_saw, "red");
141+
}
83142
}

rusty/src/views/view.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use serde::{Deserialize, Serialize};
2-
use std::any::Any;
2+
use std::any::{Any, TypeId};
33
use std::collections::hash_map::DefaultHasher;
4+
use std::collections::HashMap;
45
use std::fmt::Debug;
56
use std::hash::{Hash, Hasher};
7+
use std::sync::Arc;
68

79
use crate::core::event_registry::{EventCallback, EventRegistry};
810
use crate::hooks::hook_store::HookStore;
@@ -112,6 +114,9 @@ impl Element {
112114
}
113115
}
114116

117+
/// A snapshot of a view's context map, cheaply clonable via `Arc`.
118+
pub type ContextSnapshot = HashMap<TypeId, Arc<dyn Any + Send + Sync>>;
119+
115120
/// A stateful component that produces an element tree.
116121
pub trait View: Send + Sync + 'static {
117122
/// Build the element tree for this view.
@@ -134,9 +139,9 @@ pub struct BuildContext<'a> {
134139
widget_id_counter: usize,
135140
/// Child views registered during this build via `child_view()`.
136141
pub(crate) child_views: Vec<ChildViewEntry>,
137-
/// Access to ancestor HookStores for context lookup (read-only view).
138-
/// Maps ViewId -> reference to ancestor contexts.
139-
pub(crate) ancestor_contexts: Vec<(ViewId, *const HookStore)>,
142+
/// Cloned snapshots of ancestor context maps for safe context lookup.
143+
/// Each entry is a (ViewId, context map) pair, cheaply cloned via Arc.
144+
pub(crate) ancestor_contexts: Vec<(ViewId, ContextSnapshot)>,
140145
}
141146

142147
/// Cleanup function returned by an effect callback.
@@ -292,11 +297,12 @@ impl<'a> BuildContext<'a> {
292297
let mut child_ctx =
293298
BuildContext::with_view_id(&mut owned_store, self.rebuild_tx.clone(), child_view_id);
294299
child_ctx.reset();
295-
// Set ancestor contexts: current view's store + all ancestors above
300+
// Set ancestor contexts: current view's store + all ancestors above.
301+
// Clone via Arc::clone per entry (cheap reference count bump).
296302
child_ctx.ancestor_contexts = self.ancestor_contexts.clone();
297303
child_ctx
298304
.ancestor_contexts
299-
.push((self.current_view_id, self.store as *const HookStore));
305+
.push((self.current_view_id, self.store.contexts.clone()));
300306

301307
let mut element = view.build(&mut child_ctx);
302308
element.assign_ids(&mut child_ctx);
@@ -329,12 +335,9 @@ impl<'a> BuildContext<'a> {
329335
if let Some(val) = self.store.contexts.get(&type_id) {
330336
return Some(val.as_ref());
331337
}
332-
// Walk ancestors from nearest to farthest
333-
for (_view_id, store_ptr) in self.ancestor_contexts.iter().rev() {
334-
// SAFETY: ancestor_contexts are set up during build traversal and the
335-
// stores remain valid for the duration of the build
336-
let store = unsafe { &**store_ptr };
337-
if let Some(val) = store.contexts.get(&type_id) {
338+
// Walk ancestors from nearest to farthest (safe — no raw pointers)
339+
for (_view_id, contexts) in self.ancestor_contexts.iter().rev() {
340+
if let Some(val) = contexts.get(&type_id) {
338341
return Some(val.as_ref());
339342
}
340343
}

0 commit comments

Comments
 (0)