Skip to content

Commit 4ddc37d

Browse files
committed
Auto merge of #1031 - RalfJung:ptr-offset, r=RalfJung
Refactor ptr_offset_inbounds I finally found a way to write this using basically just `check_ptr_access` while handling all cases (integers and pointers, offset 0 or not) correctly. This changes behavior for NULL ptrs, but I think the change is for the better. Depends on rust-lang/rust#66081.
2 parents 61fd7e3 + 8b0c14f commit 4ddc37d

File tree

3 files changed

+28
-35
lines changed

3 files changed

+28
-35
lines changed

rust-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3a1b3b30c6cdd674049b144a3ced7b711de962b2
1+
e4931eaaa3d95189b30e90d3af9f0db17c41bbb0

src/operator.rs

+20-34
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
use std::convert::TryFrom;
22

3-
use rustc::ty::{Ty, layout::LayoutOf};
3+
use rustc::ty::{Ty, layout::{Size, LayoutOf}};
44
use rustc::mir;
55

66
use crate::*;
77

88
pub trait EvalContextExt<'tcx> {
9-
fn pointer_inbounds(
10-
&self,
11-
ptr: Pointer<Tag>
12-
) -> InterpResult<'tcx>;
13-
149
fn binary_ptr_op(
1510
&self,
1611
bin_op: mir::BinOp,
@@ -33,13 +28,6 @@ pub trait EvalContextExt<'tcx> {
3328
}
3429

3530
impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
36-
/// Test if the pointer is in-bounds of a live allocation.
37-
#[inline]
38-
fn pointer_inbounds(&self, ptr: Pointer<Tag>) -> InterpResult<'tcx> {
39-
let (size, _align) = self.memory.get_size_and_align(ptr.alloc_id, AllocCheck::Live)?;
40-
ptr.check_inbounds_alloc(size, CheckInAllocMsg::InboundsTest)
41-
}
42-
4331
fn binary_ptr_op(
4432
&self,
4533
bin_op: mir::BinOp,
@@ -110,9 +98,8 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
11098
}
11199

112100
/// Raises an error if the offset moves the pointer outside of its allocation.
113-
/// We consider ZSTs their own huge allocation that doesn't overlap with anything (and nothing
114-
/// moves in there because the size is 0). We also consider the NULL pointer its own separate
115-
/// allocation, and all the remaining integers pointers their own allocation.
101+
/// For integers, we consider each of them their own tiny allocation of size 0,
102+
/// so offset-by-0 is okay for them -- except for NULL, which we rule out entirely.
116103
fn pointer_offset_inbounds(
117104
&self,
118105
ptr: Scalar<Tag>,
@@ -123,25 +110,24 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
123110
let offset = offset
124111
.checked_mul(pointee_size)
125112
.ok_or_else(|| err_panic!(Overflow(mir::BinOp::Mul)))?;
126-
// Now let's see what kind of pointer this is.
127-
let ptr = if offset == 0 {
128-
match ptr {
129-
Scalar::Ptr(ptr) => ptr,
130-
Scalar::Raw { .. } => {
131-
// Offset 0 on an integer. We accept that, pretending there is
132-
// a little zero-sized allocation here.
133-
return Ok(ptr);
134-
}
135-
}
113+
// We do this first, to rule out overflows.
114+
let offset_ptr = ptr.ptr_signed_offset(offset, self)?;
115+
// What we need to check is that starting at `min(ptr, offset_ptr)`,
116+
// we could do an access of size `abs(offset)`. Alignment does not matter.
117+
let (min_ptr, abs_offset) = if offset >= 0 {
118+
(ptr, u64::try_from(offset).unwrap())
136119
} else {
137-
// Offset > 0. We *require* a pointer.
138-
self.force_ptr(ptr)?
120+
// Negative offset.
121+
// If the negation overflows, the result will be negative so the try_from will fail.
122+
(offset_ptr, u64::try_from(-offset).unwrap())
139123
};
140-
// Both old and new pointer must be in-bounds of a *live* allocation.
141-
// (Of the same allocation, but that part is trivial with our representation.)
142-
self.pointer_inbounds(ptr)?;
143-
let ptr = ptr.signed_offset(offset, self)?;
144-
self.pointer_inbounds(ptr)?;
145-
Ok(Scalar::Ptr(ptr))
124+
self.memory.check_ptr_access_align(
125+
min_ptr,
126+
Size::from_bytes(abs_offset),
127+
None,
128+
CheckInAllocMsg::InboundsTest,
129+
)?;
130+
// That's it!
131+
Ok(offset_ptr)
146132
}
147133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// error-pattern: invalid use of NULL pointer
2+
3+
fn main() {
4+
let x = 0 as *mut i32;
5+
let _x = x.wrapping_offset(8); // ok, this has no inbounds tag
6+
let _x = unsafe { x.offset(0) }; // UB despite offset 0, NULL is never inbounds
7+
}

0 commit comments

Comments
 (0)