Skip to content

Commit 3033f18

Browse files
Aaron1011JohnTitor
andauthored
Enforce safety of downcast_ref at compile time. (#1326)
* Enforce safety of `downcast_ref` at compile time. The safety of `downcast_ref` requires that `__private_get_type_id__` not be overriden by callers, since the returned `TypeId` is used to check if the cast is safe. However, all trait methods in Rust are public, so users can override `__private_get_type_id__` despite it being `#[doc(hidden)]`. This commit makes `__private_get_type_id__` return a type with a private constructor, ensuring that the only possible implementation is the default implementation. A more detailed explanation is provided in the comments added to the file. Note that the standard library was affected by this type of issue with the `Error::type_id` function: see https://blog.rust-lang.org/2019/05/14/Rust-1.34.2.html#whats-in-1.34.2-stable Co-authored-by: Yuki Okushi <[email protected]>
1 parent 276a5a3 commit 3033f18

File tree

1 file changed

+27
-3
lines changed

1 file changed

+27
-3
lines changed

actix-http/src/error.rs

+27-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ impl Error {
6060
}
6161
}
6262

63+
/// A struct with a private constructor, for use with
64+
/// `__private_get_type_id__`. Its single field is private,
65+
/// ensuring that it can only be constructed from this module
66+
#[doc(hidden)]
67+
pub struct PrivateHelper(());
68+
6369
/// Error that can be converted to `Response`
6470
pub trait ResponseError: fmt::Debug + fmt::Display {
6571
/// Response's status code
@@ -83,19 +89,37 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
8389
resp.set_body(Body::from(buf))
8490
}
8591

92+
/// A helper method to get the type ID of the type
93+
/// this trait is implemented on.
94+
/// This method is unsafe to *implement*, since `downcast_ref` relies
95+
/// on the returned `TypeId` to perform a cast.
96+
///
97+
/// Unfortunately, Rust has no notion of a trait method that is
98+
/// unsafe to implement (marking it as `unsafe` makes it unsafe
99+
/// to *call*). As a workaround, we require this method
100+
/// to return a private type along with the `TypeId`. This
101+
/// private type (`PrivateHelper`) has a private constructor,
102+
/// making it impossible for safe code to construct outside of
103+
/// this module. This ensures that safe code cannot violate
104+
/// type-safety by implementing this method.
86105
#[doc(hidden)]
87-
fn __private_get_type_id__(&self) -> TypeId
106+
fn __private_get_type_id__(&self) -> (TypeId, PrivateHelper)
88107
where
89108
Self: 'static,
90109
{
91-
TypeId::of::<Self>()
110+
(TypeId::of::<Self>(), PrivateHelper(()))
92111
}
93112
}
94113

95114
impl dyn ResponseError + 'static {
96115
/// Downcasts a response error to a specific type.
97116
pub fn downcast_ref<T: ResponseError + 'static>(&self) -> Option<&T> {
98-
if self.__private_get_type_id__() == TypeId::of::<T>() {
117+
if self.__private_get_type_id__().0 == TypeId::of::<T>() {
118+
// Safety: external crates cannot override the default
119+
// implementation of `__private_get_type_id__`, since
120+
// it requires returning a private type. We can therefore
121+
// rely on the returned `TypeId`, which ensures that this
122+
// case is correct.
99123
unsafe { Some(&*(self as *const dyn ResponseError as *const T)) }
100124
} else {
101125
None

0 commit comments

Comments
 (0)