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