rustc_mir_transform/
check_alignment.rs

1use rustc_index::IndexVec;
2use rustc_middle::mir::interpret::Scalar;
3use rustc_middle::mir::visit::PlaceContext;
4use rustc_middle::mir::*;
5use rustc_middle::ty::{Ty, TyCtxt};
6use rustc_session::Session;
7
8use crate::check_pointers::{BorrowCheckMode, PointerCheck, check_pointers};
9
10pub(super) struct CheckAlignment;
11
12impl<'tcx> crate::MirPass<'tcx> for CheckAlignment {
13    fn is_enabled(&self, sess: &Session) -> bool {
14        // FIXME(#112480) MSVC and rustc disagree on minimum stack alignment on x86 Windows
15        if sess.target.llvm_target == "i686-pc-windows-msvc" {
16            return false;
17        }
18        sess.ub_checks()
19    }
20
21    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
22        // Skip trivially aligned place types.
23        let excluded_pointees = [tcx.types.bool, tcx.types.i8, tcx.types.u8];
24
25        // We have to exclude borrows here: in `&x.field`, the exact
26        // requirement is that the final reference must be aligned, but
27        // `check_pointers` would check that `x` is aligned, which would be wrong.
28        check_pointers(
29            tcx,
30            body,
31            &excluded_pointees,
32            insert_alignment_check,
33            BorrowCheckMode::ExcludeBorrows,
34        );
35    }
36
37    fn is_required(&self) -> bool {
38        true
39    }
40}
41
42/// Inserts the actual alignment check's logic. Returns a
43/// [AssertKind::MisalignedPointerDereference] on failure.
44fn insert_alignment_check<'tcx>(
45    tcx: TyCtxt<'tcx>,
46    pointer: Place<'tcx>,
47    pointee_ty: Ty<'tcx>,
48    _context: PlaceContext,
49    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
50    stmts: &mut Vec<Statement<'tcx>>,
51    source_info: SourceInfo,
52) -> PointerCheck<'tcx> {
53    // Cast the pointer to a *const ().
54    let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit);
55    let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr);
56    let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into();
57    stmts
58        .push(Statement { source_info, kind: StatementKind::Assign(Box::new((thin_ptr, rvalue))) });
59
60    // Transmute the pointer to a usize (equivalent to `ptr.addr()`).
61    let rvalue = Rvalue::Cast(CastKind::Transmute, Operand::Copy(thin_ptr), tcx.types.usize);
62    let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
63    stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new((addr, rvalue))) });
64
65    // Get the alignment of the pointee
66    let alignment =
67        local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
68    let rvalue = Rvalue::NullaryOp(NullOp::AlignOf, pointee_ty);
69    stmts.push(Statement {
70        source_info,
71        kind: StatementKind::Assign(Box::new((alignment, rvalue))),
72    });
73
74    // Subtract 1 from the alignment to get the alignment mask
75    let alignment_mask =
76        local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
77    let one = Operand::Constant(Box::new(ConstOperand {
78        span: source_info.span,
79        user_ty: None,
80        const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(1, &tcx)), tcx.types.usize),
81    }));
82    stmts.push(Statement {
83        source_info,
84        kind: StatementKind::Assign(Box::new((
85            alignment_mask,
86            Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(alignment), one))),
87        ))),
88    });
89
90    // BitAnd the alignment mask with the pointer
91    let alignment_bits =
92        local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
93    stmts.push(Statement {
94        source_info,
95        kind: StatementKind::Assign(Box::new((
96            alignment_bits,
97            Rvalue::BinaryOp(
98                BinOp::BitAnd,
99                Box::new((Operand::Copy(addr), Operand::Copy(alignment_mask))),
100            ),
101        ))),
102    });
103
104    // Check if the alignment bits are all zero
105    let is_ok = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
106    let zero = Operand::Constant(Box::new(ConstOperand {
107        span: source_info.span,
108        user_ty: None,
109        const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(0, &tcx)), tcx.types.usize),
110    }));
111    stmts.push(Statement {
112        source_info,
113        kind: StatementKind::Assign(Box::new((
114            is_ok,
115            Rvalue::BinaryOp(BinOp::Eq, Box::new((Operand::Copy(alignment_bits), zero.clone()))),
116        ))),
117    });
118
119    // Emit a check that asserts on the alignment and otherwise triggers a
120    // AssertKind::MisalignedPointerDereference.
121    PointerCheck {
122        cond: Operand::Copy(is_ok),
123        assert_kind: Box::new(AssertKind::MisalignedPointerDereference {
124            required: Operand::Copy(alignment),
125            found: Operand::Copy(addr),
126        }),
127    }
128}