rustc_mir_transform/
check_null.rs

1use rustc_index::IndexVec;
2use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext};
3use rustc_middle::mir::*;
4use rustc_middle::ty::{Ty, TyCtxt};
5use rustc_session::Session;
6
7use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers};
8
9pub(super) struct CheckNull;
10
11impl<'tcx> crate::MirPass<'tcx> for CheckNull {
12    fn is_enabled(&self, sess: &Session) -> bool {
13        sess.ub_checks()
14    }
15
16    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
17        check_pointers(
18            tcx,
19            body,
20            &[],
21            insert_null_check,
22            BorrowedFieldProjectionMode::NoFollowProjections,
23        );
24    }
25
26    fn is_required(&self) -> bool {
27        true
28    }
29}
30
31fn insert_null_check<'tcx>(
32    tcx: TyCtxt<'tcx>,
33    pointer: Place<'tcx>,
34    pointee_ty: Ty<'tcx>,
35    context: PlaceContext,
36    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
37    stmts: &mut Vec<Statement<'tcx>>,
38    source_info: SourceInfo,
39) -> PointerCheck<'tcx> {
40    // Cast the pointer to a *const ().
41    let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit);
42    let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr);
43    let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into();
44    stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((thin_ptr, rvalue)))));
45
46    // Transmute the pointer to a usize (equivalent to `ptr.addr()`).
47    let rvalue = Rvalue::Cast(CastKind::Transmute, Operand::Copy(thin_ptr), tcx.types.usize);
48    let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
49    stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((addr, rvalue)))));
50
51    let zero = Operand::Constant(Box::new(ConstOperand {
52        span: source_info.span,
53        user_ty: None,
54        const_: Const::Val(ConstValue::from_target_usize(0, &tcx), tcx.types.usize),
55    }));
56
57    let pointee_should_be_checked = match context {
58        // Borrows pointing to "null" are UB even if the pointee is a ZST.
59        PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
60        | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
61            // Pointer should be checked unconditionally.
62            Operand::Constant(Box::new(ConstOperand {
63                span: source_info.span,
64                user_ty: None,
65                const_: Const::Val(ConstValue::from_bool(true), tcx.types.bool),
66            }))
67        }
68        // Other usages of null pointers only are UB if the pointee is not a ZST.
69        _ => {
70            let rvalue = Rvalue::NullaryOp(NullOp::SizeOf, pointee_ty);
71            let sizeof_pointee =
72                local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
73            stmts.push(Statement::new(
74                source_info,
75                StatementKind::Assign(Box::new((sizeof_pointee, rvalue))),
76            ));
77
78            // Check that the pointee is not a ZST.
79            let is_pointee_not_zst =
80                local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
81            stmts.push(Statement::new(
82                source_info,
83                StatementKind::Assign(Box::new((
84                    is_pointee_not_zst,
85                    Rvalue::BinaryOp(
86                        BinOp::Ne,
87                        Box::new((Operand::Copy(sizeof_pointee), zero.clone())),
88                    ),
89                ))),
90            ));
91
92            // Pointer needs to be checked only if pointee is not a ZST.
93            Operand::Copy(is_pointee_not_zst)
94        }
95    };
96
97    // Check whether the pointer is null.
98    let is_null = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
99    stmts.push(Statement::new(
100        source_info,
101        StatementKind::Assign(Box::new((
102            is_null,
103            Rvalue::BinaryOp(BinOp::Eq, Box::new((Operand::Copy(addr), zero))),
104        ))),
105    ));
106
107    // We want to throw an exception if the pointer is null and the pointee is not unconditionally
108    // allowed (which for all non-borrow place uses, is when the pointee is ZST).
109    let should_throw_exception =
110        local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
111    stmts.push(Statement::new(
112        source_info,
113        StatementKind::Assign(Box::new((
114            should_throw_exception,
115            Rvalue::BinaryOp(
116                BinOp::BitAnd,
117                Box::new((Operand::Copy(is_null), pointee_should_be_checked)),
118            ),
119        ))),
120    ));
121
122    // The final condition whether this pointer usage is ok or not.
123    let is_ok = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
124    stmts.push(Statement::new(
125        source_info,
126        StatementKind::Assign(Box::new((
127            is_ok,
128            Rvalue::UnaryOp(UnOp::Not, Operand::Copy(should_throw_exception)),
129        ))),
130    ));
131
132    // Emit a PointerCheck that asserts on the condition and otherwise triggers
133    // a AssertKind::NullPointerDereference.
134    PointerCheck {
135        cond: Operand::Copy(is_ok),
136        assert_kind: Box::new(AssertKind::NullPointerDereference),
137    }
138}