Skip to content

Commit 108f540

Browse files
committed
Fix unsoundness in conversion functions
- Fix unsoundness in `project`, `from_(a)rc` and `try_from_(a)rc` by adding the missing lifetime constraints. - Add doctests that will test that the offending examples don't compile - Bump version to 0.4.0 due to this breaking the public API - Fix new warnings with newer Rust versions
1 parent 65bbc62 commit 108f540

File tree

9 files changed

+143
-15
lines changed

9 files changed

+143
-15
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.4.0
4+
- Fix unsoundness in `Parc::project` and `Prc::project` due to lifetime coersion and a missing
5+
lifetime constraint.
6+
- Fix unsoudness in `Parc::from_arc`, `Parc::try_from_arc`, `Prc::from_rc`, and `Prc::try_from_rc`
7+
due to a missing lifetime constraint on the `Arc`/`Rc` parameter.
8+
39
## 0.3.0
410
- Change the API of `try_*` functions on `Parc` and `Prc` to take functions returning `Result` instead of `Option` and make them return `Result` instead of `Option`.
511

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pared"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
authors = ["Radek Vít <[email protected]>"]
55
edition = "2021"
66
rust-version = "1.56"
@@ -14,6 +14,9 @@ categories = ["data-structures", "memory-management", "no-std", "rust-patterns"]
1414
default = ["std"]
1515
std = []
1616

17+
[lints.rust]
18+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
19+
1720
[dependencies]
1821

1922
[dev-dependencies]

src/erased_ptr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ mod tests {
8181
#[cfg_attr(coverage_nightly, coverage(off))]
8282
fn dyn_ptr() {
8383
// We want to check that the pointers actually ARE compatible
84-
#![allow(clippy::vtable_address_comparisons)]
84+
#![allow(ambiguous_wide_pointer_comparisons)]
8585

8686
let debug: &dyn core::fmt::Debug = &"Hello!";
8787
let ptr = TypeErasedPtr::new(debug as *const dyn core::fmt::Debug);
@@ -103,6 +103,6 @@ mod tests {
103103
#[cfg_attr(coverage_nightly, coverage(off))]
104104
fn debug() {
105105
let ptr = TypeErasedPtr::new(&1);
106-
format!("{:?}", ptr);
106+
let _msg = format!("{:?}", ptr);
107107
}
108108
}

src/prc.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,32 @@ impl<T: ?Sized> Prc<T> {
159159
/// let local = 5;
160160
/// let prc = Prc::from_rc(&rc, |_| &local);
161161
/// ```
162+
///
163+
/// Do not allow calling `from_rc` on `Rc`s that don't own their data.
164+
/// If this is allowed to happen, we could create a `Prc` with a longer lifetime than the
165+
/// original `Rc`.
166+
/// ```compile_fail,E0597
167+
/// use pared::prc::Prc;
168+
/// use std::rc::Rc;
169+
///
170+
/// struct PrintOnDrop<'a>(&'a str);
171+
/// impl Drop for PrintOnDrop<'_> {
172+
/// fn drop(&mut self) {
173+
/// println!("dropping: {:?}", self.0);
174+
/// }
175+
/// }
176+
///
177+
/// let s = "Hello World!".to_owned();
178+
/// let rc = Rc::new(PrintOnDrop(&s));
179+
/// let p = Prc::from_rc(&rc, |_| &());
180+
/// drop(rc);
181+
/// drop(s);
182+
/// drop(p); // garbage / use-after-free if `from_rc` compiles
183+
/// ```
162184
#[inline]
163185
pub fn from_rc<U, F>(rc: &Rc<U>, project: F) -> Self
164186
where
165-
U: ?Sized,
187+
U: ?Sized + 'static,
166188
T: 'static,
167189
F: FnOnce(&U) -> &T,
168190
{
@@ -201,10 +223,32 @@ impl<T: ?Sized> Prc<T> {
201223
///
202224
/// assert!(matches!(prc, Ok(prc) if *prc == 5 ));
203225
/// ```
226+
///
227+
/// Do not allow calling `from_rc` on `Rc`s that don't own their data.
228+
/// If this is allowed to happen, we could create a `Prc` with a longer lifetime than the
229+
/// original `Rc`.
230+
/// ```compile_fail,E0597
231+
/// use pared::prc::Prc;
232+
/// use std::rc::Rc;
233+
///
234+
/// struct PrintOnDrop<'a>(&'a str);
235+
/// impl Drop for PrintOnDrop<'_> {
236+
/// fn drop(&mut self) {
237+
/// println!("dropping: {:?}", self.0);
238+
/// }
239+
/// }
240+
///
241+
/// let s = "Hello World!".to_owned();
242+
/// let rc = Rc::new(PrintOnDrop(&s));
243+
/// let p = Prc::try_from_rc(&rc, |_| Ok::<&'static(), ()>(&()));
244+
/// drop(rc);
245+
/// drop(s);
246+
/// drop(p); // garbage / use-after-free if `try_from_rc` compiles
247+
/// ```
204248
#[inline]
205249
pub fn try_from_rc<U, E, F>(rc: &Rc<U>, project: F) -> Result<Self, E>
206250
where
207-
U: ?Sized,
251+
U: ?Sized + 'static,
208252
T: 'static,
209253
F: FnOnce(&U) -> Result<&T, E>,
210254
{
@@ -237,10 +281,26 @@ impl<T: ?Sized> Prc<T> {
237281
/// let local = 5;
238282
/// let projected = prc.project(|_| &local);
239283
/// ```
284+
///
285+
/// /// Do not allow coercing `Prc<&'static T>` to `Prc<&'short T>` when projecting:
286+
/// ```compile_fail,E0597
287+
/// use pared::prc::Prc;
288+
///
289+
/// let s = "Hello World!".to_owned();
290+
/// // x: Prc<&'static ()>
291+
/// let x = Prc::new(&());
292+
/// // This must fail
293+
/// let x = x.project(|_| s.as_str());
294+
///
295+
/// println!("{:?}", &*x); // "Hello World!"
296+
/// drop(s);
297+
/// println!("{:?}", &*x); // garbage / use-after-free if the above doesn't fail
298+
/// ```
240299
#[inline]
241300
pub fn project<U, F>(&self, project: F) -> Prc<U>
242301
where
243302
U: ?Sized + 'static,
303+
T: 'static,
244304
F: FnOnce(&T) -> &U,
245305
{
246306
let projected = project(self);

src/sync.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,34 @@ impl<T: ?Sized> Parc<T> {
204204
/// let local = 5;
205205
/// let parc = Parc::from_arc(&arc, |tuple| &local);
206206
/// ```
207+
///
208+
/// Do not allow calling `from_arc` on `Arc`s that don't own their data.
209+
/// If this is allowed to happen, we could create a `Parc` with a longer lifetime than the
210+
/// original `Arc`.
211+
/// ```compile_fail,E0597
212+
/// use pared::sync::Parc;
213+
/// use std::sync::Arc;
214+
///
215+
/// struct PrintOnDrop<'a>(&'a str);
216+
/// impl Drop for PrintOnDrop<'_> {
217+
/// fn drop(&mut self) {
218+
/// println!("dropping: {:?}", self.0);
219+
/// }
220+
/// }
221+
///
222+
/// let s = "Hello World!".to_owned();
223+
/// let arc = Arc::new(PrintOnDrop(&s));
224+
/// let p = Parc::from_arc(&arc, |_| &());
225+
/// drop(arc);
226+
/// drop(s);
227+
/// drop(p); // garbage / use-after-free if `from_arc` compiles
228+
/// ```
207229
#[inline]
208230
pub fn from_arc<U, F>(arc: &Arc<U>, project: F) -> Self
209231
where
210-
T: 'static,
211-
U: ?Sized + Send + Sync,
232+
U: ?Sized + Send + Sync + 'static,
212233
F: FnOnce(&U) -> &T,
234+
T: 'static,
213235
{
214236
let projected = project(arc);
215237
// SAFETY: the returned reference always converts to a non-null pointer.
@@ -249,10 +271,32 @@ impl<T: ?Sized> Parc<T> {
249271
///
250272
/// assert!(matches!(parc, Ok(parc) if *parc == 5 ));
251273
/// ```
274+
///
275+
/// Do not allow calling `try_from_arc` on `Arc`s that don't own their data.
276+
/// If this is allowed to happen, we could create a `Parc` with a longer lifetime than the
277+
/// original `Arc`.
278+
/// ```compile_fail,E0597
279+
/// use pared::sync::Parc;
280+
/// use std::sync::Arc;
281+
///
282+
/// struct PrintOnDrop<'a>(&'a str);
283+
/// impl Drop for PrintOnDrop<'_> {
284+
/// fn drop(&mut self) {
285+
/// println!("dropping: {:?}", self.0);
286+
/// }
287+
/// }
288+
///
289+
/// let s = "Hello World!".to_owned();
290+
/// let arc = Arc::new(PrintOnDrop(&s));
291+
/// let p = Parc::try_from_arc(&arc, |_| Ok::<&'static (), ()>(&()));
292+
/// drop(arc);
293+
/// drop(s);
294+
/// drop(p); // garbage / use-after-free if `try_from_arc` compiles
295+
/// ```
252296
#[inline]
253297
pub fn try_from_arc<U, E, F>(arc: &Arc<U>, project: F) -> Result<Self, E>
254298
where
255-
U: ?Sized + Sync + Send,
299+
U: ?Sized + Sync + Send + 'static,
256300
T: 'static,
257301
F: FnOnce(&U) -> Result<&T, E>,
258302
{
@@ -285,10 +329,25 @@ impl<T: ?Sized> Parc<T> {
285329
/// let local = 5;
286330
/// let projected = parc.project(|tuple| &local);
287331
/// ```
332+
///
333+
/// Do not allow coercing `Parc<&'static T>` to `Parc<&'short T>` when projecting:
334+
/// ```compile_fail,E0597
335+
/// use pared::sync::Parc;
336+
///
337+
/// let s = "Hello World!".to_owned();
338+
/// // x: Parc<&'static ()>
339+
/// let x = Parc::new(&());
340+
/// // This must fail
341+
/// let x = x.project(|_| s.as_str());
342+
///
343+
/// println!("{:?}", &*x); // "Hello World!"
344+
/// drop(s);
345+
/// println!("{:?}", &*x); // garbage / use-after-free if the above doesn't fail
346+
/// ```
288347
#[inline]
289348
pub fn project<U, F>(&self, project: F) -> Parc<U>
290349
where
291-
T: Send + Sync,
350+
T: Send + Sync + 'static,
292351
U: ?Sized + 'static,
293352
F: FnOnce(&T) -> &U,
294353
{

src/vtable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ mod tests {
5555
strong_count_weak: c,
5656
weak_count_weak: c,
5757
};
58-
format!("{:?}", vtable);
58+
let _msg = format!("{:?}", vtable);
5959
}
6060
}

tests/parc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,11 @@ fn borrows() {
217217
fn fmt() {
218218
let parc = Parc::new(5);
219219

220-
format!("{} {:?} {:p}", parc, parc, parc);
220+
let _msg = format!("{} {:?} {:p}", parc, parc, parc);
221221

222222
let weak = Parc::downgrade(&parc);
223223

224-
format!("{:?}", weak);
224+
let _msg = format!("{:?}", weak);
225225
}
226226

227227
#[test]

tests/prc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,11 @@ fn borrows() {
218218
fn fmt() {
219219
let prc = Prc::new(5);
220220

221-
format!("{} {:?} {:p}", prc, prc, prc);
221+
let _msg = format!("{} {:?} {:p}", prc, prc, prc);
222222

223223
let weak = Prc::downgrade(&prc);
224224

225-
format!("{:?}", weak);
225+
let _msg = format!("{:?}", weak);
226226
}
227227

228228
#[test]

0 commit comments

Comments
 (0)