Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3a1b3b30c6cdd674049b144a3ced7b711de962b2
e4931eaaa3d95189b30e90d3af9f0db17c41bbb0
54 changes: 20 additions & 34 deletions src/operator.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use std::convert::TryFrom;

use rustc::ty::{Ty, layout::LayoutOf};
use rustc::ty::{Ty, layout::{Size, LayoutOf}};
use rustc::mir;

use crate::*;

pub trait EvalContextExt<'tcx> {
fn pointer_inbounds(
&self,
ptr: Pointer<Tag>
) -> InterpResult<'tcx>;

fn binary_ptr_op(
&self,
bin_op: mir::BinOp,
Expand All @@ -33,13 +28,6 @@ pub trait EvalContextExt<'tcx> {
}

impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
/// Test if the pointer is in-bounds of a live allocation.
#[inline]
fn pointer_inbounds(&self, ptr: Pointer<Tag>) -> InterpResult<'tcx> {
let (size, _align) = self.memory.get_size_and_align(ptr.alloc_id, AllocCheck::Live)?;
ptr.check_inbounds_alloc(size, CheckInAllocMsg::InboundsTest)
}

fn binary_ptr_op(
&self,
bin_op: mir::BinOp,
Expand Down Expand Up @@ -110,9 +98,8 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
}

/// Raises an error if the offset moves the pointer outside of its allocation.
/// We consider ZSTs their own huge allocation that doesn't overlap with anything (and nothing
/// moves in there because the size is 0). We also consider the NULL pointer its own separate
/// allocation, and all the remaining integers pointers their own allocation.
/// For integers, we consider each of them their own tiny allocation of size 0,
/// so offset-by-0 is okay for them -- except for NULL, which we rule out entirely.
fn pointer_offset_inbounds(
&self,
ptr: Scalar<Tag>,
Expand All @@ -123,25 +110,24 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
let offset = offset
.checked_mul(pointee_size)
.ok_or_else(|| err_panic!(Overflow(mir::BinOp::Mul)))?;
// Now let's see what kind of pointer this is.
let ptr = if offset == 0 {
match ptr {
Scalar::Ptr(ptr) => ptr,
Scalar::Raw { .. } => {
// Offset 0 on an integer. We accept that, pretending there is
// a little zero-sized allocation here.
return Ok(ptr);
}
}
// We do this first, to rule out overflows.
let offset_ptr = ptr.ptr_signed_offset(offset, self)?;
// What we need to check is that starting at `min(ptr, offset_ptr)`,
// we could do an access of size `abs(offset)`. Alignment does not matter.
let (min_ptr, abs_offset) = if offset >= 0 {
(ptr, u64::try_from(offset).unwrap())
} else {
// Offset > 0. We *require* a pointer.
self.force_ptr(ptr)?
// Negative offset.
// If the negation overflows, the result will be negative so the try_from will fail.
(offset_ptr, u64::try_from(-offset).unwrap())
};
// Both old and new pointer must be in-bounds of a *live* allocation.
// (Of the same allocation, but that part is trivial with our representation.)
self.pointer_inbounds(ptr)?;
let ptr = ptr.signed_offset(offset, self)?;
self.pointer_inbounds(ptr)?;
Ok(Scalar::Ptr(ptr))
self.memory.check_ptr_access_align(
min_ptr,
Size::from_bytes(abs_offset),
None,
CheckInAllocMsg::InboundsTest,
)?;
// That's it!
Ok(offset_ptr)
}
}
7 changes: 7 additions & 0 deletions tests/compile-fail/ptr_offset_0_plus_0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// error-pattern: invalid use of NULL pointer

fn main() {
let x = 0 as *mut i32;
let _x = x.wrapping_offset(8); // ok, this has no inbounds tag
let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds
}