diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 0790db984e345..2772c94d52b0b 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -1481,22 +1481,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Test if this value might be null. /// If the machine does not support ptr-to-int casts, this is conservative. pub fn scalar_may_be_null(&self, scalar: Scalar) -> InterpResult<'tcx, bool> { - interp_ok(match scalar.try_to_scalar_int() { - Ok(int) => int.is_null(), + match scalar.try_to_scalar_int() { + Ok(int) => interp_ok(int.is_null()), Err(_) => { - // Can only happen during CTFE. + // We can't cast this pointer to an integer. Can only happen during CTFE. let ptr = scalar.to_pointer(self)?; match self.ptr_try_get_alloc_id(ptr, 0) { Ok((alloc_id, offset, _)) => { - let size = self.get_alloc_info(alloc_id).size; - // If the pointer is out-of-bounds, it may be null. - // Note that one-past-the-end (offset == size) is still inbounds, and never null. - offset > size + let info = self.get_alloc_info(alloc_id); + // If the pointer is in-bounds (including "at the end"), it is definitely not null. + if offset <= info.size { + return interp_ok(false); + } + // If the allocation is N-aligned, and the offset is not divisible by N, + // then `base + offset` has a non-zero remainder after division by `N`, + // which means `base + offset` cannot be null. + if offset.bytes() % info.align.bytes() != 0 { + return interp_ok(false); + } + // We don't know enough, this might be null. + interp_ok(true) } Err(_offset) => bug!("a non-int scalar is always a pointer"), } } - }) + } } /// Turning a "maybe pointer" into a proper pointer (and some information diff --git a/tests/ui/consts/const-ptr-is-null.rs b/tests/ui/consts/const-ptr-is-null.rs index 92cf87a9782f2..0abd9afa42246 100644 --- a/tests/ui/consts/const-ptr-is-null.rs +++ b/tests/ui/consts/const-ptr-is-null.rs @@ -12,7 +12,13 @@ const MAYBE_NULL: () = { let ptr = &x as *const i32; // This one is still unambiguous... assert!(!ptr.is_null()); - // but once we shift outside the allocation, we might become null. + // and in fact, any offset not visible by 4 (the alignment) cannot be null, + // even if it goes out-of-bounds... + assert!(!ptr.wrapping_byte_add(13).is_null()); + assert!(!ptr.wrapping_byte_add(18).is_null()); + assert!(!ptr.wrapping_byte_sub(1).is_null()); + // ... but once we shift outside the allocation, with an offset divisible by 4, + // we might become null. assert!(!ptr.wrapping_sub(512).is_null()); //~inside `MAYBE_NULL` }; diff --git a/tests/ui/consts/const-ptr-is-null.stderr b/tests/ui/consts/const-ptr-is-null.stderr index f71b37527726f..5ef79790d93bc 100644 --- a/tests/ui/consts/const-ptr-is-null.stderr +++ b/tests/ui/consts/const-ptr-is-null.stderr @@ -8,7 +8,7 @@ note: inside `std::ptr::const_ptr::::is_null::compiletime` note: inside `std::ptr::const_ptr::::is_null` --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL note: inside `MAYBE_NULL` - --> $DIR/const-ptr-is-null.rs:16:14 + --> $DIR/const-ptr-is-null.rs:22:14 | LL | assert!(!ptr.wrapping_sub(512).is_null()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^