Skip to content

Commit bfdbd14

Browse files
committed
Fixed String::retain, RVec::retain. Bumped patch version to 0.9.1 .
These methods copied their implementation from the standard library, which had memory safety bugs discovered in rust-lang/rust#60977 and rust-lang/rust#78498 . This bug was reported in #44 . Added adapted tests from std which test these bugs.
1 parent 2bff3b3 commit bfdbd14

File tree

6 files changed

+143
-22
lines changed

6 files changed

+143
-22
lines changed

abi_stable/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "abi_stable"
3-
version = "0.9.0"
3+
version = "0.9.1"
44
authors = ["rodrimati1992 <[email protected]>"]
55
edition="2018"
66
license = "MIT/Apache-2.0"

abi_stable/src/std_types/string.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use crate::std_types::{RStr, RVec};
2222

2323
mod iters;
2424

25-
#[cfg(all(test,not(feature="only_new_tests")))]
25+
#[cfg(test)]
26+
// #[cfg(all(test,not(feature="only_new_tests")))]
2627
mod tests;
2728

2829
pub use self::iters::{Drain, IntoIter};
@@ -643,10 +644,12 @@ impl RString {
643644
let mut del_bytes = 0;
644645
let mut idx = 0;
645646

647+
unsafe {
648+
self.inner.set_len(0);
649+
}
650+
646651
while idx < len {
647-
let ch = unsafe {
648-
self.get_unchecked(idx..len).chars().next().unwrap()
649-
};
652+
let ch = unsafe { self.get_unchecked(idx..len).chars().next().unwrap() };
650653
let ch_len = ch.len_utf8();
651654

652655
if !pred(ch) {
@@ -656,16 +659,17 @@ impl RString {
656659
ptr::copy(
657660
self.inner.as_ptr().add(idx),
658661
self.inner.as_mut_ptr().add(idx - del_bytes),
659-
ch_len
662+
ch_len,
660663
);
661664
}
662665
}
663666

667+
// Point idx to the next char
664668
idx += ch_len;
665669
}
666670

667-
if del_bytes > 0 {
668-
unsafe { self.inner.set_len(len - del_bytes); }
671+
unsafe {
672+
self.inner.set_len(len - del_bytes);
669673
}
670674
}
671675

abi_stable/src/std_types/string/tests.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,23 @@ fn retain(){
201201

202202
assert_eq!(&*rstr, &*string);
203203
}
204-
204+
{
205+
// Copied from:
206+
// https://github.com/rust-lang/rust/blob/48c4afbf9c29880dd946067d1c9aee1e7f75834a/library/alloc/tests/string.rs#L383
207+
let mut s = RString::from("0è0");
208+
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
209+
let mut count = 0;
210+
s.retain(|_| {
211+
count += 1;
212+
match count {
213+
1 => false,
214+
2 => true,
215+
_ => panic!(),
216+
}
217+
});
218+
}));
219+
assert!(std::str::from_utf8(s.as_bytes()).is_ok());
220+
}
205221
}
206222

207223

abi_stable/src/std_types/vec.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,7 @@ impl<T> RVec<T> {
812812
del: 0,
813813
old_len,
814814
pred: |x| !pred(x),
815+
panic_flag: false,
815816
};
816817
}
817818

abi_stable/src/std_types/vec/iters.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ impl<'a, T> Drop for Drain<'a, T> {
277277

278278

279279

280-
// copy-paste of the std library DrainFilter
280+
// copy of the std library DrainFilter, without the allocator parameter.
281+
// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17))
281282
#[derive(Debug)]
282283
pub(crate) struct DrainFilter<'a, T, F>
283284
where F: FnMut(&mut T) -> bool,
@@ -287,20 +288,30 @@ pub(crate) struct DrainFilter<'a, T, F>
287288
pub(super) del: usize,
288289
pub(super) old_len: usize,
289290
pub(super) pred: F,
291+
pub(super) panic_flag: bool,
290292
}
291293

294+
// copy of the std library DrainFilter impl, without the allocator parameter.
295+
// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17))
292296
impl<T, F> Iterator for DrainFilter<'_, T, F>
293-
where F: FnMut(&mut T) -> bool,
297+
where
298+
F: FnMut(&mut T) -> bool,
294299
{
295300
type Item = T;
296301

297302
fn next(&mut self) -> Option<T> {
298303
unsafe {
299-
while self.idx != self.old_len {
304+
while self.idx < self.old_len {
300305
let i = self.idx;
301-
self.idx += 1;
302306
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
303-
if (self.pred)(&mut v[i]) {
307+
self.panic_flag = true;
308+
let drained = (self.pred)(&mut v[i]);
309+
self.panic_flag = false;
310+
// Update the index *after* the predicate is called. If the index
311+
// is updated prior and the predicate panics, the element at this
312+
// index would be leaked.
313+
self.idx += 1;
314+
if drained {
304315
self.del += 1;
305316
return Some(ptr::read(&v[i]));
306317
} else if self.del > 0 {
@@ -319,14 +330,51 @@ impl<T, F> Iterator for DrainFilter<'_, T, F>
319330
}
320331
}
321332

333+
// copy of the std library DrainFilter impl, without the allocator parameter.
334+
// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17))
322335
impl<T, F> Drop for DrainFilter<'_, T, F>
323-
where F: FnMut(&mut T) -> bool,
336+
where
337+
F: FnMut(&mut T) -> bool,
324338
{
325339
fn drop(&mut self) {
326-
self.for_each(drop);
327-
unsafe {
328-
self.vec.set_len(self.old_len - self.del);
340+
struct BackshiftOnDrop<'a, 'b, T, F>
341+
where
342+
F: FnMut(&mut T) -> bool,
343+
{
344+
drain: &'b mut DrainFilter<'a, T, F>,
345+
}
346+
347+
impl<'a, 'b, T, F> Drop for BackshiftOnDrop<'a, 'b, T, F>
348+
where
349+
F: FnMut(&mut T) -> bool,
350+
{
351+
fn drop(&mut self) {
352+
unsafe {
353+
if self.drain.idx < self.drain.old_len && self.drain.del > 0 {
354+
// This is a pretty messed up state, and there isn't really an
355+
// obviously right thing to do. We don't want to keep trying
356+
// to execute `pred`, so we just backshift all the unprocessed
357+
// elements and tell the vec that they still exist. The backshift
358+
// is required to prevent a double-drop of the last successfully
359+
// drained item prior to a panic in the predicate.
360+
let ptr = self.drain.vec.as_mut_ptr();
361+
let src = ptr.add(self.drain.idx);
362+
let dst = src.sub(self.drain.del);
363+
let tail_len = self.drain.old_len - self.drain.idx;
364+
src.copy_to(dst, tail_len);
365+
}
366+
self.drain.vec.set_len(self.drain.old_len - self.drain.del);
367+
}
368+
}
369+
}
370+
371+
let backshift = BackshiftOnDrop { drain: self };
372+
373+
// Attempt to consume any remaining elements if the filter predicate
374+
// has not yet panicked. We'll backshift any remaining elements
375+
// whether we've already panicked or if the consumption here panics.
376+
if !backshift.drain.panic_flag {
377+
backshift.drain.for_each(drop);
329378
}
330379
}
331380
}
332-

abi_stable/src/std_types/vec/tests.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ fn truncate() {
165165

166166
#[test]
167167
fn retain(){
168-
let orig = vec![2, 3, 4 , 5, 6,7,8];
168+
let orig = vec![2, 3, 4, 5, 6, 7, 8];
169169
let copy = orig.clone().into_(RVec::T);
170170
{
171171
let mut copy=copy.clone();
@@ -235,7 +235,7 @@ fn retain(){
235235
true
236236
});
237237
}).unwrap();
238-
assert_eq!(&copy[..], <&[i32]>::default());
238+
assert_eq!(&copy[..], &orig[..]);
239239
}
240240
}
241241

@@ -397,4 +397,56 @@ fn rvec_macro(){
397397
assert_eq!(RVec::from(vec![0,3]), rvec![0,3]);
398398
assert_eq!(RVec::from(vec![0,3,6]), rvec![0,3,6]);
399399
assert_eq!(RVec::from(vec![1;10]), rvec![1;10]);
400-
}
400+
}
401+
402+
// Adapted from Vec tests
403+
// (from rustc 1.50.0-nightly (eb4fc71dc 2020-12-17))
404+
#[test]
405+
fn retain_panic() {
406+
use std::rc::Rc;
407+
use std::sync::Mutex;
408+
use std::panic::AssertUnwindSafe;
409+
410+
struct Check {
411+
index: usize,
412+
drop_counts: Rc<Mutex<RVec<usize>>>,
413+
}
414+
415+
impl Drop for Check {
416+
fn drop(&mut self) {
417+
self.drop_counts.lock().unwrap()[self.index] += 1;
418+
println!("drop: {}", self.index);
419+
}
420+
}
421+
422+
let check_count = 10;
423+
let drop_counts = Rc::new(Mutex::new(rvec![0_usize; check_count]));
424+
let mut data: RVec<Check> = (0..check_count)
425+
.map(|index| Check { index, drop_counts: Rc::clone(&drop_counts) })
426+
.collect();
427+
428+
let _ = std::panic::catch_unwind(AssertUnwindSafe(move || {
429+
let filter = |c: &Check| {
430+
if c.index == 2 {
431+
panic!("panic at index: {}", c.index);
432+
}
433+
// Verify that if the filter could panic again on another element
434+
// that it would not cause a double panic and all elements of the
435+
// vec would still be dropped exactly once.
436+
if c.index == 4 {
437+
panic!("panic at index: {}", c.index);
438+
}
439+
c.index < 6
440+
};
441+
data.retain(filter);
442+
}));
443+
444+
let drop_counts = drop_counts.lock().unwrap();
445+
assert_eq!(check_count, drop_counts.len());
446+
447+
for (index, count) in drop_counts.iter().cloned().enumerate() {
448+
assert_eq!(1, count, "unexpected drop count at index: {} (count: {})", index, count);
449+
}
450+
}
451+
452+

0 commit comments

Comments
 (0)