Skip to content

Commit 5b38b14

Browse files
committed
miri: fix offset_from behavior on wildcard pointers
1 parent a526d7c commit 5b38b14

11 files changed

+80
-61
lines changed

compiler/rustc_const_eval/messages.ftl

+4-3
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,17 @@ const_eval_nullary_intrinsic_fail =
233233
234234
const_eval_offset_from_different_allocations =
235235
`{$name}` called on pointers into different allocations
236-
const_eval_offset_from_different_integers =
237-
`{$name}` called on different pointers without provenance (i.e., without an associated allocation)
238236
const_eval_offset_from_overflow =
239237
`{$name}` called when first pointer is too far ahead of second
240238
const_eval_offset_from_test =
241239
out-of-bounds `offset_from`
242240
const_eval_offset_from_underflow =
243241
`{$name}` called when first pointer is too far before second
244242
const_eval_offset_from_unsigned_overflow =
245-
`ptr_offset_from_unsigned` called when first pointer has smaller offset than second: {$a_offset} < {$b_offset}
243+
`ptr_offset_from_unsigned` called when first pointer has smaller {$is_addr ->
244+
[true] address
245+
*[false] offset
246+
} than second: {$a_offset} < {$b_offset}
246247
247248
const_eval_operator_non_const =
248249
cannot call non-const operator in {const_eval_const_context}s

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+36-47
Original file line numberDiff line numberDiff line change
@@ -243,36 +243,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
243243
let isize_layout = self.layout_of(self.tcx.types.isize)?;
244244

245245
// Get offsets for both that are at least relative to the same base.
246-
let (a_offset, b_offset) =
246+
// With `OFFSET_IS_ADDR` this is trivial; without it we need either
247+
// two integers or two pointers into the same allocation.
248+
let (a_offset, b_offset, is_addr) = if M::Provenance::OFFSET_IS_ADDR {
249+
(a.addr().bytes(), b.addr().bytes(), /*is_addr*/ true)
250+
} else {
247251
match (self.ptr_try_get_alloc_id(a), self.ptr_try_get_alloc_id(b)) {
248252
(Err(a), Err(b)) => {
249-
// Neither pointer points to an allocation.
250-
// This is okay only if they are the same.
251-
if a != b {
252-
// We'd catch this below in the "dereferenceable" check, but
253-
// show a nicer error for this particular case.
254-
throw_ub_custom!(
255-
fluent::const_eval_offset_from_different_integers,
256-
name = intrinsic_name,
257-
);
258-
}
259-
// This will always return 0.
260-
(a, b)
261-
}
262-
_ if M::Provenance::OFFSET_IS_ADDR && a.addr() == b.addr() => {
263-
// At least one of the pointers has provenance, but they also point to
264-
// the same address so it doesn't matter; this is fine. `(0, 0)` means
265-
// we pass all the checks below and return 0.
266-
(0, 0)
253+
// Neither pointer points to an allocation, so they are both absolute.
254+
(a, b, /*is_addr*/ true)
267255
}
268-
// From here onwards, the pointers are definitely for different addresses
269-
// (or we can't determine their absolute address).
270256
(Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _)))
271257
if a_alloc_id == b_alloc_id =>
272258
{
273259
// Found allocation for both, and it's the same.
274260
// Use these offsets for distance calculation.
275-
(a_offset.bytes(), b_offset.bytes())
261+
(a_offset.bytes(), b_offset.bytes(), /*is_addr*/ false)
276262
}
277263
_ => {
278264
// Not into the same allocation -- this is UB.
@@ -281,9 +267,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
281267
name = intrinsic_name,
282268
);
283269
}
284-
};
270+
}
271+
};
285272

286-
// Compute distance.
273+
// Compute distance: a - b.
287274
let dist = {
288275
// Addresses are unsigned, so this is a `usize` computation. We have to do the
289276
// overflow check separately anyway.
@@ -300,6 +287,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
300287
fluent::const_eval_offset_from_unsigned_overflow,
301288
a_offset = a_offset,
302289
b_offset = b_offset,
290+
is_addr = is_addr,
303291
);
304292
}
305293
// The signed form of the intrinsic allows this. If we interpret the
@@ -328,14 +316,23 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
328316
}
329317
};
330318

331-
// Check that the range between them is dereferenceable ("in-bounds or one past the
332-
// end of the same allocation"). This is like the check in ptr_offset_inbounds.
333-
let min_ptr = if dist >= 0 { b } else { a };
334-
self.check_ptr_access(
335-
min_ptr,
336-
Size::from_bytes(dist.unsigned_abs()),
319+
// Check that the memory between them is dereferenceable at all, starting from the
320+
// base pointer: `dist` is `a - b`, so it is based on `b`.
321+
self.check_ptr_access_signed(b, dist, CheckInAllocMsg::OffsetFromTest)?;
322+
// Then check that this is also dereferenceable from `a`. This ensures that they are
323+
// derived from the same allocation.
324+
self.check_ptr_access_signed(
325+
a,
326+
dist.checked_neg().unwrap(), // i64::MIN is impossible as no allocation can be that large
337327
CheckInAllocMsg::OffsetFromTest,
338-
)?;
328+
)
329+
.map_err(|_| {
330+
// Make the error more specific.
331+
err_ub_custom!(
332+
fluent::const_eval_offset_from_different_allocations,
333+
name = intrinsic_name,
334+
)
335+
})?;
339336

340337
// Perform division by size to compute return value.
341338
let ret_layout = if intrinsic_name == sym::ptr_offset_from_unsigned {
@@ -582,27 +579,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
582579
}
583580

584581
/// Offsets a pointer by some multiple of its type, returning an error if the pointer leaves its
585-
/// allocation. For integer pointers, we consider each of them their own tiny allocation of size
586-
/// 0, so offset-by-0 (and only 0) is okay -- except that null cannot be offset by _any_ value.
582+
/// allocation.
587583
pub fn ptr_offset_inbounds(
588584
&self,
589585
ptr: Pointer<Option<M::Provenance>>,
590586
offset_bytes: i64,
591587
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
592-
// The offset being in bounds cannot rely on "wrapping around" the address space.
593-
// So, first rule out overflows in the pointer arithmetic.
594-
let offset_ptr = ptr.signed_offset(offset_bytes, self)?;
595-
// ptr and offset_ptr must be in bounds of the same allocated object. This means all of the
596-
// memory between these pointers must be accessible. Note that we do not require the
597-
// pointers to be properly aligned (unlike a read/write operation).
598-
let min_ptr = if offset_bytes >= 0 { ptr } else { offset_ptr };
599-
// This call handles checking for integer/null pointers.
600-
self.check_ptr_access(
601-
min_ptr,
602-
Size::from_bytes(offset_bytes.unsigned_abs()),
603-
CheckInAllocMsg::PointerArithmeticTest,
604-
)?;
605-
Ok(offset_ptr)
588+
// We first compute the pointer with overflow checks, to get a specific error for when it
589+
// overflows (though technically this is redundant with the following inbounds check).
590+
let result = ptr.signed_offset(offset_bytes, self)?;
591+
// The offset must be in bounds starting from `ptr`.
592+
self.check_ptr_access_signed(ptr, offset_bytes, CheckInAllocMsg::PointerArithmeticTest)?;
593+
// Done.
594+
Ok(result)
606595
}
607596

608597
/// Copy `count*size_of::<T>()` many bytes from `*src` to `*dst`.

compiler/rustc_const_eval/src/interpret/memory.rs

+19
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
414414
Ok(())
415415
}
416416

417+
/// Check whether the given pointer points to live memory for a signed amount of bytes.
418+
/// A negative amounts means that the given range of memory to the left of the pointer
419+
/// needs to be dereferenceable.
420+
pub fn check_ptr_access_signed(
421+
&self,
422+
ptr: Pointer<Option<M::Provenance>>,
423+
size: i64,
424+
msg: CheckInAllocMsg,
425+
) -> InterpResult<'tcx> {
426+
if let Ok(size) = u64::try_from(size) {
427+
self.check_ptr_access(ptr, Size::from_bytes(size), msg)
428+
} else {
429+
// Compute the pointer at the beginning of the range, and do the standard
430+
// dereferenceability check from there.
431+
let begin_ptr = ptr.wrapping_signed_offset(size, self);
432+
self.check_ptr_access(begin_ptr, Size::from_bytes(size.unsigned_abs()), msg)
433+
}
434+
}
435+
417436
/// Low-level helper function to check if a ptr is in-bounds and potentially return a reference
418437
/// to the allocation it points to. Supports both shared and mutable references, as the actual
419438
/// checking is offloaded to a helper closure.

src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ fn main() {
1616
let _ = p1.byte_offset_from(p1);
1717

1818
// UB because different pointers.
19-
let _ = p1.byte_offset_from(p2); //~ERROR: different pointers without provenance
19+
let _ = p1.byte_offset_from(p2); //~ERROR: no provenance
2020
}
2121
}

src/tools/miri/tests/fail/intrinsics/ptr_offset_from_different_ints.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation)
1+
error: Undefined Behavior: out-of-bounds `offset_from`: 0xa[noalloc] is a dangling pointer (it has no provenance)
22
--> $DIR/ptr_offset_from_different_ints.rs:LL:CC
33
|
44
LL | let _ = p1.byte_offset_from(p2);
5-
| ^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds `offset_from`: 0xa[noalloc] is a dangling pointer (it has no provenance)
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
//@normalize-stderr-test: "\d+ < \d+" -> "$$ADDR < $$ADDR"
12
#![feature(ptr_sub_ptr)]
23

34
fn main() {
45
let arr = [0u8; 8];
56
let ptr1 = arr.as_ptr();
67
let ptr2 = ptr1.wrapping_add(4);
7-
let _val = unsafe { ptr1.sub_ptr(ptr2) }; //~ERROR: first pointer has smaller offset than second: 0 < 4
8+
let _val = unsafe { ptr1.sub_ptr(ptr2) }; //~ERROR: first pointer has smaller address than second
89
}

src/tools/miri/tests/fail/intrinsics/ptr_offset_from_unsigned_neg.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4
1+
error: Undefined Behavior: `ptr_offset_from_unsigned` called when first pointer has smaller address than second: $ADDR < $ADDR
22
--> $DIR/ptr_offset_from_unsigned_neg.rs:LL:CC
33
|
44
LL | let _val = unsafe { ptr1.sub_ptr(ptr2) };
5-
| ^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: 0 < 4
5+
| ^^^^^^^^^^^^^^^^^^ `ptr_offset_from_unsigned` called when first pointer has smaller address than second: $ADDR < $ADDR
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/pass/ptr_int_from_exposed.rs

+9
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,18 @@ fn ptr_roundtrip_null() {
5656
assert_eq!(unsafe { *x_ptr_copy }, 42);
5757
}
5858

59+
fn ptr_roundtrip_offset_from() {
60+
let arr = [0; 5];
61+
let begin = arr.as_ptr();
62+
let end = begin.wrapping_add(arr.len());
63+
let end_roundtrip = ptr::with_exposed_provenance::<i32>(end.expose_provenance());
64+
unsafe { end_roundtrip.offset_from(begin) };
65+
}
66+
5967
fn main() {
6068
ptr_roundtrip_out_of_bounds();
6169
ptr_roundtrip_confusion();
6270
ptr_roundtrip_imperfect();
6371
ptr_roundtrip_null();
72+
ptr_roundtrip_offset_from();
6473
}

tests/ui/consts/offset_from_ub.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub const DIFFERENT_INT: isize = { // offset_from with two different integers: l
3636
let ptr1 = 8 as *const u8;
3737
let ptr2 = 16 as *const u8;
3838
unsafe { ptr_offset_from(ptr2, ptr1) } //~ERROR evaluation of constant value failed
39-
//~| different pointers without provenance
39+
//~| dangling pointer
4040
};
4141

4242
const OUT_OF_BOUNDS_1: isize = {

tests/ui/consts/offset_from_ub.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ error[E0080]: evaluation of constant value failed
2727
--> $DIR/offset_from_ub.rs:38:14
2828
|
2929
LL | unsafe { ptr_offset_from(ptr2, ptr1) }
30-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation)
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds `offset_from`: 0x8[noalloc] is a dangling pointer (it has no provenance)
3131

3232
error[E0080]: evaluation of constant value failed
3333
--> $DIR/offset_from_ub.rs:47:14
@@ -80,7 +80,7 @@ LL | unsafe { ptr_offset_from_unsigned(ptr2, ptr1) }
8080
error[E0080]: evaluation of constant value failed
8181
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
8282
|
83-
= note: `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation)
83+
= note: out-of-bounds `offset_from`: null pointer is a dangling pointer (it has no provenance)
8484
|
8585
note: inside `std::ptr::const_ptr::<impl *const u8>::offset_from`
8686
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
@@ -93,7 +93,7 @@ LL | unsafe { ptr2.offset_from(ptr1) }
9393
error[E0080]: evaluation of constant value failed
9494
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
9595
|
96-
= note: `ptr_offset_from` called on different pointers without provenance (i.e., without an associated allocation)
96+
= note: `ptr_offset_from` called when first pointer is too far before second
9797
|
9898
note: inside `std::ptr::const_ptr::<impl *const u8>::offset_from`
9999
--> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL

tests/ui/consts/offset_ub.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ptr;
22

3-
3+
//@ normalize-stderr-test: "0xf+" -> "0xf..f"
44
//@ normalize-stderr-test: "0x7f+" -> "0x7f..f"
55

66

0 commit comments

Comments
 (0)