-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Reduce the scope of unsafe and ways to cause UB #13257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Package Changes Through a472d51There are 8 changes which include tauri-bundler with minor, tauri with minor, tauri-cli with minor, tauri-codegen with minor, tauri-utils with minor, @tauri-apps/api with minor, @tauri-apps/cli with minor, tauri-runtime-wry with patch Planned Package VersionsThe following package releases are the planned based on the context of changes in this pull request.
Add another change file through the GitHub UI by following this link. Read about change files or the docs at github.com/jbolda/covector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think originally, the life time alone was enough since the state will be staying for as long as the StateManager
, but later on, with the introduction of unmanage
, this is no longer true, so now we need to wrap it inside an Arc
(#12745 or what this PR tries to do)
Personally, I agree with @WSH032 on this one, and I would probably think we should remove this function in v3
I personally believe that most people do not need the
unmanage
feature (it seems that tauri itself does not use it also); if users really needunmanage
, they can implement it themselves usingMutex
andOption::take
. This way, users who do not need mutability (unmanage
) do not have to pay the performance cost (useArc
instead ofBox
, andArc::clone
for everyget
).
https://github.com/tauri-apps/tauri/pull/12745/files#r1962817326
For now though, I think this is fine either ways (a safe implementation for unmanage
with a higher overhead vs the current unsafe implementation), just some suggestions on the implementation in this PR:
diff --git a/crates/tauri/src/state.rs b/crates/tauri/src/state.rs
index 37adc8464..69a03afe1 100644
--- a/crates/tauri/src/state.rs
+++ b/crates/tauri/src/state.rs
@@ -6,7 +6,6 @@ use std::{
any::{Any, TypeId},
collections::HashMap,
hash::BuildHasherDefault,
- marker::PhantomData,
sync::Arc,
sync::Mutex,
};
@@ -20,10 +19,7 @@ use crate::{
///
/// See [`Manager::manage`](`crate::Manager::manage`) for usage examples.
#[derive(Clone)]
-pub struct State<'r, T: Send + Sync + 'static> {
- _life: PhantomData<&'r T>,
- t: Arc<dyn Any + Send + Sync>,
-}
+pub struct State<'r, T: Send + Sync + 'static>(Arc<&'r T>);
impl<'r, T: Send + Sync + 'static> State<'r, T> {
/// Retrieve a borrow to the underlying value with a lifetime of `'r`.
@@ -31,13 +27,7 @@ impl<'r, T: Send + Sync + 'static> State<'r, T> {
/// [`std::ops::Deref`] with a [`std::ops::Deref::Target`] of `T`.
#[inline(always)]
pub fn inner(&self) -> &'r T {
- let x: &(dyn Any + Send + Sync) = &*self.t;
- // SAFETY: everything returned from this function must no longer live when fn unmanage() is called
- unsafe {
- &*(x
- .downcast_ref::<T>()
- .expect("the type of the key should be same as the type of the value") as *const T)
- }
+ *self.0
}
}
@@ -46,19 +36,19 @@ impl<T: Send + Sync + 'static> std::ops::Deref for State<'_, T> {
#[inline(always)]
fn deref(&self) -> &T {
- self.inner()
+ &self.0
}
}
impl<T: Send + Sync + 'static + PartialEq> PartialEq for State<'_, T> {
fn eq(&self, other: &Self) -> bool {
- self.t.downcast_ref::<T>() == other.t.downcast_ref::<T>()
+ self.0 == other.0
}
}
impl<T: Send + Sync + std::fmt::Debug> std::fmt::Debug for State<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_tuple("State").field(&self.t).finish()
+ f.debug_tuple("State").field(&self.0).finish()
}
}
@@ -154,10 +144,11 @@ impl StateManager {
let map = self.map.lock().unwrap();
let type_id = TypeId::of::<T>();
let state = map.get(&type_id)?;
- Some(State {
- _life: PhantomData,
- t: state.clone(),
- })
+ Some(State(
+ Arc::downcast::<&T>(state.clone())
+ // SAFETY: the type of the key is the same as the type of the value
+ .expect("the type of the key should be same as the type of the value"),
+ ))
}
}
also pinging @amrbashir since you did most of the reviews on the last one
I noticed that many pieces of code/plugins frequently call |
This downcast will fail, the type when it was inserted is let mut map: HashMap<TypeId, Arc<dyn Any + Send + Sync>> = HashMap::new();
map.insert(TypeId::of::<u8>(), Arc::new(0) as Arc<_>);
let arc = map.get(&TypeId::of::<u8>()).unwrap();
assert!(Arc::downcast::<&u8>(arc.clone()).is_err()); There is a way to get this to work with these types, but it defeats the purpose of this PR. Creating the However, it is a bit contorted, if only to reduce the scope of UB without changing the public API. I don't think the
I removed the |
You're right, ignore my suggestions then, I thought the downcast would work but apparently no |
This reduces the number of public API functions that can have bad interactions and cause UB.
This reduces it to only
fn inner()
andfn unmanage()
while previously any combination of methods onStateManager
were potentially perilous.