From ab9cb7fa2af5d17ba08f49c798565c513a905e3c Mon Sep 17 00:00:00 2001 From: ticki Date: Thu, 1 Sep 2016 11:47:32 +0200 Subject: [PATCH 1/4] Implement `replace_with`, a function similar to the one provided by the `take_mut` crate. `replace_with` invokes a closure that will temporarily move out of reference and later place back the value. --- src/libcore/mem.rs | 60 ++++++++++++++++++++++++++++++++++++++++++ src/libcoretest/mem.rs | 22 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index 9c61f76ac8895..a9fa35774a0c8 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -415,6 +415,66 @@ pub fn replace(dest: &mut T, mut src: T) -> T { src } +/// A guarding type which will abort upon drop. +/// +/// This is used for catching unwinding and transforming it into abort. +struct ExitGuard; + +impl Drop for ExitGuard { + fn drop(&mut self) { + // To avoid unwinding, we abort the program, which ensures that the destructor of the + // invalidated value isn't runned. + unsafe { intrinsics::abort(); } + } +} + +/// Temporarily takes ownership of a value at a mutable location, and replace it with a new value +/// based on the old one. +/// +/// We move out of reference temporarily, to apply a closure, returning a new value, which is then +/// placed at the original value's location. +/// +/// # An important note +/// +/// The behavior on panic (or to be more precise, unwinding) is unspecified (but not undefined). +/// +/// # Example +/// +/// ``` +/// use std::mem; +/// +/// // Dump some stuff into a box. +/// let mut bx: Box = Box::new(200); +/// +/// // Temporarily steal ownership. +/// mem::replace(&mut bx, |mut owned| { +/// owner = 5; +/// +/// // The returned value is placed back in `&mut bx`. +/// Box::new(owner) +/// }); +/// ``` +#[inline] +#[unstable(feature = "replace_with", issue = "...")] +pub fn replace_with(val: &mut T, replace: F) + where F: FnOnce(T) -> T { + // Guard against unwinding. Note that this is critical to safety, to avoid the value behind the + // reference `val` is not dropped twice during unwinding. + let guard = ExitGuard; + + unsafe { + // Take out the value behind the pointer. + let old = ptr::read(val); + // Run the closure. + let new = closure(old); + // Put the result back. + ptr::write(val, new); + } + + // Drop the guard. + mem::forget(guard); +} + /// Disposes of a value. /// /// While this does call the argument's implementation of `Drop`, it will not diff --git a/src/libcoretest/mem.rs b/src/libcoretest/mem.rs index 01bafe49a7acd..6068822a22589 100644 --- a/src/libcoretest/mem.rs +++ b/src/libcoretest/mem.rs @@ -99,6 +99,28 @@ fn test_replace() { assert!(y.is_some()); } +#[test] +fn test_replace_with() { + let mut x = Some("test".to_string()); + replace_with(&mut x, |_| None); + assert!(x.is_none()); +} + +#[test] +fn test_replace_with_2() { + let mut x = Box::new(21); + replace_with(&mut x, |x| Box::new(x + 5)); + assert_eq!(*x, 26); +} + +#[test] +fn test_replace_with_3() { + let is_called = Cell::new(false); + let mut x = 2; + replace_with(&mut x, |_| is_called.set(true)); + assert!(is_called.get()); +} + #[test] fn test_transmute_copy() { assert_eq!(1, unsafe { transmute_copy(&1) }); From 582b6f99c1ec429fcf5b7be598b4f537d19a49c8 Mon Sep 17 00:00:00 2001 From: ticki Date: Thu, 13 Oct 2016 20:12:52 +0200 Subject: [PATCH 2/4] Change guarantees, and fix tests. --- src/libcore/mem.rs | 10 ++++++---- src/libcoretest/mem.rs | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index a9fa35774a0c8..4dc9e656040af 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -422,9 +422,10 @@ struct ExitGuard; impl Drop for ExitGuard { fn drop(&mut self) { - // To avoid unwinding, we abort the program, which ensures that the destructor of the - // invalidated value isn't runned. - unsafe { intrinsics::abort(); } + // To avoid unwinding, we abort (we panic, which is equivalent to abort inside a + // destructor, which we are currently in) the program, which ensures that the destructor of + // the invalidated value isn't runned. + panic!(); } } @@ -436,7 +437,8 @@ impl Drop for ExitGuard { /// /// # An important note /// -/// The behavior on panic (or to be more precise, unwinding) is unspecified (but not undefined). +/// The behavior on panic (or to be more precise, unwinding) is specified to match the behavior of +/// panicking inside a destructor, which itself is simply specified to not unwind. /// /// # Example /// diff --git a/src/libcoretest/mem.rs b/src/libcoretest/mem.rs index 6068822a22589..6f86faf3400d9 100644 --- a/src/libcoretest/mem.rs +++ b/src/libcoretest/mem.rs @@ -117,7 +117,10 @@ fn test_replace_with_2() { fn test_replace_with_3() { let is_called = Cell::new(false); let mut x = 2; - replace_with(&mut x, |_| is_called.set(true)); + replace_with(&mut x, |_| { + is_called.set(true); + 3 + }); assert!(is_called.get()); } From 41ae5a846b32d082206f96f317b1c0488c0acbe0 Mon Sep 17 00:00:00 2001 From: ticki Date: Thu, 13 Oct 2016 20:27:28 +0200 Subject: [PATCH 3/4] Add an error message (exploit the panick-during-unwind behavior). --- src/libcore/mem.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index 4dc9e656040af..e59a592472162 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -418,14 +418,17 @@ pub fn replace(dest: &mut T, mut src: T) -> T { /// A guarding type which will abort upon drop. /// /// This is used for catching unwinding and transforming it into abort. +/// +/// The destructor should never be called naturally (use `mem::forget()`), and only when unwinding. struct ExitGuard; impl Drop for ExitGuard { fn drop(&mut self) { - // To avoid unwinding, we abort (we panic, which is equivalent to abort inside a - // destructor, which we are currently in) the program, which ensures that the destructor of - // the invalidated value isn't runned. - panic!(); + // To avoid unwinding, we abort (we panic, which is equivalent to abort inside an unwinding + // destructor) the program, which ensures that the destructor of the invalidated value + // isn't runned, since this destructor ought to be called only if unwinding happens. + panic!("`replace_with` closure unwinded. For safety reasons, this will \ + abort your program. Check the documentation"); } } @@ -473,7 +476,7 @@ pub fn replace_with(val: &mut T, replace: F) ptr::write(val, new); } - // Drop the guard. + // Forget the guard, to avoid panicking. mem::forget(guard); } From 9c92fe40dcaefb60e1008d30a5b149ac47771baa Mon Sep 17 00:00:00 2001 From: ticki Date: Thu, 13 Oct 2016 20:39:30 +0200 Subject: [PATCH 4/4] Fix typos. --- src/libcore/mem.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcore/mem.rs b/src/libcore/mem.rs index e59a592472162..5de9f8859a9e9 100644 --- a/src/libcore/mem.rs +++ b/src/libcore/mem.rs @@ -426,7 +426,7 @@ impl Drop for ExitGuard { fn drop(&mut self) { // To avoid unwinding, we abort (we panic, which is equivalent to abort inside an unwinding // destructor) the program, which ensures that the destructor of the invalidated value - // isn't runned, since this destructor ought to be called only if unwinding happens. + // isn't run, since this destructor ought to be called only if unwinding happens. panic!("`replace_with` closure unwinded. For safety reasons, this will \ abort your program. Check the documentation"); } @@ -452,7 +452,7 @@ impl Drop for ExitGuard { /// let mut bx: Box = Box::new(200); /// /// // Temporarily steal ownership. -/// mem::replace(&mut bx, |mut owned| { +/// mem::replace_with(&mut bx, |mut owned| { /// owner = 5; /// /// // The returned value is placed back in `&mut bx`. @@ -461,7 +461,7 @@ impl Drop for ExitGuard { /// ``` #[inline] #[unstable(feature = "replace_with", issue = "...")] -pub fn replace_with(val: &mut T, replace: F) +pub fn replace_with(val: &mut T, closure: F) where F: FnOnce(T) -> T { // Guard against unwinding. Note that this is critical to safety, to avoid the value behind the // reference `val` is not dropped twice during unwinding.