Skip to content

Commit 4f176c6

Browse files
committed
more compile-fail ptr equality tests, to rule out any non-determinism; and fix ptr equality to fail all of them.
At least these are the cases I can think of right now.
1 parent e08f53c commit 4f176c6

File tree

8 files changed

+102
-14
lines changed

8 files changed

+102
-14
lines changed

src/memory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub enum MemoryKind {
4646
C,
4747
/// Part of env var emulation
4848
Env,
49-
// mutable statics
49+
/// mutable statics
5050
MutStatic,
5151
}
5252

src/operator.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,50 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for EvalContext<'a, 'mir, 'tcx, super:
152152
(Scalar::Ptr(ptr), Scalar::Bits { bits, size }) |
153153
(Scalar::Bits { bits, size }, Scalar::Ptr(ptr)) => {
154154
assert_eq!(size as u64, self.pointer_size().bytes());
155+
let bits = bits as u64;
156+
let (alloc_size, alloc_align) = self.memory.get_size_and_align(ptr.alloc_id)?;
155157

158+
// Case I: Comparing with NULL
156159
if bits == 0 {
157-
// Nothing equals 0, not even dangling pointers. Ideally we would
158-
// require them to be in-bounds of their (possilby dead) allocation,
159-
// but with the allocation gonew e cannot check that.
160-
false
161-
} else {
162-
// Live pointers cannot equal an integer, but again do not
163-
// allow comparing dead pointers.
164-
self.memory.check_bounds(ptr, false)?;
165-
false
160+
// Test if the ptr is in-bounds. Then it cannot be NULL.
161+
if ptr.offset <= alloc_size {
162+
return Ok(false);
163+
}
164+
}
165+
// Case II: Alignment gives it away
166+
if ptr.offset.bytes() % alloc_align.abi() == 0 {
167+
// The offset maintains the allocation alignment, so we know `base+offset`
168+
// is aligned by `alloc_align`.
169+
// FIXME: We could be even more general, e.g. offset 2 into a 4-aligned
170+
// allocation cannot equal 3.
171+
if bits % alloc_align.abi() != 0 {
172+
// The integer is *not* aligned. So they cannot be equal.
173+
return Ok(false);
174+
}
166175
}
176+
// Case III: The integer is too big, and the allocation goes on a bit
177+
// without wrapping around the address space.
178+
{
179+
// Compute the highest address at which this allocation could live.
180+
// Substract one more, because it must be possible to add the size
181+
// to the base address without overflowing -- IOW, the very last address
182+
// of the address space is never dereferencable (but it can be in-bounds, i.e.,
183+
// one-past-the-end).
184+
let max_base_addr =
185+
((1u128 << self.pointer_size().bits())
186+
- u128::from(alloc_size.bytes())
187+
- 1
188+
) as u64;
189+
if let Some(max_addr) = max_base_addr.checked_add(ptr.offset.bytes()) {
190+
if bits > max_addr {
191+
// The integer is too big, this cannot possibly be equal
192+
return Ok(false)
193+
}
194+
}
195+
}
196+
197+
// None of the supported cases.
198+
return err!(InvalidPointerMath);
167199
}
168200
})
169201
}

tests/compile-fail/ptr_eq_dangling.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fn main() {
2+
let b = Box::new(0);
3+
let x = &*b as *const i32; // soon-to-be dangling
4+
drop(b);
5+
let b = Box::new(0);
6+
let y = &*b as *const i32; // different allocation
7+
// We cannot compare these even though both are inbounds -- they *could* be
8+
// equal if memory was reused.
9+
assert!(x != y); //~ ERROR dangling pointer
10+
}

tests/compile-fail/ptr_eq_integer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use std::mem;
2+
3+
fn main() {
4+
let b = Box::new(0);
5+
let x = &*b as *const i32;
6+
// We cannot compare this with a non-NULL integer. After all, these *could* be equal (with the right base address).
7+
assert!(x != mem::align_of::<i32>() as *const i32); //~ ERROR invalid arithmetic on pointers
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fn main() {
2+
let b = Box::new(0);
3+
let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
4+
let b = Box::new(0);
5+
let y = &*b as *const i32; // different allocation
6+
// We cannot compare these even though both allocations are live -- they *could* be
7+
// equal (with the right base addresses).
8+
assert!(x != y); //~ ERROR outside bounds
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
let b = Box::new(0);
3+
let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
4+
// We cannot compare this with NULL. After all, this *could* be NULL (with the right base address).
5+
assert!(x != std::ptr::null()); //~ ERROR invalid arithmetic on pointers
6+
}

tests/run-pass-fullmir/loop-break-value.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ pub fn main() {
6161
break Default::default()
6262
};
6363
};
64-
assert_eq!(trait_unified_2, [""]);
64+
// compare lengths; ptr comparison is not deterministic
65+
assert_eq!(trait_unified_2.len(), 1);
66+
assert_eq!(trait_unified_2[0].len(), 0);
6567

6668
let trait_unified_3 = loop {
6769
break if false {

tests/run-pass/pointers.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::usize;
2+
13
fn one_line_ref() -> i16 {
24
*&1
35
}
@@ -44,8 +46,8 @@ fn match_ref_mut() -> i8 {
4446
}
4547

4648
fn dangling_pointer() -> *const i32 {
47-
let b = Box::new(42);
48-
&*b as *const i32
49+
let b = Box::new((42, 42)); // make it bigger than the alignment, so that there is some "room" after this pointer
50+
&b.0 as *const i32
4951
}
5052

5153
fn main() {
@@ -56,10 +58,29 @@ fn main() {
5658
assert_eq!(tuple_ref_mut(), (10, 22));
5759
assert_eq!(match_ref_mut(), 42);
5860

59-
// Compare even dangling pointers with NULL, and with others in the same allocation.
61+
// Compare even dangling pointers with NULL, and with others in the same allocation, including
62+
// out-of-bounds.
6063
assert!(dangling_pointer() != std::ptr::null());
6164
assert!(match dangling_pointer() as usize { 0 => false, _ => true });
6265
let dangling = dangling_pointer();
6366
assert!(dangling == dangling);
6467
assert!(dangling.wrapping_add(1) != dangling);
68+
assert!(dangling.wrapping_sub(1) != dangling);
69+
70+
// Compare pointer with BIG integers
71+
let dangling = dangling as usize;
72+
assert!(dangling != usize::MAX);
73+
assert!(dangling != usize::MAX - 1);
74+
assert!(dangling != usize::MAX - 2);
75+
assert!(dangling != usize::MAX - 3); // this is even 4-aligned, but it still cannot be equal because of the extra "room" after this pointer
76+
assert_eq!((usize::MAX - 3) % 4, 0); // just to be sure we got this right
77+
78+
// Compare pointer with unaligned integers
79+
assert!(dangling != 1usize);
80+
assert!(dangling != 2usize);
81+
assert!(dangling != 3usize);
82+
// 4 is a possible choice! So we cannot compare with that.
83+
assert!(dangling != 5usize);
84+
assert!(dangling != 6usize);
85+
assert!(dangling != 7usize);
6586
}

0 commit comments

Comments
 (0)