From 58f2ede15f9b16e051770c573fb1a393ea0e9268 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 24 Jul 2022 16:01:47 -0400 Subject: [PATCH] interpret, ptr_offset_from: refactor and test too-far-apart check --- .../src/interpret/intrinsics.rs | 72 +++++++++++------ library/core/src/ptr/const_ptr.rs | 2 +- .../const-ptr/forbidden_slices.32bit.stderr | 4 +- .../const-ptr/forbidden_slices.64bit.stderr | 4 +- src/test/ui/consts/offset_from_ub.rs | 39 ++++++++- src/test/ui/consts/offset_from_ub.stderr | 80 +++++++++++++++---- 6 files changed, 153 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 025f8647c95..08209eb7932 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -7,7 +7,7 @@ use rustc_hir::def_id::DefId; use rustc_middle::mir::{ self, - interpret::{ConstValue, GlobalId, InterpResult, Scalar}, + interpret::{ConstValue, GlobalId, InterpResult, PointerArithmetic, Scalar}, BinOp, }; use rustc_middle::ty; @@ -328,7 +328,7 @@ pub fn emulate_intrinsic( // We managed to find a valid allocation for one pointer, but not the other. // That means they are definitely not pointing to the same allocation. throw_ub_format!( - "{} called on pointers into different allocations", + "`{}` called on pointers into different allocations", intrinsic_name ); } @@ -336,7 +336,7 @@ pub fn emulate_intrinsic( // Found allocation for both. They must be into the same allocation. if a_alloc_id != b_alloc_id { throw_ub_format!( - "{} called on pointers into different allocations", + "`{}` called on pointers into different allocations", intrinsic_name ); } @@ -346,47 +346,71 @@ pub fn emulate_intrinsic( }; // Compute distance. - let distance = { - // The subtraction is always done in `isize` to enforce - // the "no more than `isize::MAX` apart" requirement. - let a_offset = ImmTy::from_uint(a_offset, isize_layout); - let b_offset = ImmTy::from_uint(b_offset, isize_layout); - let (val, overflowed, _ty) = - self.overflowing_binary_op(BinOp::Sub, &a_offset, &b_offset)?; + let dist = { + // Addresses are unsigned, so this is a `usize` computation. We have to do the + // overflow check separately anyway. + let (val, overflowed, _ty) = { + let a_offset = ImmTy::from_uint(a_offset, usize_layout); + let b_offset = ImmTy::from_uint(b_offset, usize_layout); + self.overflowing_binary_op(BinOp::Sub, &a_offset, &b_offset)? + }; if overflowed { - throw_ub_format!("pointers were too far apart for {}", intrinsic_name); + // a < b + if intrinsic_name == sym::ptr_offset_from_unsigned { + throw_ub_format!( + "`{}` called when first pointer has smaller offset than second: {} < {}", + intrinsic_name, + a_offset, + b_offset, + ); + } + // The signed form of the intrinsic allows this. If we interpret the + // difference as isize, we'll get the proper signed difference. If that + // seems *positive*, they were more than isize::MAX apart. + let dist = val.to_machine_isize(self)?; + if dist >= 0 { + throw_ub_format!( + "`{}` called when first pointer is too far before second", + intrinsic_name + ); + } + dist + } else { + // b >= a + let dist = val.to_machine_isize(self)?; + // If converting to isize produced a *negative* result, we had an overflow + // because they were more than isize::MAX apart. + if dist < 0 { + throw_ub_format!( + "`{}` called when first pointer is too far ahead of second", + intrinsic_name + ); + } + dist } - val.to_machine_isize(self)? }; // Check that the range between them is dereferenceable ("in-bounds or one past the // end of the same allocation"). This is like the check in ptr_offset_inbounds. - let min_ptr = if distance >= 0 { b } else { a }; + let min_ptr = if dist >= 0 { b } else { a }; self.check_ptr_access_align( min_ptr, - Size::from_bytes(distance.unsigned_abs()), + Size::from_bytes(dist.unsigned_abs()), Align::ONE, CheckInAllocMsg::OffsetFromTest, )?; - if intrinsic_name == sym::ptr_offset_from_unsigned && distance < 0 { - throw_ub_format!( - "{} called when first pointer has smaller offset than second: {} < {}", - intrinsic_name, - a_offset, - b_offset, - ); - } - // Perform division by size to compute return value. let ret_layout = if intrinsic_name == sym::ptr_offset_from_unsigned { + assert!(0 <= dist && dist <= self.machine_isize_max()); usize_layout } else { + assert!(self.machine_isize_min() <= dist && dist <= self.machine_isize_max()); isize_layout }; let pointee_layout = self.layout_of(substs.type_at(0))?; // If ret_layout is unsigned, we checked that so is the distance, so we are good. - let val = ImmTy::from_int(distance, ret_layout); + let val = ImmTy::from_int(dist, ret_layout); let size = ImmTy::from_int(pointee_layout.size.bytes(), ret_layout); self.exact_div(&val, &size, dest)?; } diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 6cdbab30589..922bc8df7c3 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -726,7 +726,7 @@ pub const fn wrapping_byte_offset(self, count: isize) -> Self { /// } /// /// // This would be incorrect, as the pointers are not correctly ordered: - /// // ptr1.offset_from(ptr2) + /// // ptr1.sub_ptr(ptr2) /// ``` #[unstable(feature = "ptr_sub_ptr", issue = "95892")] #[rustc_const_unstable(feature = "const_ptr_sub_ptr", issue = "95892")] diff --git a/src/test/ui/const-ptr/forbidden_slices.32bit.stderr b/src/test/ui/const-ptr/forbidden_slices.32bit.stderr index c2d22ca4917..f3bf9c496da 100644 --- a/src/test/ui/const-ptr/forbidden_slices.32bit.stderr +++ b/src/test/ui/const-ptr/forbidden_slices.32bit.stderr @@ -222,7 +222,7 @@ error[E0080]: could not evaluate static initializer LL | unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | ptr_offset_from_unsigned called on pointers into different allocations + | `ptr_offset_from_unsigned` called on pointers into different allocations | inside `ptr::const_ptr::::sub_ptr` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL | ::: $SRC_DIR/core/src/slice/raw.rs:LL:COL @@ -241,7 +241,7 @@ error[E0080]: could not evaluate static initializer LL | unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | ptr_offset_from_unsigned called on pointers into different allocations + | `ptr_offset_from_unsigned` called on pointers into different allocations | inside `ptr::const_ptr::::sub_ptr` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL | ::: $SRC_DIR/core/src/slice/raw.rs:LL:COL diff --git a/src/test/ui/const-ptr/forbidden_slices.64bit.stderr b/src/test/ui/const-ptr/forbidden_slices.64bit.stderr index da9df1c63a4..5f2821a9193 100644 --- a/src/test/ui/const-ptr/forbidden_slices.64bit.stderr +++ b/src/test/ui/const-ptr/forbidden_slices.64bit.stderr @@ -222,7 +222,7 @@ error[E0080]: could not evaluate static initializer LL | unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | ptr_offset_from_unsigned called on pointers into different allocations + | `ptr_offset_from_unsigned` called on pointers into different allocations | inside `ptr::const_ptr::::sub_ptr` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL | ::: $SRC_DIR/core/src/slice/raw.rs:LL:COL @@ -241,7 +241,7 @@ error[E0080]: could not evaluate static initializer LL | unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | ptr_offset_from_unsigned called on pointers into different allocations + | `ptr_offset_from_unsigned` called on pointers into different allocations | inside `ptr::const_ptr::::sub_ptr` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL | ::: $SRC_DIR/core/src/slice/raw.rs:LL:COL diff --git a/src/test/ui/consts/offset_from_ub.rs b/src/test/ui/consts/offset_from_ub.rs index db2d421427c..1f29a690550 100644 --- a/src/test/ui/consts/offset_from_ub.rs +++ b/src/test/ui/consts/offset_from_ub.rs @@ -2,6 +2,7 @@ #![feature(core_intrinsics)] use std::intrinsics::{ptr_offset_from, ptr_offset_from_unsigned}; +use std::ptr; #[repr(C)] struct Struct { @@ -75,9 +76,21 @@ struct Struct { let base_ptr: *const Struct = &uninit as *const _ as *const Struct; let uninit2 = std::mem::MaybeUninit::::uninit(); let field_ptr: *const Struct = &uninit2 as *const _ as *const Struct; - let offset = unsafe { ptr_offset_from_unsigned(field_ptr, base_ptr) }; //~ERROR evaluation of constant value failed + unsafe { ptr_offset_from_unsigned(field_ptr, base_ptr) } //~ERROR evaluation of constant value failed //~| pointers into different allocations - offset as usize +}; + +pub const TOO_FAR_APART1: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_add(isize::MAX as usize + 42); + unsafe { ptr_offset_from(ptr2, ptr1) } //~ERROR evaluation of constant value failed + //~| too far ahead +}; +pub const TOO_FAR_APART2: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_add(isize::MAX as usize + 42); + unsafe { ptr_offset_from(ptr1, ptr2) } //~ERROR evaluation of constant value failed + //~| too far before }; const WRONG_ORDER_UNSIGNED: usize = { @@ -86,5 +99,27 @@ struct Struct { unsafe { ptr_offset_from_unsigned(p, p.add(2) ) } //~ERROR evaluation of constant value failed //~| first pointer has smaller offset than second: 0 < 8 }; +pub const TOO_FAR_APART_UNSIGNED: usize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_add(isize::MAX as usize + 42); + // This would fit into a `usize` but we still don't allow it. + unsafe { ptr_offset_from_unsigned(ptr2, ptr1) } //~ERROR evaluation of constant value failed + //~| too far ahead +}; + +// These do NOT complain that pointers are too far apart; they pass that check (to then fail the +// next one). +pub const OFFSET_VERY_FAR1: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_offset(isize::MAX); + unsafe { ptr2.offset_from(ptr1) } + //~^ inside +}; +pub const OFFSET_VERY_FAR2: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_offset(isize::MAX); + unsafe { ptr1.offset_from(ptr2.wrapping_offset(1)) } + //~^ inside +}; fn main() {} diff --git a/src/test/ui/consts/offset_from_ub.stderr b/src/test/ui/consts/offset_from_ub.stderr index 94d778bc8a1..62a087d94d3 100644 --- a/src/test/ui/consts/offset_from_ub.stderr +++ b/src/test/ui/consts/offset_from_ub.stderr @@ -1,8 +1,8 @@ error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:17:27 + --> $DIR/offset_from_ub.rs:18:27 | LL | let offset = unsafe { ptr_offset_from(field_ptr, base_ptr) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ptr_offset_from called on pointers into different allocations + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on pointers into different allocations error[E0080]: evaluation of constant value failed --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL @@ -10,62 +10,108 @@ error[E0080]: evaluation of constant value failed LL | unsafe { intrinsics::ptr_offset_from(self, origin) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | ptr_offset_from called on pointers into different allocations + | `ptr_offset_from` called on pointers into different allocations | inside `ptr::const_ptr::::offset_from` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL | - ::: $DIR/offset_from_ub.rs:23:14 + ::: $DIR/offset_from_ub.rs:24:14 | LL | unsafe { (42 as *const u8).offset_from(&5u8) as usize } - | ----------------------------------- inside `NOT_PTR` at $DIR/offset_from_ub.rs:23:14 + | ----------------------------------- inside `NOT_PTR` at $DIR/offset_from_ub.rs:24:14 error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:30:14 + --> $DIR/offset_from_ub.rs:31:14 | LL | unsafe { ptr_offset_from(field_ptr, base_ptr as *const u16) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: 1_isize cannot be divided by 2_isize without remainder error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:36:14 + --> $DIR/offset_from_ub.rs:37:14 | LL | unsafe { ptr_offset_from(ptr, ptr) } | ^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: null pointer is a dangling pointer (it has no provenance) error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:43:14 + --> $DIR/offset_from_ub.rs:44:14 | LL | unsafe { ptr_offset_from(ptr2, ptr1) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: 0x8[noalloc] is a dangling pointer (it has no provenance) error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:52:14 + --> $DIR/offset_from_ub.rs:53:14 | LL | unsafe { ptr_offset_from(end_ptr, start_ptr) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: alloc18 has size 4, so pointer to 10 bytes starting at offset 0 is out-of-bounds error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:61:14 + --> $DIR/offset_from_ub.rs:62:14 | LL | unsafe { ptr_offset_from(start_ptr, end_ptr) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: alloc21 has size 4, so pointer to 10 bytes starting at offset 0 is out-of-bounds error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:69:14 + --> $DIR/offset_from_ub.rs:70:14 | LL | unsafe { ptr_offset_from(end_ptr, end_ptr) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: alloc24 has size 4, so pointer at offset 10 is out-of-bounds error[E0080]: evaluation of constant value failed - --> $DIR/offset_from_ub.rs:78:27 + --> $DIR/offset_from_ub.rs:79:14 | -LL | let offset = unsafe { ptr_offset_from_unsigned(field_ptr, base_ptr) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ptr_offset_from_unsigned called on pointers into different allocations +LL | unsafe { ptr_offset_from_unsigned(field_ptr, base_ptr) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called on pointers into different allocations error[E0080]: evaluation of constant value failed --> $DIR/offset_from_ub.rs:86:14 | -LL | unsafe { ptr_offset_from_unsigned(p, p.add(2) ) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ptr_offset_from_unsigned called when first pointer has smaller offset than second: 0 < 8 +LL | unsafe { ptr_offset_from(ptr2, ptr1) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called when first pointer is too far ahead of second -error: aborting due to 10 previous errors +error[E0080]: evaluation of constant value failed + --> $DIR/offset_from_ub.rs:92:14 + | +LL | unsafe { ptr_offset_from(ptr1, ptr2) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called when first pointer is too far before second + +error[E0080]: evaluation of constant value failed + --> $DIR/offset_from_ub.rs:99:14 + | +LL | unsafe { ptr_offset_from_unsigned(p, p.add(2) ) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 8 + +error[E0080]: evaluation of constant value failed + --> $DIR/offset_from_ub.rs:106:14 + | +LL | unsafe { ptr_offset_from_unsigned(ptr2, ptr1) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer is too far ahead of second + +error[E0080]: evaluation of constant value failed + --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL + | +LL | unsafe { intrinsics::ptr_offset_from(self, origin) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | out-of-bounds offset_from: null pointer is a dangling pointer (it has no provenance) + | inside `ptr::const_ptr::::offset_from` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL + | + ::: $DIR/offset_from_ub.rs:115:14 + | +LL | unsafe { ptr2.offset_from(ptr1) } + | ---------------------- inside `OFFSET_VERY_FAR1` at $DIR/offset_from_ub.rs:115:14 + +error[E0080]: evaluation of constant value failed + --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL + | +LL | unsafe { intrinsics::ptr_offset_from(self, origin) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | out-of-bounds offset_from: null pointer is a dangling pointer (it has no provenance) + | inside `ptr::const_ptr::::offset_from` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL + | + ::: $DIR/offset_from_ub.rs:121:14 + | +LL | unsafe { ptr1.offset_from(ptr2.wrapping_offset(1)) } + | ----------------------------------------- inside `OFFSET_VERY_FAR2` at $DIR/offset_from_ub.rs:121:14 + +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0080`.