Skip to content

Commit 2a24220

Browse files
committed
A pointer cannot be equal to an integer if that would imply the null pointer is misaligned or in-bounds of the pointer's allocation.
1 parent edbafa2 commit 2a24220

File tree

2 files changed

+29
-30
lines changed

2 files changed

+29
-30
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -280,37 +280,25 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
280280
interp_ok(match (a, b) {
281281
// Comparisons between integers are always known.
282282
(Scalar::Int(a), Scalar::Int(b)) => (a == b) as u8,
283-
// Comparisons of null with an arbitrary scalar can be known if `scalar_may_be_null`
284-
// indicates that the scalar can definitely *not* be null.
285-
(Scalar::Int(int), ptr) | (ptr, Scalar::Int(int))
286-
if int.is_null() && !self.scalar_may_be_null(ptr)? =>
287-
{
288-
0
289-
}
290-
// Other ways of comparing integers and pointers can never be known for sure,
291-
// except for alignment, e.g. `1 as *const _` can never be equal to an even offset
292-
// in an `align(2)` allocation.
283+
// A pointer can be known to be unequal to an integer if them being equal would imply
284+
// that the null pointer is in-bounds (including one-past-the-end) of the pointer's
285+
// allocation (as the null pointer is never in-bounds of any allocation), or is
286+
// misaligned (as `0` is a multiple of any alignment), or that the pointer's
287+
// allocation would wrap around the address space.
288+
// Concretely, if `scalar_may_be_null` indicates that `ptr.wrapping_sub(int)` cannot
289+
// be equal to `0`, then we know that `ptr != int`.
290+
// For the specific case where `int == 0`, this checks if the pointer itself
291+
// can definitely not be null, and this behavior is exposed on stable as
292+
// `pointer::is_null`.
293293
(Scalar::Int(int), Scalar::Ptr(ptr, _)) | (Scalar::Ptr(ptr, _), Scalar::Int(int)) => {
294294
let int = int.to_target_usize(*self.tcx);
295-
let (ptr_prov, ptr_offset) = ptr.prov_and_relative_offset();
296-
let allocid = ptr_prov.alloc_id();
297-
let allocinfo = self.get_alloc_info(allocid);
298-
299-
// Check if the pointer cannot be equal to the integer due to alignment.
300-
// For this purpose, an integer can be thought of as an offset into a
301-
// maximally-aligned "allocation" (the whole address space),
302-
// so the least common alignment is the alignment of the pointer's allocation.
303-
let min_align = allocinfo.align.bytes();
304-
let ptr_residue = ptr_offset.bytes() % min_align;
305-
let int_residue = int % min_align;
306-
if ptr_residue != int_residue {
307-
// The pointer and integer have a different residue modulo their common
308-
// alignment, they can never be equal.
295+
let offset_ptr = ptr.wrapping_offset(Size::from_bytes(int.wrapping_neg()), self);
296+
if !self.scalar_may_be_null(Scalar::from_pointer(offset_ptr, self))? {
297+
// `ptr.wrapping_sub(int)` is definitely not equal to `0`, so `ptr != int`
309298
0
310299
} else {
311-
// The pointer and integer have the same residue modulo their common alignment,
312-
// so the pointer could end up equal to the integer at runtime;
313-
// we can't know for sure.
300+
// `ptr.wrapping_sub(int)` could be equal to `0`, but might not be,
301+
// so we cannot know for sure if `ptr == int` or not
314302
2
315303
}
316304
}

tests/ui/consts/ptr_comparisons.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,20 @@ do_test!((&raw const A).wrapping_byte_add(1), (align_of::<T>() + 1) as *const u8
108108
do_test!(&A, 1 as *const u8, Some(false));
109109
do_test!((&raw const A).wrapping_byte_add(1), align_of::<T>() as *const u8, Some(false));
110110

111-
// This one could return `Some(false)` by noticing that it would require `A` to be at address `0`,
112-
// but we don't currently check for that.
113-
do_test!((&raw const A).wrapping_byte_add(1), 1 as *const u8, None);
111+
// If `ptr.wrapping_sub(int)` cannot be null (because it is in-bounds or one-past-the-end of
112+
// `ptr`'s allocation, or because it is misaligned from `ptr`'s allocation), then we know that
113+
// `ptr != int`, even if `ptr` itself is out-of-bounds or one-past-the-end of its allocation.
114+
do_test!((&raw const A).wrapping_byte_add(1), 1 as *const u8, Some(false));
115+
do_test!((&raw const A).wrapping_byte_add(2), 2 as *const u8, Some(false));
116+
do_test!((&raw const A).wrapping_byte_add(3), 1 as *const u8, Some(false));
117+
do_test!((&raw const ZST).wrapping_byte_add(1), 1 as *const u8, Some(false));
118+
do_test!(VTABLE_PTR_1.wrapping_byte_add(1), 1 as *const u8, Some(false));
119+
do_test!(FN_PTR.wrapping_byte_add(1), 1 as *const u8, Some(false));
120+
do_test!(&A, size_of::<T>().wrapping_neg() as *const u8, Some(false));
121+
do_test!(&LARGE_WORD_ALIGNED, size_of::<usize>().wrapping_neg() as *const u8, Some(false));
122+
// (`ptr - int != 0` due to misalignment)
123+
do_test!((&raw const A).wrapping_byte_add(2), 1 as *const u8, Some(false));
124+
do_test!((&raw const ALIGNED_ZST).wrapping_byte_add(2), 1 as *const u8, Some(false));
114125

115126
// When pointers go out-of-bounds, they *might* become null, so these comparions cannot work.
116127
do_test!((&raw const A).wrapping_add(2), 0 as *const u8, None);

0 commit comments

Comments
 (0)