Skip to content

Commit c6ec1dd

Browse files
committed
Add precondition checks to ptr::offset, ptr::add, ptr::sub
1 parent 648d024 commit c6ec1dd

13 files changed

+277
-43
lines changed

library/core/src/ptr/const_ptr.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,36 @@ impl<T: ?Sized> *const T {
394394
where
395395
T: Sized,
396396
{
397+
#[inline]
398+
const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
399+
#[inline]
400+
fn runtime(this: *const (), count: isize, size: usize) -> bool {
401+
// We know `size <= isize::MAX` so the `as` cast here is not lossy.
402+
let Some(byte_offset) = count.checked_mul(size as isize) else {
403+
return false;
404+
};
405+
let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
406+
!overflow
407+
}
408+
409+
const fn comptime(_: *const (), _: isize, _: usize) -> bool {
410+
true
411+
}
412+
413+
// We can use const_eval_select here because this is only for UB checks.
414+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
415+
}
416+
417+
ub_checks::assert_unsafe_precondition!(
418+
check_language_ub,
419+
"ptr::offset requires the address calculation to not overflow",
420+
(
421+
this: *const () = self as *const (),
422+
count: isize = count,
423+
size: usize = size_of::<T>(),
424+
) => runtime_offset_nowrap(this, count, size)
425+
);
426+
397427
// SAFETY: the caller must uphold the safety contract for `offset`.
398428
unsafe { intrinsics::offset(self, count) }
399429
}
@@ -728,7 +758,6 @@ impl<T: ?Sized> *const T {
728758
true
729759
}
730760

731-
#[allow(unused_unsafe)]
732761
intrinsics::const_eval_select((this, origin), comptime, runtime)
733762
}
734763

@@ -855,6 +884,34 @@ impl<T: ?Sized> *const T {
855884
where
856885
T: Sized,
857886
{
887+
#[inline]
888+
const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
889+
#[inline]
890+
fn runtime(this: *const (), count: usize, size: usize) -> bool {
891+
let Some(byte_offset) = count.checked_mul(size) else {
892+
return false;
893+
};
894+
let (_, overflow) = this.addr().overflowing_add(byte_offset);
895+
byte_offset <= (isize::MAX as usize) && !overflow
896+
}
897+
898+
const fn comptime(_: *const (), _: usize, _: usize) -> bool {
899+
true
900+
}
901+
902+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
903+
}
904+
905+
ub_checks::assert_unsafe_precondition!(
906+
check_language_ub,
907+
"ptr::add requires that the address calculation does not overflow",
908+
(
909+
this: *const () = self as *const (),
910+
count: usize = count,
911+
size: usize = size_of::<T>(),
912+
) => runtime_add_nowrap(this, count, size)
913+
);
914+
858915
// SAFETY: the caller must uphold the safety contract for `offset`.
859916
unsafe { intrinsics::offset(self, count) }
860917
}
@@ -930,14 +987,41 @@ impl<T: ?Sized> *const T {
930987
where
931988
T: Sized,
932989
{
990+
#[inline]
991+
const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
992+
#[inline]
993+
fn runtime(this: *const (), count: usize, size: usize) -> bool {
994+
let Some(byte_offset) = count.checked_mul(size) else {
995+
return false;
996+
};
997+
byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
998+
}
999+
1000+
const fn comptime(_: *const (), _: usize, _: usize) -> bool {
1001+
true
1002+
}
1003+
1004+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
1005+
}
1006+
1007+
ub_checks::assert_unsafe_precondition!(
1008+
check_language_ub,
1009+
"ptr::sub requires that the address calculation does not overflow",
1010+
(
1011+
this: *const () = self as *const (),
1012+
count: usize = count,
1013+
size: usize = size_of::<T>(),
1014+
) => runtime_sub_nowrap(this, count, size)
1015+
);
1016+
9331017
if T::IS_ZST {
9341018
// Pointer arithmetic does nothing when the pointee is a ZST.
9351019
self
9361020
} else {
9371021
// SAFETY: the caller must uphold the safety contract for `offset`.
9381022
// Because the pointee is *not* a ZST, that means that `count` is
9391023
// at most `isize::MAX`, and thus the negation cannot overflow.
940-
unsafe { self.offset((count as isize).unchecked_neg()) }
1024+
unsafe { intrinsics::offset(self, intrinsics::unchecked_sub(0, count as isize)) }
9411025
}
9421026
}
9431027

library/core/src/ptr/mut_ptr.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,37 @@ impl<T: ?Sized> *mut T {
392392
where
393393
T: Sized,
394394
{
395+
#[inline]
396+
const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
397+
#[inline]
398+
fn runtime(this: *const (), count: isize, size: usize) -> bool {
399+
// `size` is the size of a Rust type, so we know that
400+
// `size <= isize::MAX` and thus `as` cast here is not lossy.
401+
let Some(byte_offset) = count.checked_mul(size as isize) else {
402+
return false;
403+
};
404+
let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
405+
!overflow
406+
}
407+
408+
const fn comptime(_: *const (), _: isize, _: usize) -> bool {
409+
true
410+
}
411+
412+
// We can use const_eval_select here because this is only for UB checks.
413+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
414+
}
415+
416+
ub_checks::assert_unsafe_precondition!(
417+
check_language_ub,
418+
"ptr::offset requires the address calculation to not overflow",
419+
(
420+
this: *const () = self as *const (),
421+
count: isize = count,
422+
size: usize = size_of::<T>(),
423+
) => runtime_offset_nowrap(this, count, size)
424+
);
425+
395426
// SAFETY: the caller must uphold the safety contract for `offset`.
396427
// The obtained pointer is valid for writes since the caller must
397428
// guarantee that it points to the same allocated object as `self`.
@@ -936,6 +967,34 @@ impl<T: ?Sized> *mut T {
936967
where
937968
T: Sized,
938969
{
970+
#[inline]
971+
const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
972+
#[inline]
973+
fn runtime(this: *const (), count: usize, size: usize) -> bool {
974+
let Some(byte_offset) = count.checked_mul(size) else {
975+
return false;
976+
};
977+
let (_, overflow) = this.addr().overflowing_add(byte_offset);
978+
byte_offset <= (isize::MAX as usize) && !overflow
979+
}
980+
981+
const fn comptime(_: *const (), _: usize, _: usize) -> bool {
982+
true
983+
}
984+
985+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
986+
}
987+
988+
ub_checks::assert_unsafe_precondition!(
989+
check_language_ub,
990+
"ptr::add requires that the address calculation does not overflow",
991+
(
992+
this: *const () = self as *const (),
993+
count: usize = count,
994+
size: usize = size_of::<T>(),
995+
) => runtime_add_nowrap(this, count, size)
996+
);
997+
939998
// SAFETY: the caller must uphold the safety contract for `offset`.
940999
unsafe { intrinsics::offset(self, count) }
9411000
}
@@ -1011,14 +1070,41 @@ impl<T: ?Sized> *mut T {
10111070
where
10121071
T: Sized,
10131072
{
1073+
#[inline]
1074+
const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
1075+
#[inline]
1076+
fn runtime(this: *const (), count: usize, size: usize) -> bool {
1077+
let Some(byte_offset) = count.checked_mul(size) else {
1078+
return false;
1079+
};
1080+
byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
1081+
}
1082+
1083+
const fn comptime(_: *const (), _: usize, _: usize) -> bool {
1084+
true
1085+
}
1086+
1087+
intrinsics::const_eval_select((this, count, size), comptime, runtime)
1088+
}
1089+
1090+
ub_checks::assert_unsafe_precondition!(
1091+
check_language_ub,
1092+
"ptr::sub requires that the address calculation does not overflow",
1093+
(
1094+
this: *const () = self as *const (),
1095+
count: usize = count,
1096+
size: usize = size_of::<T>(),
1097+
) => runtime_sub_nowrap(this, count, size)
1098+
);
1099+
10141100
if T::IS_ZST {
10151101
// Pointer arithmetic does nothing when the pointee is a ZST.
10161102
self
10171103
} else {
10181104
// SAFETY: the caller must uphold the safety contract for `offset`.
10191105
// Because the pointee is *not* a ZST, that means that `count` is
10201106
// at most `isize::MAX`, and thus the negation cannot overflow.
1021-
unsafe { self.offset((count as isize).unchecked_neg()) }
1107+
unsafe { intrinsics::offset(self, intrinsics::unchecked_sub(0, count as isize)) }
10221108
}
10231109
}
10241110

tests/codegen/option-as-slice.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ pub fn u64_opt_as_slice(o: &Option<u64>) -> &[u64] {
1414
// CHECK-NOT: br
1515
// CHECK-NOT: switch
1616
// CHECK-NOT: icmp
17-
// CHECK: %[[LEN:.+]] = load i64,{{.+}} !range ![[META_U64:.+]], !noundef
17+
// CHECK: %[[LEN:.+]] = load i64
18+
// CHECK-SAME: !range ![[META_U64:[0-9]+]],
19+
// CHECK-SAME: !noundef
1820
// CHECK-NOT: select
1921
// CHECK-NOT: br
2022
// CHECK-NOT: switch
@@ -51,7 +53,9 @@ pub fn u8_opt_as_slice(o: &Option<u8>) -> &[u8] {
5153
// CHECK-NOT: br
5254
// CHECK-NOT: switch
5355
// CHECK-NOT: icmp
54-
// CHECK: %[[TAG:.+]] = load i8,{{.+}} !range ![[META_U8:.+]], !noundef
56+
// CHECK: %[[TAG:.+]] = load i8
57+
// CHECK-SAME: !range ![[META_U8:[0-9]+]],
58+
// CHECK-SAME: !noundef
5559
// CHECK: %[[LEN:.+]] = zext{{.*}} i8 %[[TAG]] to i64
5660
// CHECK-NOT: select
5761
// CHECK-NOT: br

tests/mir-opt/pre-codegen/ptr_offset.demo_byte_add_fat.PreCodegen.after.panic-abort.mir

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ fn demo_byte_add_fat(_1: *const [u32], _2: usize) -> *const [u32] {
1010
scope 2 (inlined std::ptr::const_ptr::<impl *const [u32]>::cast::<u8>) {
1111
}
1212
scope 3 (inlined std::ptr::const_ptr::<impl *const u8>::add) {
13+
scope 4 (inlined core::ub_checks::check_language_ub) {
14+
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
15+
}
16+
}
17+
scope 6 (inlined std::mem::size_of::<u8>) {
18+
}
1319
}
14-
scope 4 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<[u32]>) {
20+
scope 7 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<[u32]>) {
1521
let mut _5: usize;
16-
scope 5 (inlined std::ptr::metadata::<[u32]>) {
22+
scope 8 (inlined std::ptr::metadata::<[u32]>) {
1723
}
18-
scope 6 (inlined std::ptr::from_raw_parts::<[u32], ()>) {
24+
scope 9 (inlined std::ptr::from_raw_parts::<[u32], ()>) {
1925
}
2026
}
2127
}

tests/mir-opt/pre-codegen/ptr_offset.demo_byte_add_fat.PreCodegen.after.panic-unwind.mir

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ fn demo_byte_add_fat(_1: *const [u32], _2: usize) -> *const [u32] {
1010
scope 2 (inlined std::ptr::const_ptr::<impl *const [u32]>::cast::<u8>) {
1111
}
1212
scope 3 (inlined std::ptr::const_ptr::<impl *const u8>::add) {
13+
scope 4 (inlined core::ub_checks::check_language_ub) {
14+
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
15+
}
16+
}
17+
scope 6 (inlined std::mem::size_of::<u8>) {
18+
}
1319
}
14-
scope 4 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<[u32]>) {
20+
scope 7 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<[u32]>) {
1521
let mut _5: usize;
16-
scope 5 (inlined std::ptr::metadata::<[u32]>) {
22+
scope 8 (inlined std::ptr::metadata::<[u32]>) {
1723
}
18-
scope 6 (inlined std::ptr::from_raw_parts::<[u32], ()>) {
24+
scope 9 (inlined std::ptr::from_raw_parts::<[u32], ()>) {
1925
}
2026
}
2127
}

tests/mir-opt/pre-codegen/ptr_offset.demo_byte_add_thin.PreCodegen.after.panic-abort.mir

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ fn demo_byte_add_thin(_1: *const u32, _2: usize) -> *const u32 {
1010
scope 2 (inlined std::ptr::const_ptr::<impl *const u32>::cast::<u8>) {
1111
}
1212
scope 3 (inlined std::ptr::const_ptr::<impl *const u8>::add) {
13+
scope 4 (inlined core::ub_checks::check_language_ub) {
14+
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
15+
}
16+
}
17+
scope 6 (inlined std::mem::size_of::<u8>) {
18+
}
1319
}
14-
scope 4 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<u32>) {
15-
scope 5 (inlined std::ptr::metadata::<u32>) {
20+
scope 7 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<u32>) {
21+
scope 8 (inlined std::ptr::metadata::<u32>) {
1622
}
17-
scope 6 (inlined std::ptr::from_raw_parts::<u32, ()>) {
23+
scope 9 (inlined std::ptr::from_raw_parts::<u32, ()>) {
1824
}
1925
}
2026
}

tests/mir-opt/pre-codegen/ptr_offset.demo_byte_add_thin.PreCodegen.after.panic-unwind.mir

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ fn demo_byte_add_thin(_1: *const u32, _2: usize) -> *const u32 {
1010
scope 2 (inlined std::ptr::const_ptr::<impl *const u32>::cast::<u8>) {
1111
}
1212
scope 3 (inlined std::ptr::const_ptr::<impl *const u8>::add) {
13+
scope 4 (inlined core::ub_checks::check_language_ub) {
14+
scope 5 (inlined core::ub_checks::check_language_ub::runtime) {
15+
}
16+
}
17+
scope 6 (inlined std::mem::size_of::<u8>) {
18+
}
1319
}
14-
scope 4 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<u32>) {
15-
scope 5 (inlined std::ptr::metadata::<u32>) {
20+
scope 7 (inlined std::ptr::const_ptr::<impl *const u8>::with_metadata_of::<u32>) {
21+
scope 8 (inlined std::ptr::metadata::<u32>) {
1622
}
17-
scope 6 (inlined std::ptr::from_raw_parts::<u32, ()>) {
23+
scope 9 (inlined std::ptr::from_raw_parts::<u32, ()>) {
1824
}
1925
}
2026
}

0 commit comments

Comments
 (0)