| use rustc_hir::LangItem; | |
| use rustc_index::IndexVec; | |
| use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext}; | |
| use rustc_middle::mir::*; | |
| use rustc_middle::ty::{Ty, TyCtxt}; | |
| use rustc_session::Session; | |
| use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers}; | |
| pub(super) struct CheckNull; | |
| impl<'tcx> crate::MirPass<'tcx> for CheckNull { | |
| fn is_enabled(&self, sess: &Session) -> bool { | |
| sess.ub_checks() | |
| } | |
| fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
| check_pointers( | |
| tcx, | |
| body, | |
| &[], | |
| insert_null_check, | |
| BorrowedFieldProjectionMode::NoFollowProjections, | |
| ); | |
| } | |
| fn is_required(&self) -> bool { | |
| true | |
| } | |
| } | |
| fn insert_null_check<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| pointer: Place<'tcx>, | |
| pointee_ty: Ty<'tcx>, | |
| context: PlaceContext, | |
| local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>, | |
| stmts: &mut Vec<Statement<'tcx>>, | |
| source_info: SourceInfo, | |
| ) -> PointerCheck<'tcx> { | |
| // Cast the pointer to a *const (). | |
| let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit); | |
| let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr); | |
| let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into(); | |
| stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((thin_ptr, rvalue))))); | |
| // Transmute the pointer to a usize (equivalent to `ptr.addr()`). | |
| let rvalue = Rvalue::Cast(CastKind::Transmute, Operand::Copy(thin_ptr), tcx.types.usize); | |
| let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into(); | |
| stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((addr, rvalue))))); | |
| let zero = Operand::Constant(Box::new(ConstOperand { | |
| span: source_info.span, | |
| user_ty: None, | |
| const_: Const::Val(ConstValue::from_target_usize(0, &tcx), tcx.types.usize), | |
| })); | |
| let pointee_should_be_checked = match context { | |
| // Borrows pointing to "null" are UB even if the pointee is a ZST. | |
| PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) | |
| | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => { | |
| // Pointer should be checked unconditionally. | |
| Operand::Constant(Box::new(ConstOperand { | |
| span: source_info.span, | |
| user_ty: None, | |
| const_: Const::from_bool(tcx, true), | |
| })) | |
| } | |
| // Other usages of null pointers only are UB if the pointee is not a ZST. | |
| _ => { | |
| let size_of = tcx.require_lang_item(LangItem::SizeOf, source_info.span); | |
| let size_of = | |
| Operand::unevaluated_constant(tcx, size_of, &[pointee_ty.into()], source_info.span); | |
| let pointee_should_be_checked = | |
| local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); | |
| let rvalue = Rvalue::BinaryOp(BinOp::Ne, Box::new((size_of, zero.clone()))); | |
| stmts.push(Statement::new( | |
| source_info, | |
| StatementKind::Assign(Box::new((pointee_should_be_checked, rvalue))), | |
| )); | |
| Operand::Copy(pointee_should_be_checked.into()) | |
| } | |
| }; | |
| // Check whether the pointer is null. | |
| let is_null = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); | |
| stmts.push(Statement::new( | |
| source_info, | |
| StatementKind::Assign(Box::new(( | |
| is_null, | |
| Rvalue::BinaryOp(BinOp::Eq, Box::new((Operand::Copy(addr), zero))), | |
| ))), | |
| )); | |
| // 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 = | |
| local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); | |
| stmts.push(Statement::new( | |
| source_info, | |
| StatementKind::Assign(Box::new(( | |
| should_throw_exception, | |
| Rvalue::BinaryOp( | |
| BinOp::BitAnd, | |
| Box::new((Operand::Copy(is_null), pointee_should_be_checked)), | |
| ), | |
| ))), | |
| )); | |
| // The final condition whether this pointer usage is ok or not. | |
| let is_ok = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); | |
| stmts.push(Statement::new( | |
| source_info, | |
| StatementKind::Assign(Box::new(( | |
| is_ok, | |
| Rvalue::UnaryOp(UnOp::Not, Operand::Copy(should_throw_exception)), | |
| ))), | |
| )); | |
| // Emit a PointerCheck that asserts on the condition and otherwise triggers | |
| // a AssertKind::NullPointerDereference. | |
| PointerCheck { | |
| cond: Operand::Copy(is_ok), | |
| assert_kind: Box::new(AssertKind::NullPointerDereference), | |
| } | |
| } |