Rollup merge of #136601 - compiler-errors:borrow-null-zst, r=saethlin
Detect (non-raw) borrows of null ZST pointers in CheckNull Fixes #136568. Ensures that we check that borrows of derefs are non-null in the `CheckNull` pass **even if** it's a ZST pointee. I'm actually surprised that this is UB in Miri, but if it's certainly UB, then this PR modifies the null check to be stricter. I couldn't find anywhere in https://doc.rust-lang.org/reference/behavior-considered-undefined.html that discusses this case specifically, but I didn't read it too closely, or perhaps it's just missing a bullet point. On the contrary, if this is actually erroneous UB in Miri, then I'm happy to close this (and perhaps fix the null check in Miri to exclude ZSTs?) On the double contrary, if this is still an "open question", I'm also happy to close this and wait for a decision to be made. r? ``@saethlin`` cc ``@RalfJung`` (perhaps you feel strongly about this change)
This commit is contained in:
commit
e5bc12e4a3
15 changed files with 85 additions and 38 deletions
|
@ -272,7 +272,7 @@ impl<O> AssertKind<O> {
|
||||||
"\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}"
|
"\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NullPointerDereference => write!(f, "\"null pointer dereference occured\""),
|
NullPointerDereference => write!(f, "\"null pointer dereference occurred\""),
|
||||||
ResumedAfterReturn(CoroutineKind::Coroutine(_)) => {
|
ResumedAfterReturn(CoroutineKind::Coroutine(_)) => {
|
||||||
write!(f, "\"coroutine resumed after completion\"")
|
write!(f, "\"coroutine resumed after completion\"")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use rustc_index::IndexVec;
|
use rustc_index::IndexVec;
|
||||||
use rustc_middle::mir::interpret::Scalar;
|
use rustc_middle::mir::interpret::Scalar;
|
||||||
|
use rustc_middle::mir::visit::PlaceContext;
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::ty::{Ty, TyCtxt};
|
use rustc_middle::ty::{Ty, TyCtxt};
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
|
@ -44,6 +45,7 @@ fn insert_alignment_check<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
pointer: Place<'tcx>,
|
pointer: Place<'tcx>,
|
||||||
pointee_ty: Ty<'tcx>,
|
pointee_ty: Ty<'tcx>,
|
||||||
|
_context: PlaceContext,
|
||||||
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
|
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
|
||||||
stmts: &mut Vec<Statement<'tcx>>,
|
stmts: &mut Vec<Statement<'tcx>>,
|
||||||
source_info: SourceInfo,
|
source_info: SourceInfo,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use rustc_index::IndexVec;
|
use rustc_index::IndexVec;
|
||||||
use rustc_middle::mir::interpret::Scalar;
|
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext};
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::ty::{Ty, TyCtxt};
|
use rustc_middle::ty::{Ty, TyCtxt};
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
|
@ -26,6 +26,7 @@ fn insert_null_check<'tcx>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
pointer: Place<'tcx>,
|
pointer: Place<'tcx>,
|
||||||
pointee_ty: Ty<'tcx>,
|
pointee_ty: Ty<'tcx>,
|
||||||
|
context: PlaceContext,
|
||||||
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
|
local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
|
||||||
stmts: &mut Vec<Statement<'tcx>>,
|
stmts: &mut Vec<Statement<'tcx>>,
|
||||||
source_info: SourceInfo,
|
source_info: SourceInfo,
|
||||||
|
@ -42,30 +43,51 @@ fn insert_null_check<'tcx>(
|
||||||
let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
|
let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
|
||||||
stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new((addr, rvalue))) });
|
stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new((addr, rvalue))) });
|
||||||
|
|
||||||
// Get size of the pointee (zero-sized reads and writes are allowed).
|
|
||||||
let rvalue = Rvalue::NullaryOp(NullOp::SizeOf, pointee_ty);
|
|
||||||
let sizeof_pointee =
|
|
||||||
local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
|
|
||||||
stmts.push(Statement {
|
|
||||||
source_info,
|
|
||||||
kind: StatementKind::Assign(Box::new((sizeof_pointee, rvalue))),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check that the pointee is not a ZST.
|
|
||||||
let zero = Operand::Constant(Box::new(ConstOperand {
|
let zero = Operand::Constant(Box::new(ConstOperand {
|
||||||
span: source_info.span,
|
span: source_info.span,
|
||||||
user_ty: None,
|
user_ty: None,
|
||||||
const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(0, &tcx)), tcx.types.usize),
|
const_: Const::Val(ConstValue::from_target_usize(0, &tcx), tcx.types.usize),
|
||||||
}));
|
}));
|
||||||
let is_pointee_no_zst =
|
|
||||||
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
let pointee_should_be_checked = match context {
|
||||||
stmts.push(Statement {
|
// Borrows pointing to "null" are UB even if the pointee is a ZST.
|
||||||
source_info,
|
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
|
||||||
kind: StatementKind::Assign(Box::new((
|
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
|
||||||
is_pointee_no_zst,
|
// Pointer should be checked unconditionally.
|
||||||
Rvalue::BinaryOp(BinOp::Ne, Box::new((Operand::Copy(sizeof_pointee), zero.clone()))),
|
Operand::Constant(Box::new(ConstOperand {
|
||||||
))),
|
span: source_info.span,
|
||||||
});
|
user_ty: None,
|
||||||
|
const_: Const::Val(ConstValue::from_bool(true), tcx.types.bool),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
// Other usages of null pointers only are UB if the pointee is not a ZST.
|
||||||
|
_ => {
|
||||||
|
let rvalue = Rvalue::NullaryOp(NullOp::SizeOf, pointee_ty);
|
||||||
|
let sizeof_pointee =
|
||||||
|
local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
|
||||||
|
stmts.push(Statement {
|
||||||
|
source_info,
|
||||||
|
kind: StatementKind::Assign(Box::new((sizeof_pointee, rvalue))),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that the pointee is not a ZST.
|
||||||
|
let is_pointee_not_zst =
|
||||||
|
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
||||||
|
stmts.push(Statement {
|
||||||
|
source_info,
|
||||||
|
kind: StatementKind::Assign(Box::new((
|
||||||
|
is_pointee_not_zst,
|
||||||
|
Rvalue::BinaryOp(
|
||||||
|
BinOp::Ne,
|
||||||
|
Box::new((Operand::Copy(sizeof_pointee), zero.clone())),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pointer needs to be checked only if pointee is not a ZST.
|
||||||
|
Operand::Copy(is_pointee_not_zst)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Check whether the pointer is null.
|
// Check whether the pointer is null.
|
||||||
let is_null = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
let is_null = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
||||||
|
@ -77,7 +99,8 @@ fn insert_null_check<'tcx>(
|
||||||
))),
|
))),
|
||||||
});
|
});
|
||||||
|
|
||||||
// We want to throw an exception if the pointer is null and doesn't point to a ZST.
|
// We want to throw an exception if the pointer is null and the pointee is not unconditionally
|
||||||
|
// allowed (which for all non-borrow place uses, is when the pointee is ZST).
|
||||||
let should_throw_exception =
|
let should_throw_exception =
|
||||||
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
|
||||||
stmts.push(Statement {
|
stmts.push(Statement {
|
||||||
|
@ -86,7 +109,7 @@ fn insert_null_check<'tcx>(
|
||||||
should_throw_exception,
|
should_throw_exception,
|
||||||
Rvalue::BinaryOp(
|
Rvalue::BinaryOp(
|
||||||
BinOp::BitAnd,
|
BinOp::BitAnd,
|
||||||
Box::new((Operand::Copy(is_null), Operand::Copy(is_pointee_no_zst))),
|
Box::new((Operand::Copy(is_null), pointee_should_be_checked)),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,10 +40,10 @@ pub(crate) enum BorrowCheckMode {
|
||||||
/// success and fail the check otherwise.
|
/// success and fail the check otherwise.
|
||||||
/// This utility will insert a terminator block that asserts on the condition
|
/// This utility will insert a terminator block that asserts on the condition
|
||||||
/// and panics on failure.
|
/// and panics on failure.
|
||||||
pub(crate) fn check_pointers<'a, 'tcx, F>(
|
pub(crate) fn check_pointers<'tcx, F>(
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
body: &mut Body<'tcx>,
|
body: &mut Body<'tcx>,
|
||||||
excluded_pointees: &'a [Ty<'tcx>],
|
excluded_pointees: &[Ty<'tcx>],
|
||||||
on_finding: F,
|
on_finding: F,
|
||||||
borrow_check_mode: BorrowCheckMode,
|
borrow_check_mode: BorrowCheckMode,
|
||||||
) where
|
) where
|
||||||
|
@ -51,6 +51,7 @@ pub(crate) fn check_pointers<'a, 'tcx, F>(
|
||||||
/* tcx: */ TyCtxt<'tcx>,
|
/* tcx: */ TyCtxt<'tcx>,
|
||||||
/* pointer: */ Place<'tcx>,
|
/* pointer: */ Place<'tcx>,
|
||||||
/* pointee_ty: */ Ty<'tcx>,
|
/* pointee_ty: */ Ty<'tcx>,
|
||||||
|
/* context: */ PlaceContext,
|
||||||
/* local_decls: */ &mut IndexVec<Local, LocalDecl<'tcx>>,
|
/* local_decls: */ &mut IndexVec<Local, LocalDecl<'tcx>>,
|
||||||
/* stmts: */ &mut Vec<Statement<'tcx>>,
|
/* stmts: */ &mut Vec<Statement<'tcx>>,
|
||||||
/* source_info: */ SourceInfo,
|
/* source_info: */ SourceInfo,
|
||||||
|
@ -86,7 +87,7 @@ pub(crate) fn check_pointers<'a, 'tcx, F>(
|
||||||
);
|
);
|
||||||
finder.visit_statement(statement, location);
|
finder.visit_statement(statement, location);
|
||||||
|
|
||||||
for (local, ty) in finder.into_found_pointers() {
|
for (local, ty, context) in finder.into_found_pointers() {
|
||||||
debug!("Inserting check for {:?}", ty);
|
debug!("Inserting check for {:?}", ty);
|
||||||
let new_block = split_block(basic_blocks, location);
|
let new_block = split_block(basic_blocks, location);
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ pub(crate) fn check_pointers<'a, 'tcx, F>(
|
||||||
tcx,
|
tcx,
|
||||||
local,
|
local,
|
||||||
ty,
|
ty,
|
||||||
|
context,
|
||||||
local_decls,
|
local_decls,
|
||||||
&mut block_data.statements,
|
&mut block_data.statements,
|
||||||
source_info,
|
source_info,
|
||||||
|
@ -125,7 +127,7 @@ struct PointerFinder<'a, 'tcx> {
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
local_decls: &'a mut LocalDecls<'tcx>,
|
local_decls: &'a mut LocalDecls<'tcx>,
|
||||||
typing_env: ty::TypingEnv<'tcx>,
|
typing_env: ty::TypingEnv<'tcx>,
|
||||||
pointers: Vec<(Place<'tcx>, Ty<'tcx>)>,
|
pointers: Vec<(Place<'tcx>, Ty<'tcx>, PlaceContext)>,
|
||||||
excluded_pointees: &'a [Ty<'tcx>],
|
excluded_pointees: &'a [Ty<'tcx>],
|
||||||
borrow_check_mode: BorrowCheckMode,
|
borrow_check_mode: BorrowCheckMode,
|
||||||
}
|
}
|
||||||
|
@ -148,7 +150,7 @@ impl<'a, 'tcx> PointerFinder<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_found_pointers(self) -> Vec<(Place<'tcx>, Ty<'tcx>)> {
|
fn into_found_pointers(self) -> Vec<(Place<'tcx>, Ty<'tcx>, PlaceContext)> {
|
||||||
self.pointers
|
self.pointers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +213,7 @@ impl<'a, 'tcx> Visitor<'tcx> for PointerFinder<'a, 'tcx> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pointers.push((pointer, pointee_ty));
|
self.pointers.push((pointer, pointee_ty, context));
|
||||||
|
|
||||||
self.super_place(place, context, location);
|
self.super_place(place, context, location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,7 @@ impl AssertMessage {
|
||||||
AssertMessage::MisalignedPointerDereference { .. } => {
|
AssertMessage::MisalignedPointerDereference { .. } => {
|
||||||
Ok("misaligned pointer dereference")
|
Ok("misaligned pointer dereference")
|
||||||
}
|
}
|
||||||
AssertMessage::NullPointerDereference => Ok("null pointer dereference occured"),
|
AssertMessage::NullPointerDereference => Ok("null pointer dereference occurred"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,7 +299,7 @@ fn pretty_assert_message<W: Write>(writer: &mut W, msg: &AssertMessage) -> io::R
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AssertMessage::NullPointerDereference => {
|
AssertMessage::NullPointerDereference => {
|
||||||
write!(writer, "\"null pointer dereference occured.\"")
|
write!(writer, "\"null pointer dereference occurred\"")
|
||||||
}
|
}
|
||||||
AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) => {
|
AssertMessage::ResumedAfterReturn(_) | AssertMessage::ResumedAfterPanic(_) => {
|
||||||
write!(writer, "{}", msg.description().unwrap())
|
write!(writer, "{}", msg.description().unwrap())
|
||||||
|
|
|
@ -302,7 +302,7 @@ fn panic_null_pointer_dereference() -> ! {
|
||||||
}
|
}
|
||||||
|
|
||||||
panic_nounwind_fmt(
|
panic_nounwind_fmt(
|
||||||
format_args!("null pointer dereference occured"),
|
format_args!("null pointer dereference occurred"),
|
||||||
/* force_no_backtrace */ false,
|
/* force_no_backtrace */ false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//@ run-fail
|
//@ run-fail
|
||||||
//@ compile-flags: -C debug-assertions
|
//@ compile-flags: -C debug-assertions
|
||||||
//@ error-pattern: null pointer dereference occured
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ptr: *mut u32 = std::ptr::null_mut();
|
let ptr: *mut u32 = std::ptr::null_mut();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//@ run-fail
|
//@ run-fail
|
||||||
//@ compile-flags: -C debug-assertions
|
//@ compile-flags: -C debug-assertions
|
||||||
//@ error-pattern: null pointer dereference occured
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ptr: *const u32 = std::ptr::null();
|
let ptr: *const u32 = std::ptr::null();
|
||||||
|
|
8
tests/ui/mir/null/borrowed_null_zst.rs
Normal file
8
tests/ui/mir/null/borrowed_null_zst.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//@ run-fail
|
||||||
|
//@ compile-flags: -C debug-assertions
|
||||||
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ptr: *const () = std::ptr::null();
|
||||||
|
let _ptr: &() = unsafe { &*ptr };
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
//@ run-fail
|
//@ run-fail
|
||||||
//@ compile-flags: -C debug-assertions
|
//@ compile-flags: -C debug-assertions
|
||||||
//@ error-pattern: null pointer dereference occured
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ptr: *mut u32 = std::ptr::null_mut();
|
let ptr: *mut u32 = std::ptr::null_mut();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//@ run-fail
|
//@ run-fail
|
||||||
//@ compile-flags: -C debug-assertions
|
//@ compile-flags: -C debug-assertions
|
||||||
//@ error-pattern: null pointer dereference occured
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ptr: *mut u32 = std::ptr::null_mut();
|
let ptr: *mut u32 = std::ptr::null_mut();
|
||||||
|
|
|
@ -6,5 +6,6 @@ fn main() {
|
||||||
let ptr: *const u16 = std::ptr::null();
|
let ptr: *const u16 = std::ptr::null();
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = *ptr;
|
let _ = *ptr;
|
||||||
|
let _ = &raw const *ptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
tests/ui/mir/null/place_without_read_zst.rs
Normal file
11
tests/ui/mir/null/place_without_read_zst.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Make sure that we don't insert a check for places that do not read.
|
||||||
|
//@ run-pass
|
||||||
|
//@ compile-flags: -C debug-assertions
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ptr: *const () = std::ptr::null();
|
||||||
|
unsafe {
|
||||||
|
let _ = *ptr;
|
||||||
|
let _ = &raw const *ptr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
//@ run-fail
|
//@ run-fail
|
||||||
//@ compile-flags: -C debug-assertions
|
//@ compile-flags: -C debug-assertions
|
||||||
//@ error-pattern: null pointer dereference occured
|
//@ error-pattern: null pointer dereference occurred
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ptr = std::ptr::null();
|
let ptr = std::ptr::null();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue