Skip to content

Commit 6877eed

Browse files
Merge #280
280: Fix undefined behavior identified by Miri r=japaric a=jgallagher Hi! We ran into an exception triggered by new undefined behavior checks inserted into the nightly compiler (https://github.com/rust-lang/rust/pull/92686/files#diff-54110dcedc5a4d976321aa5d2a6767ac0744a3ef1363b75ffc62faf81cf14c30R230-L229). Running `heapless`'s test suite under Miri didn't flag anything at first, but it did once we added `MIRIFLAGS="-Zmiri-tag-raw-pointers"`. All three of the fixes in this PR were identified via ``` MIRIFLAGS="-Zmiri-tag-raw-pointers -Zmiri-ignore-leaks" cargo +nightly miri test -- --skip pool:: ``` and the fixes came from copying the implementations from the equivalent methods in `std`. Note that I skipped the `pool::` tests; there is at least one miri failure in them, but it wasn't immediately obvious how to fix it so I skipped it for now. It's probably worth adding the flag above to the CI miri run, but I didn't do that either (since it would immediately cause failures given I didn't fix the problem in `pool`). The specific output for `pool` is ``` test pool::singleton::tests::sanity ... error: Undefined Behavior: trying to reborrow <untagged> for SharedReadWrite permission at alloc36[0x1], but that tag does not exist in the borrow stack for this location --> /home/john/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:380:18 | 380 | unsafe { &*self.as_ptr() } | ^^^^^^^^^^^^^^^ | | | trying to reborrow <untagged> for SharedReadWrite permission at alloc36[0x1], but that tag does not exist in the borrow stack for this location | this error occurs as part of a reborrow at alloc36[0x1..0x9] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information = note: inside `std::ptr::NonNull::<pool::stack::Node<u8>>::as_ref` at /home/john/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:380:18 note: inside `pool::stack::Stack::<u8>::push` at src/pool/cas.rs:43:17 --> src/pool/cas.rs:43:17 | 43 | / new_head 44 | | .as_raw() 45 | | .as_ref() | |_____________________________^ note: inside `pool::Pool::<u8>::grow` at src/pool/mod.rs:390:25 --> src/pool/mod.rs:390:25 | 390 | self.stack.push(p); | ^^^^^^^^^^^^^^^^^^ note: inside `<pool::singleton::tests::sanity::A as pool::singleton::Pool>::grow` at src/pool/singleton.rs:78:9 --> src/pool/singleton.rs:78:9 | 78 | Self::ptr().grow(memory) | ^^^^^^^^^^^^^^^^^^^^^^^^ note: inside `pool::singleton::tests::sanity` at src/pool/singleton.rs:362:9 --> src/pool/singleton.rs:362:9 | 362 | A::grow(unsafe { &mut MEMORY }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: inside closure at src/pool/singleton.rs:353:5 --> src/pool/singleton.rs:353:5 | 352 | #[test] | ------- in this procedural macro expansion 353 | / fn sanity() { 354 | | const SZ: usize = 2 * mem::size_of::<Node<u8>>() - 1; 355 | | static mut MEMORY: [u8; SZ] = [0; SZ]; 356 | | ... | 373 | | assert_eq!(*A::alloc().unwrap().init(1), 1); 374 | | } | |_____^ = note: this error originates in the attribute macro `test` (in Nightly builds, run with -Z macro-backtrace for more info) ``` Co-authored-by: John Gallagher <[email protected]>
2 parents 1c56672 + 8fd2907 commit 6877eed

File tree

3 files changed

+28
-14
lines changed

3 files changed

+28
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
### Fixed
1111

1212
* Fixed `pool` example in docstring.
13+
* Fixed undefined behavior in `Vec::truncate()`, `Vec::swap_remove_unchecked()`,
14+
and `Hole::move_to()` (internal to the binary heap implementation).
1315

1416
### Added
1517

src/binary_heap.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,9 @@ impl<'a, T> Hole<'a, T> {
428428
unsafe fn move_to(&mut self, index: usize) {
429429
debug_assert!(index != self.pos);
430430
debug_assert!(index < self.data.len());
431-
let index_ptr: *const _ = self.data.get_unchecked(index);
432-
let hole_ptr = self.data.get_unchecked_mut(self.pos);
431+
let ptr = self.data.as_mut_ptr();
432+
let index_ptr: *const _ = ptr.add(index);
433+
let hole_ptr = ptr.add(self.pos);
433434
ptr::copy_nonoverlapping(index_ptr, hole_ptr, 1);
434435
self.pos = index;
435436
}

src/vec.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,24 @@ impl<T, const N: usize> Vec<T, N> {
263263

264264
/// Shortens the vector, keeping the first `len` elements and dropping the rest.
265265
pub fn truncate(&mut self, len: usize) {
266-
// drop any extra elements
267-
while len < self.len {
268-
// decrement len before the drop_in_place(), so a panic on Drop
269-
// doesn't re-drop the just-failed value.
270-
self.len -= 1;
271-
let len = self.len;
272-
unsafe { ptr::drop_in_place(self.as_mut_slice().get_unchecked_mut(len)) };
266+
// This is safe because:
267+
//
268+
// * the slice passed to `drop_in_place` is valid; the `len > self.len`
269+
// case avoids creating an invalid slice, and
270+
// * the `len` of the vector is shrunk before calling `drop_in_place`,
271+
// such that no value will be dropped twice in case `drop_in_place`
272+
// were to panic once (if it panics twice, the program aborts).
273+
unsafe {
274+
// Note: It's intentional that this is `>` and not `>=`.
275+
// Changing it to `>=` has negative performance
276+
// implications in some cases. See rust-lang/rust#78884 for more.
277+
if len > self.len {
278+
return;
279+
}
280+
let remaining_len = self.len - len;
281+
let s = ptr::slice_from_raw_parts_mut(self.as_mut_ptr().add(len), remaining_len);
282+
self.len = len;
283+
ptr::drop_in_place(s);
273284
}
274285
}
275286

@@ -471,11 +482,11 @@ impl<T, const N: usize> Vec<T, N> {
471482
pub unsafe fn swap_remove_unchecked(&mut self, index: usize) -> T {
472483
let length = self.len();
473484
debug_assert!(index < length);
474-
ptr::swap(
475-
self.as_mut_slice().get_unchecked_mut(index),
476-
self.as_mut_slice().get_unchecked_mut(length - 1),
477-
);
478-
self.pop_unchecked()
485+
let value = ptr::read(self.as_ptr().add(index));
486+
let base_ptr = self.as_mut_ptr();
487+
ptr::copy(base_ptr.add(length - 1), base_ptr.add(index), 1);
488+
self.len -= 1;
489+
value
479490
}
480491

481492
/// Returns true if the vec is full

0 commit comments

Comments
 (0)