From 0c41c3414c0e89ccb19310a4ec423ac83a48f039 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 7 Oct 2024 19:34:13 -0400 Subject: [PATCH 1/4] Allow zero-size reads/writes on null pointers --- library/core/src/intrinsics.rs | 19 ++++++++++++------- library/core/src/ptr/mod.rs | 21 +++++++++++++-------- library/core/src/slice/raw.rs | 4 ++-- library/core/src/ub_checks.rs | 4 ++-- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index d7a2f1909cabe..d76b7ca66cca4 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -64,6 +64,7 @@ #![allow(missing_docs)] use crate::marker::{DiscriminantKind, Tuple}; +use crate::mem::SizedTypeProperties; use crate::{ptr, ub_checks}; pub mod mir; @@ -3311,10 +3312,12 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us size: usize = size_of::(), align: usize = align_of::(), count: usize = count, - ) => - ub_checks::is_aligned_and_not_null(src, align) - && ub_checks::is_aligned_and_not_null(dst, align) - && ub_checks::is_nonoverlapping(src, dst, size, count) + ) => { + let zero_size = count == 0 || size == 0; + ub_checks::is_aligned_and_not_null(src, align, zero_size) + && ub_checks::is_aligned_and_not_null(dst, align, zero_size) + && ub_checks::is_nonoverlapping(src, dst, size, count) + } ); // SAFETY: the safety contract for `copy_nonoverlapping` must be @@ -3412,9 +3415,10 @@ pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { src: *const () = src as *const (), dst: *mut () = dst as *mut (), align: usize = align_of::(), + zero_size: bool = T::IS_ZST || count == 0, ) => - ub_checks::is_aligned_and_not_null(src, align) - && ub_checks::is_aligned_and_not_null(dst, align) + ub_checks::is_aligned_and_not_null(src, align, zero_size) + && ub_checks::is_aligned_and_not_null(dst, align, zero_size) ); copy(src, dst, count) } @@ -3491,7 +3495,8 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { ( addr: *const () = dst as *const (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + zero_size: bool = T::IS_ZST || count == 0, + ) => ub_checks::is_aligned_and_not_null(addr, align, zero_size) ); write_bytes(dst, val, count) } diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 67f1b0cd16de4..53ca5a7f198e0 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -448,7 +448,7 @@ use crate::cmp::Ordering; use crate::marker::FnPtr; -use crate::mem::{self, MaybeUninit}; +use crate::mem::{self, MaybeUninit, SizedTypeProperties}; use crate::{fmt, hash, intrinsics, ub_checks}; mod alignment; @@ -1165,10 +1165,12 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { size: usize = size_of::(), align: usize = align_of::(), count: usize = count, - ) => - ub_checks::is_aligned_and_not_null(x, align) - && ub_checks::is_aligned_and_not_null(y, align) - && ub_checks::is_nonoverlapping(x, y, size, count) + ) => { + let zero_size = size == 0 || count == 0; + ub_checks::is_aligned_and_not_null(x, align, zero_size) + && ub_checks::is_aligned_and_not_null(y, align, zero_size) + && ub_checks::is_nonoverlapping(x, y, size, count) + } ); // Split up the slice into small power-of-two-sized chunks that LLVM is able @@ -1277,7 +1279,8 @@ pub const unsafe fn replace(dst: *mut T, src: T) -> T { ( addr: *const () = dst as *const (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + is_zst: bool = T::IS_ZST, + ) => ub_checks::is_aligned_and_not_null(addr, align, is_zst) ); mem::replace(&mut *dst, src) } @@ -1806,7 +1809,8 @@ pub unsafe fn read_volatile(src: *const T) -> T { ( addr: *const () = src as *const (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + is_zst: bool = T::IS_ZST, + ) => ub_checks::is_aligned_and_not_null(addr, align, is_zst) ); intrinsics::volatile_load(src) } @@ -1885,7 +1889,8 @@ pub unsafe fn write_volatile(dst: *mut T, src: T) { ( addr: *mut () = dst as *mut (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + is_zst: bool = T::IS_ZST, + ) => ub_checks::is_aligned_and_not_null(addr, align, is_zst) ); intrinsics::volatile_store(dst, src); } diff --git a/library/core/src/slice/raw.rs b/library/core/src/slice/raw.rs index 84e916b9a84e7..27bbadf7c89de 100644 --- a/library/core/src/slice/raw.rs +++ b/library/core/src/slice/raw.rs @@ -132,7 +132,7 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] align: usize = align_of::(), len: usize = len, ) => - ub_checks::is_aligned_and_not_null(data, align) + ub_checks::is_aligned_and_not_null(data, align, false) && ub_checks::is_valid_allocation_size(size, len) ); &*ptr::slice_from_raw_parts(data, len) @@ -187,7 +187,7 @@ pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a m align: usize = align_of::(), len: usize = len, ) => - ub_checks::is_aligned_and_not_null(data, align) + ub_checks::is_aligned_and_not_null(data, align, false) && ub_checks::is_valid_allocation_size(size, len) ); &mut *ptr::slice_from_raw_parts_mut(data, len) diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs index c1a8c34539e6c..2ea30e77609f3 100644 --- a/library/core/src/ub_checks.rs +++ b/library/core/src/ub_checks.rs @@ -116,8 +116,8 @@ pub(crate) const fn check_language_ub() -> bool { /// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the /// check is anyway not executed in `const`. #[inline] -pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool { - !ptr.is_null() && ptr.is_aligned_to(align) +pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize, is_zst: bool) -> bool { + if is_zst { ptr.is_aligned_to(align) } else { !ptr.is_null() && ptr.is_aligned_to(align) } } #[inline] From 84dacc18829e24ec5d61087a233de68ef57dfabc Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 7 Oct 2024 19:34:25 -0400 Subject: [PATCH 2/4] Add more precondition check tests --- library/core/src/ascii/ascii_char.rs | 2 +- library/core/src/ptr/mod.rs | 6 +++-- tests/ui/precondition-checks/alignment.rs | 11 ++++++++ .../ascii-char-digit_unchecked.rs | 11 ++++++++ .../precondition-checks/assert_unchecked.rs | 9 +++++++ .../char-from_u32_unchecked.rs | 9 +++++++ .../copy-nonoverlapping.rs | 25 +++++++++++++++++++ tests/ui/precondition-checks/copy.rs | 23 +++++++++++++++++ tests/ui/precondition-checks/layout.rs | 15 +++++++++++ .../precondition-checks/misaligned-slice.rs | 10 -------- tests/ui/precondition-checks/nonnull.rs | 9 +++++++ .../nonzero-from_mut_unchecked.rs | 12 +++++++++ .../nonzero-new_unchecked.rs | 9 +++++++ tests/ui/precondition-checks/null-slice.rs | 10 -------- .../out-of-bounds-get-unchecked.rs | 11 -------- tests/ui/precondition-checks/read.rs | 18 +++++++++++++ tests/ui/precondition-checks/read_volatile.rs | 17 +++++++++++++ tests/ui/precondition-checks/replace.rs | 17 +++++++++++++ .../slice-from-raw-parts-mut.rs | 16 ++++++++++++ .../slice-from-raw-parts.rs | 15 +++++++++++ .../slice-get_unchecked.rs | 20 +++++++++++++++ .../slice-get_unchecked_mut.rs | 20 +++++++++++++++ .../slice-swap_unchecked.rs | 14 +++++++++++ .../precondition-checks/str-get_unchecked.rs | 18 +++++++++++++ .../str-get_unchecked_mut.rs | 19 ++++++++++++++ .../swap-nonoverlapping.rs | 25 +++++++++++++++++++ tests/ui/precondition-checks/unchecked_add.rs | 9 +++++++ tests/ui/precondition-checks/unchecked_mul.rs | 9 +++++++ tests/ui/precondition-checks/unchecked_shl.rs | 11 ++++++++ tests/ui/precondition-checks/unchecked_shr.rs | 11 ++++++++ tests/ui/precondition-checks/unchecked_sub.rs | 9 +++++++ .../unreachable_unchecked.rs | 9 +++++++ tests/ui/precondition-checks/write.rs | 18 +++++++++++++ tests/ui/precondition-checks/write_bytes.rs | 18 +++++++++++++ .../ui/precondition-checks/write_volatile.rs | 17 +++++++++++++ 35 files changed, 448 insertions(+), 34 deletions(-) create mode 100644 tests/ui/precondition-checks/alignment.rs create mode 100644 tests/ui/precondition-checks/ascii-char-digit_unchecked.rs create mode 100644 tests/ui/precondition-checks/assert_unchecked.rs create mode 100644 tests/ui/precondition-checks/char-from_u32_unchecked.rs create mode 100644 tests/ui/precondition-checks/copy-nonoverlapping.rs create mode 100644 tests/ui/precondition-checks/copy.rs create mode 100644 tests/ui/precondition-checks/layout.rs delete mode 100644 tests/ui/precondition-checks/misaligned-slice.rs create mode 100644 tests/ui/precondition-checks/nonnull.rs create mode 100644 tests/ui/precondition-checks/nonzero-from_mut_unchecked.rs create mode 100644 tests/ui/precondition-checks/nonzero-new_unchecked.rs delete mode 100644 tests/ui/precondition-checks/null-slice.rs delete mode 100644 tests/ui/precondition-checks/out-of-bounds-get-unchecked.rs create mode 100644 tests/ui/precondition-checks/read.rs create mode 100644 tests/ui/precondition-checks/read_volatile.rs create mode 100644 tests/ui/precondition-checks/replace.rs create mode 100644 tests/ui/precondition-checks/slice-from-raw-parts-mut.rs create mode 100644 tests/ui/precondition-checks/slice-from-raw-parts.rs create mode 100644 tests/ui/precondition-checks/slice-get_unchecked.rs create mode 100644 tests/ui/precondition-checks/slice-get_unchecked_mut.rs create mode 100644 tests/ui/precondition-checks/slice-swap_unchecked.rs create mode 100644 tests/ui/precondition-checks/str-get_unchecked.rs create mode 100644 tests/ui/precondition-checks/str-get_unchecked_mut.rs create mode 100644 tests/ui/precondition-checks/swap-nonoverlapping.rs create mode 100644 tests/ui/precondition-checks/unchecked_add.rs create mode 100644 tests/ui/precondition-checks/unchecked_mul.rs create mode 100644 tests/ui/precondition-checks/unchecked_shl.rs create mode 100644 tests/ui/precondition-checks/unchecked_shr.rs create mode 100644 tests/ui/precondition-checks/unchecked_sub.rs create mode 100644 tests/ui/precondition-checks/unreachable_unchecked.rs create mode 100644 tests/ui/precondition-checks/write.rs create mode 100644 tests/ui/precondition-checks/write_bytes.rs create mode 100644 tests/ui/precondition-checks/write_volatile.rs diff --git a/library/core/src/ascii/ascii_char.rs b/library/core/src/ascii/ascii_char.rs index ce09a0b444da3..48de4f17b1b3a 100644 --- a/library/core/src/ascii/ascii_char.rs +++ b/library/core/src/ascii/ascii_char.rs @@ -506,7 +506,7 @@ impl AsciiChar { pub const unsafe fn digit_unchecked(d: u8) -> Self { assert_unsafe_precondition!( check_language_ub, - "`AsciiChar::digit_unchecked` input cannot exceed 9.", + "`ascii::Char::digit_unchecked` input cannot exceed 9.", (d: u8 = d) => d < 10 ); diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 53ca5a7f198e0..62160937083c5 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -1432,7 +1432,8 @@ pub const unsafe fn read(src: *const T) -> T { ( addr: *const () = src as *const (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + is_zst: bool = T::IS_ZST, + ) => ub_checks::is_aligned_and_not_null(addr, align, is_zst) ); crate::intrinsics::read_via_copy(src) } @@ -1637,7 +1638,8 @@ pub const unsafe fn write(dst: *mut T, src: T) { ( addr: *mut () = dst as *mut (), align: usize = align_of::(), - ) => ub_checks::is_aligned_and_not_null(addr, align) + is_zst: bool = T::IS_ZST, + ) => ub_checks::is_aligned_and_not_null(addr, align, is_zst) ); intrinsics::write_via_move(dst, src) } diff --git a/tests/ui/precondition-checks/alignment.rs b/tests/ui/precondition-checks/alignment.rs new file mode 100644 index 0000000000000..92400528fa07f --- /dev/null +++ b/tests/ui/precondition-checks/alignment.rs @@ -0,0 +1,11 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: Alignment::new_unchecked requires + +#![feature(ptr_alignment_type)] + +fn main() { + unsafe { + std::ptr::Alignment::new_unchecked(0); + } +} diff --git a/tests/ui/precondition-checks/ascii-char-digit_unchecked.rs b/tests/ui/precondition-checks/ascii-char-digit_unchecked.rs new file mode 100644 index 0000000000000..30c6f79fb08f0 --- /dev/null +++ b/tests/ui/precondition-checks/ascii-char-digit_unchecked.rs @@ -0,0 +1,11 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: `ascii::Char::digit_unchecked` input cannot exceed 9 + +#![feature(ascii_char)] + +fn main() { + unsafe { + std::ascii::Char::digit_unchecked(b'a'); + } +} diff --git a/tests/ui/precondition-checks/assert_unchecked.rs b/tests/ui/precondition-checks/assert_unchecked.rs new file mode 100644 index 0000000000000..22b2b41455021 --- /dev/null +++ b/tests/ui/precondition-checks/assert_unchecked.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: hint::assert_unchecked must never be called when the condition is false + +fn main() { + unsafe { + std::hint::assert_unchecked(false); + } +} diff --git a/tests/ui/precondition-checks/char-from_u32_unchecked.rs b/tests/ui/precondition-checks/char-from_u32_unchecked.rs new file mode 100644 index 0000000000000..d950f20c77208 --- /dev/null +++ b/tests/ui/precondition-checks/char-from_u32_unchecked.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: invalid value for `char` + +fn main() { + unsafe { + char::from_u32_unchecked(0xD801); + } +} diff --git a/tests/ui/precondition-checks/copy-nonoverlapping.rs b/tests/ui/precondition-checks/copy-nonoverlapping.rs new file mode 100644 index 0000000000000..81018e4bff3e5 --- /dev/null +++ b/tests/ui/precondition-checks/copy-nonoverlapping.rs @@ -0,0 +1,25 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::copy_nonoverlapping requires +//@ revisions: null_src null_dst misaligned_src misaligned_dst overlapping + +use std::ptr; + +fn main() { + let src = [0u16; 3]; + let mut dst = [0u16; 3]; + let src = src.as_ptr(); + let dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null_src)] + ptr::copy_nonoverlapping(ptr::null(), dst, 1); + #[cfg(null_dst)] + ptr::copy_nonoverlapping(src, ptr::null_mut(), 1); + #[cfg(misaligned_src)] + ptr::copy_nonoverlapping(src.byte_add(1), dst, 1); + #[cfg(misaligned_dst)] + ptr::copy_nonoverlapping(src, dst.byte_add(1), 1); + #[cfg(overlapping)] + ptr::copy_nonoverlapping(dst, dst.add(1), 2); + } +} diff --git a/tests/ui/precondition-checks/copy.rs b/tests/ui/precondition-checks/copy.rs new file mode 100644 index 0000000000000..694853f950ab5 --- /dev/null +++ b/tests/ui/precondition-checks/copy.rs @@ -0,0 +1,23 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::copy requires +//@ revisions: null_src null_dst misaligned_src misaligned_dst + +use std::ptr; + +fn main() { + let src = [0u16; 3]; + let mut dst = [0u16; 3]; + let src = src.as_ptr(); + let dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null_src)] + ptr::copy(ptr::null(), dst, 1); + #[cfg(null_dst)] + ptr::copy(src, ptr::null_mut(), 1); + #[cfg(misaligned_src)] + ptr::copy(src.byte_add(1), dst, 1); + #[cfg(misaligned_dst)] + ptr::copy(src, dst.byte_add(1), 1); + } +} diff --git a/tests/ui/precondition-checks/layout.rs b/tests/ui/precondition-checks/layout.rs new file mode 100644 index 0000000000000..4fd1bbc4a994b --- /dev/null +++ b/tests/ui/precondition-checks/layout.rs @@ -0,0 +1,15 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: Layout::from_size_align_unchecked requires +//@ revisions: toolarge badalign +//@[toolarge] compile-flags: --cfg toolarge +//@[badalign] compile-flags: --cfg badalign + +fn main() { + unsafe { + #[cfg(toolarge)] + std::alloc::Layout::from_size_align_unchecked(isize::MAX as usize, 2); + #[cfg(badalign)] + std::alloc::Layout::from_size_align_unchecked(1, 3); + } +} diff --git a/tests/ui/precondition-checks/misaligned-slice.rs b/tests/ui/precondition-checks/misaligned-slice.rs deleted file mode 100644 index 2963a0b5e6310..0000000000000 --- a/tests/ui/precondition-checks/misaligned-slice.rs +++ /dev/null @@ -1,10 +0,0 @@ -//@ run-fail -//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes -//@ error-pattern: unsafe precondition(s) violated: slice::from_raw_parts -//@ ignore-debug - -fn main() { - unsafe { - let _s: &[u64] = std::slice::from_raw_parts(1usize as *const u64, 0); - } -} diff --git a/tests/ui/precondition-checks/nonnull.rs b/tests/ui/precondition-checks/nonnull.rs new file mode 100644 index 0000000000000..6b8edd4e5825e --- /dev/null +++ b/tests/ui/precondition-checks/nonnull.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: NonNull::new_unchecked requires + +fn main() { + unsafe { + std::ptr::NonNull::new_unchecked(std::ptr::null_mut::()); + } +} diff --git a/tests/ui/precondition-checks/nonzero-from_mut_unchecked.rs b/tests/ui/precondition-checks/nonzero-from_mut_unchecked.rs new file mode 100644 index 0000000000000..46ce7dc356fe2 --- /dev/null +++ b/tests/ui/precondition-checks/nonzero-from_mut_unchecked.rs @@ -0,0 +1,12 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: NonZero::from_mut_unchecked requires + +#![feature(nonzero_from_mut)] + +fn main() { + unsafe { + let mut num = 0u8; + std::num::NonZeroU8::from_mut_unchecked(&mut num); + } +} diff --git a/tests/ui/precondition-checks/nonzero-new_unchecked.rs b/tests/ui/precondition-checks/nonzero-new_unchecked.rs new file mode 100644 index 0000000000000..7827a42844fd4 --- /dev/null +++ b/tests/ui/precondition-checks/nonzero-new_unchecked.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: NonZero::new_unchecked requires + +fn main() { + unsafe { + std::num::NonZeroU8::new_unchecked(0); + } +} diff --git a/tests/ui/precondition-checks/null-slice.rs b/tests/ui/precondition-checks/null-slice.rs deleted file mode 100644 index 280960358b7b3..0000000000000 --- a/tests/ui/precondition-checks/null-slice.rs +++ /dev/null @@ -1,10 +0,0 @@ -//@ run-fail -//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes -//@ error-pattern: unsafe precondition(s) violated: slice::from_raw_parts -//@ ignore-debug - -fn main() { - unsafe { - let _s: &[u8] = std::slice::from_raw_parts(std::ptr::null(), 0); - } -} diff --git a/tests/ui/precondition-checks/out-of-bounds-get-unchecked.rs b/tests/ui/precondition-checks/out-of-bounds-get-unchecked.rs deleted file mode 100644 index 011e92183fa47..0000000000000 --- a/tests/ui/precondition-checks/out-of-bounds-get-unchecked.rs +++ /dev/null @@ -1,11 +0,0 @@ -//@ run-fail -//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes -//@ error-pattern: slice::get_unchecked requires -//@ ignore-debug - -fn main() { - unsafe { - let sli: &[u8] = &[0]; - sli.get_unchecked(1); - } -} diff --git a/tests/ui/precondition-checks/read.rs b/tests/ui/precondition-checks/read.rs new file mode 100644 index 0000000000000..ab9921a0ceebe --- /dev/null +++ b/tests/ui/precondition-checks/read.rs @@ -0,0 +1,18 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::read requires +//@ revisions: null misaligned +//@ ignore-test + +use std::ptr; + +fn main() { + let src = [0u16; 2]; + let src = src.as_ptr(); + unsafe { + #[cfg(null)] + ptr::read(ptr::null::()); + #[cfg(misaligned)] + ptr::read(src.byte_add(1)); + } +} diff --git a/tests/ui/precondition-checks/read_volatile.rs b/tests/ui/precondition-checks/read_volatile.rs new file mode 100644 index 0000000000000..e14881d029037 --- /dev/null +++ b/tests/ui/precondition-checks/read_volatile.rs @@ -0,0 +1,17 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::read_volatile requires +//@ revisions: null misaligned + +use std::ptr; + +fn main() { + let src = [0u16; 2]; + let src = src.as_ptr(); + unsafe { + #[cfg(null)] + ptr::read_volatile(ptr::null::()); + #[cfg(misaligned)] + ptr::read_volatile(src.byte_add(1)); + } +} diff --git a/tests/ui/precondition-checks/replace.rs b/tests/ui/precondition-checks/replace.rs new file mode 100644 index 0000000000000..2808cee7b64b1 --- /dev/null +++ b/tests/ui/precondition-checks/replace.rs @@ -0,0 +1,17 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::replace requires +//@ revisions: null misaligned + +use std::ptr; + +fn main() { + let mut dst = [0u16; 2]; + let dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null)] + ptr::replace(ptr::null_mut::(), 1); + #[cfg(misaligned)] + ptr::replace(dst.byte_add(1), 1u16); + } +} diff --git a/tests/ui/precondition-checks/slice-from-raw-parts-mut.rs b/tests/ui/precondition-checks/slice-from-raw-parts-mut.rs new file mode 100644 index 0000000000000..3801639e2551b --- /dev/null +++ b/tests/ui/precondition-checks/slice-from-raw-parts-mut.rs @@ -0,0 +1,16 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: slice::from_raw_parts_mut requires +//@ revisions: null misaligned toolarge + +fn main() { + unsafe { + #[cfg(null)] + let _s: &mut [u8] = std::slice::from_raw_parts_mut(std::ptr::null_mut(), 0); + #[cfg(misaligned)] + let _s: &mut [u16] = std::slice::from_raw_parts_mut(1usize as *mut u16, 0); + #[cfg(toolarge)] + let _s: &mut [u16] = + std::slice::from_raw_parts_mut(2usize as *mut u16, isize::MAX as usize); + } +} diff --git a/tests/ui/precondition-checks/slice-from-raw-parts.rs b/tests/ui/precondition-checks/slice-from-raw-parts.rs new file mode 100644 index 0000000000000..a3690fa045eb7 --- /dev/null +++ b/tests/ui/precondition-checks/slice-from-raw-parts.rs @@ -0,0 +1,15 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: slice::from_raw_parts requires +//@ revisions: null misaligned toolarge + +fn main() { + unsafe { + #[cfg(null)] + let _s: &[u8] = std::slice::from_raw_parts(std::ptr::null(), 0); + #[cfg(misaligned)] + let _s: &[u16] = std::slice::from_raw_parts(1usize as *const u16, 0); + #[cfg(toolarge)] + let _s: &[u16] = std::slice::from_raw_parts(2usize as *const u16, isize::MAX as usize); + } +} diff --git a/tests/ui/precondition-checks/slice-get_unchecked.rs b/tests/ui/precondition-checks/slice-get_unchecked.rs new file mode 100644 index 0000000000000..1d8188fb9531a --- /dev/null +++ b/tests/ui/precondition-checks/slice-get_unchecked.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: slice::get_unchecked requires +//@ revisions: usize range range_to range_from backwards_range + +fn main() { + unsafe { + let s = &[0]; + #[cfg(usize)] + s.get_unchecked(1); + #[cfg(range)] + s.get_unchecked(1..2); + #[cfg(range_to)] + s.get_unchecked(..2); + #[cfg(range_from)] + s.get_unchecked(2..); + #[cfg(backwards_range)] + s.get_unchecked(1..0); + } +} diff --git a/tests/ui/precondition-checks/slice-get_unchecked_mut.rs b/tests/ui/precondition-checks/slice-get_unchecked_mut.rs new file mode 100644 index 0000000000000..34c1454af438d --- /dev/null +++ b/tests/ui/precondition-checks/slice-get_unchecked_mut.rs @@ -0,0 +1,20 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: slice::get_unchecked_mut requires +//@ revisions: usize range range_to range_from backwards_range + +fn main() { + unsafe { + let mut s = &mut [0]; + #[cfg(usize)] + s.get_unchecked_mut(1); + #[cfg(range)] + s.get_unchecked_mut(1..2); + #[cfg(range_to)] + s.get_unchecked_mut(..2); + #[cfg(range_from)] + s.get_unchecked_mut(2..); + #[cfg(backwards_range)] + s.get_unchecked_mut(1..0); + } +} diff --git a/tests/ui/precondition-checks/slice-swap_unchecked.rs b/tests/ui/precondition-checks/slice-swap_unchecked.rs new file mode 100644 index 0000000000000..227a49091ec22 --- /dev/null +++ b/tests/ui/precondition-checks/slice-swap_unchecked.rs @@ -0,0 +1,14 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: index out of bounds: the len is 2 but the index is 2 +//@ revisions: oob_a oob_b + +fn main() { + let mut pair = [0u8; 2]; + unsafe { + #[cfg(oob_a)] + pair.swap(0, 2); + #[cfg(oob_b)] + pair.swap(2, 0); + } +} diff --git a/tests/ui/precondition-checks/str-get_unchecked.rs b/tests/ui/precondition-checks/str-get_unchecked.rs new file mode 100644 index 0000000000000..14d17f997ec9b --- /dev/null +++ b/tests/ui/precondition-checks/str-get_unchecked.rs @@ -0,0 +1,18 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: str::get_unchecked requires +//@ revisions: range range_to range_from backwards_range + +fn main() { + unsafe { + let s = "💅"; + #[cfg(range)] + s.get_unchecked(4..5); + #[cfg(range_to)] + s.get_unchecked(..5); + #[cfg(range_from)] + s.get_unchecked(5..); + #[cfg(backwards_range)] + s.get_unchecked(1..0); + } +} diff --git a/tests/ui/precondition-checks/str-get_unchecked_mut.rs b/tests/ui/precondition-checks/str-get_unchecked_mut.rs new file mode 100644 index 0000000000000..ca1b169005559 --- /dev/null +++ b/tests/ui/precondition-checks/str-get_unchecked_mut.rs @@ -0,0 +1,19 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: str::get_unchecked_mut requires +//@ revisions: range range_to range_from backwards_range + +fn main() { + unsafe { + let mut s: String = "💅".chars().collect(); + let mut s: &mut str = &mut s; + #[cfg(range)] + s.get_unchecked_mut(4..5); + #[cfg(range_to)] + s.get_unchecked_mut(..5); + #[cfg(range_from)] + s.get_unchecked_mut(5..); + #[cfg(backwards_range)] + s.get_unchecked_mut(1..0); + } +} diff --git a/tests/ui/precondition-checks/swap-nonoverlapping.rs b/tests/ui/precondition-checks/swap-nonoverlapping.rs new file mode 100644 index 0000000000000..52e4a3c870be5 --- /dev/null +++ b/tests/ui/precondition-checks/swap-nonoverlapping.rs @@ -0,0 +1,25 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::swap_nonoverlapping requires +//@ revisions: null_src null_dst misaligned_src misaligned_dst overlapping + +use std::ptr; + +fn main() { + let mut src = [0u16; 3]; + let mut dst = [0u16; 3]; + let src = src.as_mut_ptr(); + let dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null_src)] + ptr::swap_nonoverlapping(ptr::null_mut(), dst, 1); + #[cfg(null_dst)] + ptr::swap_nonoverlapping(src, ptr::null_mut(), 1); + #[cfg(misaligned_src)] + ptr::swap_nonoverlapping(src.byte_add(1), dst, 1); + #[cfg(misaligned_dst)] + ptr::swap_nonoverlapping(src, dst.byte_add(1), 1); + #[cfg(overlapping)] + ptr::swap_nonoverlapping(dst, dst.add(1), 2); + } +} diff --git a/tests/ui/precondition-checks/unchecked_add.rs b/tests/ui/precondition-checks/unchecked_add.rs new file mode 100644 index 0000000000000..f44a6ea32ad8f --- /dev/null +++ b/tests/ui/precondition-checks/unchecked_add.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: u8::unchecked_add cannot overflow + +fn main() { + unsafe { + 1u8.unchecked_add(u8::MAX); + } +} diff --git a/tests/ui/precondition-checks/unchecked_mul.rs b/tests/ui/precondition-checks/unchecked_mul.rs new file mode 100644 index 0000000000000..66655dda136e9 --- /dev/null +++ b/tests/ui/precondition-checks/unchecked_mul.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: u8::unchecked_add cannot overflow + +fn main() { + unsafe { + 2u8.unchecked_add(u8::MAX); + } +} diff --git a/tests/ui/precondition-checks/unchecked_shl.rs b/tests/ui/precondition-checks/unchecked_shl.rs new file mode 100644 index 0000000000000..1c96db0b1ec71 --- /dev/null +++ b/tests/ui/precondition-checks/unchecked_shl.rs @@ -0,0 +1,11 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: u8::unchecked_shl cannot overflow + +#![feature(unchecked_shifts)] + +fn main() { + unsafe { + 0u8.unchecked_shl(u8::BITS); + } +} diff --git a/tests/ui/precondition-checks/unchecked_shr.rs b/tests/ui/precondition-checks/unchecked_shr.rs new file mode 100644 index 0000000000000..4a6d9ffb1d35b --- /dev/null +++ b/tests/ui/precondition-checks/unchecked_shr.rs @@ -0,0 +1,11 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: u8::unchecked_shr cannot overflow + +#![feature(unchecked_shifts)] + +fn main() { + unsafe { + 0u8.unchecked_shr(u8::BITS); + } +} diff --git a/tests/ui/precondition-checks/unchecked_sub.rs b/tests/ui/precondition-checks/unchecked_sub.rs new file mode 100644 index 0000000000000..545dde0e27809 --- /dev/null +++ b/tests/ui/precondition-checks/unchecked_sub.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: u8::unchecked_sub cannot overflow + +fn main() { + unsafe { + 0u8.unchecked_sub(1u8); + } +} diff --git a/tests/ui/precondition-checks/unreachable_unchecked.rs b/tests/ui/precondition-checks/unreachable_unchecked.rs new file mode 100644 index 0000000000000..2435450c4b5a1 --- /dev/null +++ b/tests/ui/precondition-checks/unreachable_unchecked.rs @@ -0,0 +1,9 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: hint::unreachable_unchecked must never be reached + +fn main() { + unsafe { + std::hint::unreachable_unchecked(); + } +} diff --git a/tests/ui/precondition-checks/write.rs b/tests/ui/precondition-checks/write.rs new file mode 100644 index 0000000000000..f76e776fcf35d --- /dev/null +++ b/tests/ui/precondition-checks/write.rs @@ -0,0 +1,18 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::write requires +//@ revisions: null misaligned +//@ ignore-test + +use std::ptr; + +fn main() { + let mut dst = [0u16; 2]; + let mut dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null)] + ptr::write(ptr::null_mut::(), 1u8); + #[cfg(misaligned)] + ptr::write(dst.byte_add(1), 1u16); + } +} diff --git a/tests/ui/precondition-checks/write_bytes.rs b/tests/ui/precondition-checks/write_bytes.rs new file mode 100644 index 0000000000000..3f64be9d1ee14 --- /dev/null +++ b/tests/ui/precondition-checks/write_bytes.rs @@ -0,0 +1,18 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::write requires +//@ revisions: null misaligned +//@ ignore-test + +use std::ptr; + +fn main() { + let mut dst = [0u16; 2]; + let mut dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null)] + ptr::write_bytes(ptr::null_mut::(), 1u8, 2); + #[cfg(misaligned)] + ptr::write_bytes(dst.byte_add(1), 1u8, 2); + } +} diff --git a/tests/ui/precondition-checks/write_volatile.rs b/tests/ui/precondition-checks/write_volatile.rs new file mode 100644 index 0000000000000..ac0b89b5ecf2a --- /dev/null +++ b/tests/ui/precondition-checks/write_volatile.rs @@ -0,0 +1,17 @@ +//@ run-fail +//@ compile-flags: -Copt-level=3 -Cdebug-assertions=no -Zub-checks=yes +//@ error-pattern: unsafe precondition(s) violated: ptr::write_volatile requires +//@ revisions: null misaligned + +use std::ptr; + +fn main() { + let mut dst = [0u16; 2]; + let mut dst = dst.as_mut_ptr(); + unsafe { + #[cfg(null)] + ptr::write_volatile(ptr::null_mut::(), 1u8); + #[cfg(misaligned)] + ptr::write_volatile(dst.byte_add(1), 1u16); + } +} From aec09a43efed613ed12e32cef5d1bd94750e04eb Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 9 Oct 2024 18:34:33 -0400 Subject: [PATCH 3/4] Clean up is_aligned_and_not_null --- library/core/src/ub_checks.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs index 2ea30e77609f3..daaaf5a71953e 100644 --- a/library/core/src/ub_checks.rs +++ b/library/core/src/ub_checks.rs @@ -109,15 +109,15 @@ pub(crate) const fn check_language_ub() -> bool { intrinsics::ub_checks() && const_eval_select((), comptime, runtime) } -/// Checks whether `ptr` is properly aligned with respect to -/// `align_of::()`. +/// Checks whether `ptr` is properly aligned with respect to the given alignment, and +/// if `is_zst == false`, that `ptr` is not null. /// /// In `const` this is approximate and can fail spuriously. It is primarily intended /// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the /// check is anyway not executed in `const`. #[inline] pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize, is_zst: bool) -> bool { - if is_zst { ptr.is_aligned_to(align) } else { !ptr.is_null() && ptr.is_aligned_to(align) } + ptr.is_aligned_to(align) && (is_zst || !ptr.is_null()) } #[inline] From a3606d7ee3dd9d910578a81b2992e61521b1ce21 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 9 Oct 2024 18:46:06 -0400 Subject: [PATCH 4/4] Add a test for zero-sized writes through null --- .../ui/precondition-checks/zero-size-null.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/ui/precondition-checks/zero-size-null.rs diff --git a/tests/ui/precondition-checks/zero-size-null.rs b/tests/ui/precondition-checks/zero-size-null.rs new file mode 100644 index 0000000000000..43a81175f9448 --- /dev/null +++ b/tests/ui/precondition-checks/zero-size-null.rs @@ -0,0 +1,21 @@ +// Test that none of the precondition checks panic on zero-sized reads or writes through null. + +//@ run-pass +//@ compile-flags: -Zmir-opt-level=0 -Copt-level=0 -Cdebug-assertions=yes + +use std::ptr; + +fn main() { + unsafe { + ptr::copy_nonoverlapping::(ptr::null(), ptr::null_mut(), 0); + ptr::copy_nonoverlapping::<()>(ptr::null(), ptr::null_mut(), 123); + ptr::copy::(ptr::null(), ptr::null_mut(), 0); + ptr::copy::<()>(ptr::null(), ptr::null_mut(), 123); + ptr::swap::<()>(ptr::null_mut(), ptr::null_mut()); + ptr::replace::<()>(ptr::null_mut(), ()); + ptr::read::<()>(ptr::null()); + ptr::write::<()>(ptr::null_mut(), ()); + ptr::read_volatile::<()>(ptr::null()); + ptr::write_volatile::<()>(ptr::null_mut(), ()); + } +}