Skip to content

Commit cf3bb09

Browse files
authored
Rollup merge of #94343 - RalfJung:fn-ptr, r=oli-obk
Miri fn ptr check: don't use conservative null check In #94270 I used the wrong NULL check for function pointers: `memory.ptr_may_be_null` is conservative even on machines that support ptr-to-int casts, leading to false errors in Miri. This fixes that problem, and also replaces that foot-fun of a method with `scalar_may_be_null` which is never unnecessarily conservative. r? `@oli-obk`
2 parents f9f97b6 + d8064d7 commit cf3bb09

File tree

7 files changed

+43
-32
lines changed

7 files changed

+43
-32
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,9 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
217217
// Comparisons of abstract pointers with null pointers are known if the pointer
218218
// is in bounds, because if they are in bounds, the pointer can't be null.
219219
// Inequality with integers other than null can never be known for sure.
220-
(Scalar::Int(int), Scalar::Ptr(ptr, _)) | (Scalar::Ptr(ptr, _), Scalar::Int(int)) => {
221-
int.is_null() && !self.memory.ptr_may_be_null(ptr.into())
220+
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
221+
| (ptr @ Scalar::Ptr(..), Scalar::Int(int)) => {
222+
int.is_null() && !self.scalar_may_be_null(ptr)
222223
}
223224
// FIXME: return `true` for at least some comparisons where we can reliably
224225
// determine the result of runtime inequality tests at compile-time.

compiler/rustc_const_eval/src/interpret/eval_context.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ use rustc_span::{Pos, Span};
2222
use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayout};
2323

2424
use super::{
25-
AllocId, GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace,
26-
MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, Pointer, Provenance, Scalar,
27-
ScalarMaybeUninit, StackPopJump,
25+
AllocCheck, AllocId, GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine,
26+
MemPlace, MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, Pointer, Provenance,
27+
Scalar, ScalarMaybeUninit, StackPopJump,
2828
};
2929
use crate::transform::validate::equal_up_to_regions;
3030

@@ -440,6 +440,29 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
440440
self.memory.scalar_to_ptr(scalar)
441441
}
442442

443+
/// Test if this value might be null.
444+
/// If the machine does not support ptr-to-int casts, this is conservative.
445+
pub fn scalar_may_be_null(&self, scalar: Scalar<M::PointerTag>) -> bool {
446+
match scalar.try_to_int() {
447+
Ok(int) => int.is_null(),
448+
Err(_) => {
449+
let ptr = self.scalar_to_ptr(scalar);
450+
match self.memory.ptr_try_get_alloc(ptr) {
451+
Ok((alloc_id, offset, _)) => {
452+
let (size, _align) = self
453+
.memory
454+
.get_size_and_align(alloc_id, AllocCheck::MaybeDead)
455+
.expect("alloc info with MaybeDead cannot fail");
456+
// If the pointer is out-of-bounds, it may be null.
457+
// Note that one-past-the-end (offset == size) is still inbounds, and never null.
458+
offset > size
459+
}
460+
Err(offset) => offset == 0,
461+
}
462+
}
463+
}
464+
}
465+
443466
/// Call this to turn untagged "global" pointers (obtained via `tcx`) into
444467
/// the machine pointer to the allocation. Must never be used
445468
/// for any other pointers, nor for TLS statics.

compiler/rustc_const_eval/src/interpret/memory.rs

-15
Original file line numberDiff line numberDiff line change
@@ -483,21 +483,6 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
483483
}
484484
})
485485
}
486-
487-
/// Test if the pointer might be null.
488-
pub fn ptr_may_be_null(&self, ptr: Pointer<Option<M::PointerTag>>) -> bool {
489-
match self.ptr_try_get_alloc(ptr) {
490-
Ok((alloc_id, offset, _)) => {
491-
let (size, _align) = self
492-
.get_size_and_align(alloc_id, AllocCheck::MaybeDead)
493-
.expect("alloc info with MaybeDead cannot fail");
494-
// If the pointer is out-of-bounds, it may be null.
495-
// Note that one-past-the-end (offset == size) is still inbounds, and never null.
496-
offset > size
497-
}
498-
Err(offset) => offset == 0,
499-
}
500-
}
501486
}
502487

503488
/// Allocation accessors

compiler/rustc_const_eval/src/interpret/operand.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -720,12 +720,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
720720
Err(dbg_val) => {
721721
// So this is a pointer then, and casting to an int failed.
722722
// Can only happen during CTFE.
723-
let ptr = self.scalar_to_ptr(tag_val);
724723
// The niche must be just 0, and the ptr not null, then we know this is
725724
// okay. Everything else, we conservatively reject.
726725
let ptr_valid = niche_start == 0
727726
&& variants_start == variants_end
728-
&& !self.memory.ptr_may_be_null(ptr);
727+
&& !self.scalar_may_be_null(tag_val);
729728
if !ptr_valid {
730729
throw_ub!(InvalidTag(dbg_val))
731730
}

compiler/rustc_const_eval/src/interpret/validity.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -572,21 +572,25 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
572572
err_unsup!(ReadPointerAsBytes) => { "part of a pointer" } expected { "a proper pointer or integer value" },
573573
err_ub!(InvalidUninitBytes(None)) => { "uninitialized bytes" } expected { "a proper pointer or integer value" },
574574
);
575-
let ptr = self.ecx.scalar_to_ptr(value);
576-
// Ensure the pointer is non-null.
577-
if self.ecx.memory.ptr_may_be_null(ptr) {
578-
throw_validation_failure!(self.path, { "a potentially null function pointer" });
579-
}
575+
580576
// If we check references recursively, also check that this points to a function.
581577
if let Some(_) = self.ref_tracking {
578+
let ptr = self.ecx.scalar_to_ptr(value);
582579
let _fn = try_validation!(
583580
self.ecx.memory.get_fn(ptr),
584581
self.path,
582+
err_ub!(DanglingIntPointer(0, _)) =>
583+
{ "a null function pointer" },
585584
err_ub!(DanglingIntPointer(..)) |
586585
err_ub!(InvalidFunctionPointer(..)) =>
587586
{ "{:x}", value } expected { "a function pointer" },
588587
);
589588
// FIXME: Check if the signature matches
589+
} else {
590+
// Otherwise (for standalone Miri), we have to still check it to be non-null.
591+
if self.ecx.scalar_may_be_null(value) {
592+
throw_validation_failure!(self.path, { "a null function pointer" });
593+
}
590594
}
591595
Ok(true)
592596
}
@@ -644,10 +648,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
644648
Err(_) => {
645649
// So this is a pointer then, and casting to an int failed.
646650
// Can only happen during CTFE.
647-
let ptr = self.ecx.scalar_to_ptr(value);
648651
if start == 1 && end == max_value {
649652
// Only null is the niche. So make sure the ptr is NOT null.
650-
if self.ecx.memory.ptr_may_be_null(ptr) {
653+
if self.ecx.scalar_may_be_null(value) {
651654
throw_validation_failure!(self.path,
652655
{ "a potentially null pointer" }
653656
expected {
@@ -758,7 +761,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
758761
fn visit_value(&mut self, op: &OpTy<'tcx, M::PointerTag>) -> InterpResult<'tcx> {
759762
trace!("visit_value: {:?}, {:?}", *op, op.layout);
760763

761-
// Check primitive types -- the leafs of our recursive descend.
764+
// Check primitive types -- the leaves of our recursive descent.
762765
if self.try_visit_primitive(op)? {
763766
return Ok(());
764767
}

src/test/ui/consts/const-eval/ub-ref-ptr.32bit.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ error[E0080]: it is undefined behavior to use this value
112112
--> $DIR/ub-ref-ptr.rs:49:1
113113
|
114114
LL | const NULL_FN_PTR: fn() = unsafe { mem::transmute(0usize) };
115-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a potentially null function pointer
115+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a null function pointer
116116
|
117117
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
118118
= note: the raw bytes of the constant (size: 4, align: 4) {

src/test/ui/consts/const-eval/ub-ref-ptr.64bit.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ error[E0080]: it is undefined behavior to use this value
112112
--> $DIR/ub-ref-ptr.rs:49:1
113113
|
114114
LL | const NULL_FN_PTR: fn() = unsafe { mem::transmute(0usize) };
115-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a potentially null function pointer
115+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a null function pointer
116116
|
117117
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
118118
= note: the raw bytes of the constant (size: 8, align: 8) {

0 commit comments

Comments
 (0)