Skip to content

Commit

Permalink
Rollup merge of rust-lang#71500 - josephlr:offset, r=oli-obk,RalfJung
Browse files Browse the repository at this point in the history
Make pointer offset methods/intrinsics const

Implements rust-lang#71499 using [the implementations from miri](https://github.com/rust-lang/miri/blob/52f5d202bdcfe8986f0615845f8d1647ab8a2c6a/src/shims/intrinsics.rs#L96-L112).

I added some tests what's allowed and what's UB. Let me know if any other cases should be added.

CC: @RalfJung @oli-obk
  • Loading branch information
RalfJung authored May 29, 2020
2 parents 7823651 + 7d5415b commit 642e47e
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 49 deletions.
2 changes: 2 additions & 0 deletions src/libcore/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,7 @@ extern "rust-intrinsic" {
/// The stabilized version of this intrinsic is
/// [`std::pointer::offset`](../../std/primitive.pointer.html#method.offset).
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
pub fn offset<T>(dst: *const T, offset: isize) -> *const T;

/// Calculates the offset from a pointer, potentially wrapping.
Expand All @@ -1331,6 +1332,7 @@ extern "rust-intrinsic" {
/// The stabilized version of this intrinsic is
/// [`std::pointer::wrapping_offset`](../../std/primitive.pointer.html#method.wrapping_offset).
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;

/// Equivalent to the appropriate `llvm.memcpy.p0i8.0i8.*` intrinsic, with
Expand Down
1 change: 1 addition & 0 deletions src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
#![feature(const_panic)]
#![feature(const_fn_union)]
#![feature(const_generics)]
#![feature(const_ptr_offset)]
#![feature(const_ptr_offset_from)]
#![feature(const_result)]
#![feature(const_slice_from_raw_parts)]
Expand Down
18 changes: 12 additions & 6 deletions src/libcore/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn offset(self, count: isize) -> *const T
pub const unsafe fn offset(self, count: isize) -> *const T
where
T: Sized,
{
Expand Down Expand Up @@ -210,8 +211,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "ptr_wrapping_offset", since = "1.16.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_offset(self, count: isize) -> *const T
pub const fn wrapping_offset(self, count: isize) -> *const T
where
T: Sized,
{
Expand Down Expand Up @@ -393,8 +395,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn add(self, count: usize) -> Self
pub const unsafe fn add(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -455,8 +458,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn sub(self, count: usize) -> Self
pub const unsafe fn sub(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -511,8 +515,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_add(self, count: usize) -> Self
pub const fn wrapping_add(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -567,8 +572,9 @@ impl<T: ?Sized> *const T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_sub(self, count: usize) -> Self
pub const fn wrapping_sub(self, count: usize) -> Self
where
T: Sized,
{
Expand Down
18 changes: 12 additions & 6 deletions src/libcore/ptr/mut_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn offset(self, count: isize) -> *mut T
pub const unsafe fn offset(self, count: isize) -> *mut T
where
T: Sized,
{
Expand Down Expand Up @@ -203,8 +204,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "ptr_wrapping_offset", since = "1.16.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_offset(self, count: isize) -> *mut T
pub const fn wrapping_offset(self, count: isize) -> *mut T
where
T: Sized,
{
Expand Down Expand Up @@ -439,8 +441,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn add(self, count: usize) -> Self
pub const unsafe fn add(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -501,8 +504,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub unsafe fn sub(self, count: usize) -> Self
pub const unsafe fn sub(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -557,8 +561,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_add(self, count: usize) -> Self
pub const fn wrapping_add(self, count: usize) -> Self
where
T: Sized,
{
Expand Down Expand Up @@ -613,8 +618,9 @@ impl<T: ?Sized> *mut T {
/// ```
#[stable(feature = "pointer_methods", since = "1.26.0")]
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
#[inline]
pub fn wrapping_sub(self, count: usize) -> Self
pub const fn wrapping_sub(self, count: usize) -> Self
where
T: Sized,
{
Expand Down
9 changes: 9 additions & 0 deletions src/librustc_middle/mir/interpret/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,3 +598,12 @@ pub fn truncate(value: u128, size: Size) -> u128 {
// Truncate (shift left to drop out leftover values, shift right to fill with zeroes).
(value << shift) >> shift
}

/// Computes the unsigned absolute value without wrapping or panicking.
#[inline]
pub fn uabs(value: i64) -> u64 {
// The only tricky part here is if value == i64::MIN. In that case,
// wrapping_abs() returns i64::MIN == -2^63. Casting this value to a u64
// gives 2^63, the correct value.
value.wrapping_abs() as u64
}
26 changes: 17 additions & 9 deletions src/librustc_middle/mir/interpret/pointer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{AllocId, InterpResult};
use super::{uabs, AllocId, InterpResult};

use rustc_macros::HashStable;
use rustc_target::abi::{HasDataLayout, Size};
Expand All @@ -24,6 +24,12 @@ pub trait PointerArithmetic: HasDataLayout {
u64::try_from(max_usize_plus_1 - 1).unwrap()
}

#[inline]
fn machine_isize_min(&self) -> i64 {
let max_isize_plus_1 = 1i128 << (self.pointer_size().bits() - 1);
i64::try_from(-max_isize_plus_1).unwrap()
}

#[inline]
fn machine_isize_max(&self) -> i64 {
let max_isize_plus_1 = 1u128 << (self.pointer_size().bits() - 1);
Expand All @@ -42,21 +48,23 @@ pub trait PointerArithmetic: HasDataLayout {

#[inline]
fn overflowing_offset(&self, val: u64, i: u64) -> (u64, bool) {
// We do not need to check if i fits in a machine usize. If it doesn't,
// either the wrapping_add will wrap or res will not fit in a pointer.
let res = val.overflowing_add(i);
self.truncate_to_ptr(res)
}

#[inline]
fn overflowing_signed_offset(&self, val: u64, i: i64) -> (u64, bool) {
if i < 0 {
// Trickery to ensure that `i64::MIN` works fine: compute `n = -i`.
// This formula only works for true negative values; it overflows for zero!
let n = u64::MAX - (i as u64) + 1;
let res = val.overflowing_sub(n);
self.truncate_to_ptr(res)
// We need to make sure that i fits in a machine isize.
let n = uabs(i);
if i >= 0 {
let (val, over) = self.overflowing_offset(val, n);
(val, over || i > self.machine_isize_max())
} else {
// `i >= 0`, so the cast is safe.
self.overflowing_offset(val, i as u64)
let res = val.overflowing_sub(n);
let (val, over) = self.truncate_to_ptr(res);
(val, over || i < self.machine_isize_min())
}
}

Expand Down
57 changes: 54 additions & 3 deletions src/librustc_mir/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
//! looking at their MIR. Intrinsics/functions supported here are shared by CTFE
//! and miri.

use std::convert::TryFrom;

use rustc_hir::def_id::DefId;
use rustc_middle::mir::{
self,
interpret::{ConstValue, GlobalId, InterpResult, Scalar},
interpret::{uabs, ConstValue, GlobalId, InterpResult, Scalar},
BinOp,
};
use rustc_middle::ty;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_span::symbol::{sym, Symbol};
use rustc_target::abi::{Abi, LayoutOf as _, Primitive, Size};

use super::{ImmTy, InterpCx, Machine, OpTy, PlaceTy};
use super::{CheckInAllocMsg, ImmTy, InterpCx, Machine, OpTy, PlaceTy};

mod caller_location;
mod type_name;
Expand Down Expand Up @@ -279,7 +281,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let result = Scalar::from_uint(truncated_bits, layout.size);
self.write_scalar(result, dest)?;
}
sym::offset => {
let ptr = self.read_scalar(args[0])?.not_undef()?;
let offset_count = self.read_scalar(args[1])?.to_machine_isize(self)?;
let pointee_ty = substs.type_at(0);

let offset_ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset_count)?;
self.write_scalar(offset_ptr, dest)?;
}
sym::arith_offset => {
let ptr = self.read_scalar(args[0])?.not_undef()?;
let offset_count = self.read_scalar(args[1])?.to_machine_isize(self)?;
let pointee_ty = substs.type_at(0);

let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
let offset_bytes = offset_count.wrapping_mul(pointee_size);
let offset_ptr = ptr.ptr_wrapping_signed_offset(offset_bytes, self);
self.write_scalar(offset_ptr, dest)?;
}
sym::ptr_offset_from => {
let a = self.read_immediate(args[0])?.to_scalar()?;
let b = self.read_immediate(args[1])?.to_scalar()?;
Expand Down Expand Up @@ -409,4 +428,36 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// `Rem` says this is all right, so we can let `Div` do its job.
self.binop_ignore_overflow(BinOp::Div, a, b, dest)
}

/// Offsets a pointer by some multiple of its type, returning an error if the pointer leaves its
/// allocation. For integer pointers, we consider each of them their own tiny allocation of size
/// 0, so offset-by-0 (and only 0) is okay -- except that NULL cannot be offset by _any_ value.
pub fn ptr_offset_inbounds(
&self,
ptr: Scalar<M::PointerTag>,
pointee_ty: Ty<'tcx>,
offset_count: i64,
) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
// We cannot overflow i64 as a type's size must be <= isize::MAX.
let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
// The computed offset, in bytes, cannot overflow an isize.
let offset_bytes =
offset_count.checked_mul(pointee_size).ok_or(err_ub!(PointerArithOverflow))?;
// The offset being in bounds cannot rely on "wrapping around" the address space.
// So, first rule out overflows in the pointer arithmetic.
let offset_ptr = ptr.ptr_signed_offset(offset_bytes, self)?;
// ptr and offset_ptr must be in bounds of the same allocated object. This means all of the
// memory between these pointers must be accessible. Note that we do not require the
// pointers to be properly aligned (unlike a read/write operation).
let min_ptr = if offset_bytes >= 0 { ptr } else { offset_ptr };
let size: u64 = uabs(offset_bytes);
// This call handles checking for integer/NULL pointers.
self.memory.check_ptr_access_align(
min_ptr,
Size::from_bytes(size),
None,
CheckInAllocMsg::InboundsTest,
)?;
Ok(offset_ptr)
}
}
2 changes: 2 additions & 0 deletions src/librustc_span/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ symbols! {
Arc,
Arguments,
ArgumentV1,
arith_offset,
arm_target_feature,
asm,
assert,
Expand Down Expand Up @@ -516,6 +517,7 @@ symbols! {
not,
note,
object_safe_for_dispatch,
offset,
Ok,
omit_gdb_pretty_printer_section,
on,
Expand Down
10 changes: 1 addition & 9 deletions src/test/ui/consts/miri_unleashed/ptr_arith.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
#![feature(core_intrinsics)]
#![allow(const_err)]

// A test demonstrating that we prevent doing even trivial
// pointer arithmetic or comparison during CTFE.
// During CTFE, we prevent pointer comparison and pointer-to-int casts.

static CMP: () = {
let x = &0 as *const _;
Expand All @@ -19,11 +18,4 @@ static INT_PTR_ARITH: () = unsafe {
//~| NOTE pointer-to-integer cast
};

static PTR_ARITH: () = unsafe {
let x = &0 as *const _;
let _v = core::intrinsics::offset(x, 0);
//~^ ERROR could not evaluate static initializer
//~| NOTE calling intrinsic `offset`
};

fn main() {}
21 changes: 5 additions & 16 deletions src/test/ui/consts/miri_unleashed/ptr_arith.stderr
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
error[E0080]: could not evaluate static initializer
--> $DIR/ptr_arith.rs:10:14
--> $DIR/ptr_arith.rs:9:14
|
LL | let _v = x == x;
| ^^^^^^ "pointer arithmetic or comparison" needs an rfc before being allowed inside constants

error[E0080]: could not evaluate static initializer
--> $DIR/ptr_arith.rs:17:14
--> $DIR/ptr_arith.rs:16:14
|
LL | let _v = x + 0;
| ^^^^^ "pointer-to-integer cast" needs an rfc before being allowed inside constants

error[E0080]: could not evaluate static initializer
--> $DIR/ptr_arith.rs:24:14
|
LL | let _v = core::intrinsics::offset(x, 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "calling intrinsic `offset`" needs an rfc before being allowed inside constants

warning: skipping const checks
|
help: skipping check for `const_compare_raw_pointers` feature
--> $DIR/ptr_arith.rs:10:14
--> $DIR/ptr_arith.rs:9:14
|
LL | let _v = x == x;
| ^^^^^^
help: skipping check that does not even have a feature gate
--> $DIR/ptr_arith.rs:16:20
--> $DIR/ptr_arith.rs:15:20
|
LL | let x: usize = std::mem::transmute(&0);
| ^^^^^^^^^^^^^^^^^^^^^^^
help: skipping check that does not even have a feature gate
--> $DIR/ptr_arith.rs:24:14
|
LL | let _v = core::intrinsics::offset(x, 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 3 previous errors; 1 warning emitted
error: aborting due to 2 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0080`.
Loading

0 comments on commit 642e47e

Please sign in to comment.