about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2021-03-22 02:20:27 +0100
committerGitHub <noreply@github.com>2021-03-22 02:20:27 +0100
commitda143d38e4e47f53c4745a037563f34d7f3ea6e3 (patch)
treedf67533b7cff66ddb141e8227965e9700c38578b
parent29a53e6e69d5684a598770652e9c170dd4d149d8 (diff)
parentc89e64363ba25a4bf487d71a4ad6e9a8cbe40384 (diff)
downloadrust-da143d38e4e47f53c4745a037563f34d7f3ea6e3.tar.gz
rust-da143d38e4e47f53c4745a037563f34d7f3ea6e3.zip
Rollup merge of #82554 - SkiFire13:fix-string-retain-unsoundness, r=m-ou-se
Fix invalid slice access in String::retain

As noted in #78499, the previous fix was technically still unsound because it accessed elements of a slice outside its bounds (even though they were still inside the same allocation). This PR addresses that concern by switching to a dropguard approach.
-rw-r--r--library/alloc/src/string.rs37
1 files changed, 22 insertions, 15 deletions
diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs
index cc2d440010b..f4ec4a36ffd 100644
--- a/library/alloc/src/string.rs
+++ b/library/alloc/src/string.rs
@@ -1289,37 +1289,44 @@ impl String {
     where
         F: FnMut(char) -> bool,
     {
-        let len = self.len();
-        let mut del_bytes = 0;
-        let mut idx = 0;
+        struct SetLenOnDrop<'a> {
+            s: &'a mut String,
+            idx: usize,
+            del_bytes: usize,
+        }
 
-        unsafe {
-            self.vec.set_len(0);
+        impl<'a> Drop for SetLenOnDrop<'a> {
+            fn drop(&mut self) {
+                let new_len = self.idx - self.del_bytes;
+                debug_assert!(new_len <= self.s.len());
+                unsafe { self.s.vec.set_len(new_len) };
+            }
         }
 
-        while idx < len {
-            let ch = unsafe { self.get_unchecked(idx..len).chars().next().unwrap() };
+        let len = self.len();
+        let mut guard = SetLenOnDrop { s: self, idx: 0, del_bytes: 0 };
+
+        while guard.idx < len {
+            let ch = unsafe { guard.s.get_unchecked(guard.idx..len).chars().next().unwrap() };
             let ch_len = ch.len_utf8();
 
             if !f(ch) {
-                del_bytes += ch_len;
-            } else if del_bytes > 0 {
+                guard.del_bytes += ch_len;
+            } else if guard.del_bytes > 0 {
                 unsafe {
                     ptr::copy(
-                        self.vec.as_ptr().add(idx),
-                        self.vec.as_mut_ptr().add(idx - del_bytes),
+                        guard.s.vec.as_ptr().add(guard.idx),
+                        guard.s.vec.as_mut_ptr().add(guard.idx - guard.del_bytes),
                         ch_len,
                     );
                 }
             }
 
             // Point idx to the next char
-            idx += ch_len;
+            guard.idx += ch_len;
         }
 
-        unsafe {
-            self.vec.set_len(len - del_bytes);
-        }
+        drop(guard);
     }
 
     /// Inserts a character into this `String` at a byte position.