Skip to main content

rustc_mir_transform/
check_alignment.rs

1use rustc_abi::Align;
2use rustc_hir::attrs::lang_items::LangItem;
3use rustc_index::IndexVec;
4use rustc_middle::mir::interpret::Scalar;
5use rustc_middle::mir::visit::PlaceContext;
6use rustc_middle::mir::*;
7use rustc_middle::ty::{Ty, TyCtxt};
8use rustc_session::Session;
9
10use crate::PassPolicy;
11use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers};
12
13pub(super) struct CheckAlignment;
14
15impl<'tcx> crate::MirPass<'tcx> for CheckAlignment {
16    fn policy(&self, sess: &Session) -> PassPolicy {
17        // When UB checks are enabled this is part of their semantics, not an optimization.
18        PassPolicy::optional_non_optimization(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        // When checking the alignment of references to field projections (`&(*ptr).a`),
26        // we need to make sure that the reference is aligned according to the field type
27        // and not to the pointer type.
28        check_pointers(
29            tcx,
30            body,
31            &excluded_pointees,
32            insert_alignment_check,
33            BorrowedFieldProjectionMode::FollowProjections,
34        );
35    }
36}
37
38/// Inserts the actual alignment check's logic. Returns a
39/// [AssertKind::MisalignedPointerDereference] on failure.
40fn insert_alignment_check<'tcx>(
41    tcx: TyCtxt<'tcx>,
42    pointer: Place<'tcx>,
43    pointee_ty: Ty<'tcx>,
44    _context: PlaceContext,
45    local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
46    stmts: &mut Vec<Statement<'tcx>>,
47    source_info: SourceInfo,
48) -> PointerCheck<'tcx> {
49    // Cast the pointer to a *const ().
50    let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit);
51    let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr);
52    let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into();
53    stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((thin_ptr, rvalue)))));
54
55    // Transmute the pointer to a usize (equivalent to `ptr.addr()`).
56    let rvalue = Rvalue::Cast(CastKind::Transmute, Operand::Copy(thin_ptr), tcx.types.usize);
57    let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
58    stmts.push(Statement::new(source_info, StatementKind::Assign(Box::new((addr, rvalue)))));
59
60    // Get the alignment of the pointee
61    let align_def_id = tcx.require_lang_item(LangItem::AlignOf, source_info.span);
62    let alignment =
63        Operand::unevaluated_constant(tcx, align_def_id, &[pointee_ty.into()], source_info.span);
64
65    // Subtract 1 from the alignment to get the alignment mask
66    let alignment_mask =
67        local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
68    let one = Operand::Constant(Box::new(ConstOperand {
69        span: source_info.span,
70        user_ty: None,
71        const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(1, &tcx)), tcx.types.usize),
72    }));
73    stmts.push(Statement::new(
74        source_info,
75        StatementKind::Assign(Box::new((
76            alignment_mask,
77            Rvalue::BinaryOp(BinOp::Sub, Box::new((alignment.clone(), one))),
78        ))),
79    ));
80
81    // If this target does not have reliable alignment, further limit the mask by anding it with
82    // the mask for the highest reliable alignment.
83    if let max_align = tcx.sess.target.max_reliable_alignment()
84        && max_align < Align::MAX
85    {
86        let max_mask = max_align.bytes() - 1;
87        let max_mask = Operand::Constant(Box::new(ConstOperand {
88            span: source_info.span,
89            user_ty: None,
90            const_: Const::Val(
91                ConstValue::Scalar(Scalar::from_target_usize(max_mask, &tcx)),
92                tcx.types.usize,
93            ),
94        }));
95        stmts.push(Statement::new(
96            source_info,
97            StatementKind::Assign(Box::new((
98                alignment_mask,
99                Rvalue::BinaryOp(
100                    BinOp::BitAnd,
101                    Box::new((Operand::Copy(alignment_mask), max_mask)),
102                ),
103            ))),
104        ));
105    }
106
107    // BitAnd the alignment mask with the pointer
108    let alignment_bits =
109        local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into();
110    stmts.push(Statement::new(
111        source_info,
112        StatementKind::Assign(Box::new((
113            alignment_bits,
114            Rvalue::BinaryOp(
115                BinOp::BitAnd,
116                Box::new((Operand::Copy(addr), Operand::Copy(alignment_mask))),
117            ),
118        ))),
119    ));
120
121    // Check if the alignment bits are all zero
122    let is_ok = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
123    let zero = Operand::Constant(Box::new(ConstOperand {
124        span: source_info.span,
125        user_ty: None,
126        const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(0, &tcx)), tcx.types.usize),
127    }));
128    stmts.push(Statement::new(
129        source_info,
130        StatementKind::Assign(Box::new((
131            is_ok,
132            Rvalue::BinaryOp(BinOp::Eq, Box::new((Operand::Copy(alignment_bits), zero.clone()))),
133        ))),
134    ));
135
136    // Emit a check that asserts on the alignment and otherwise triggers a
137    // AssertKind::MisalignedPointerDereference.
138    PointerCheck {
139        cond: Operand::Copy(is_ok),
140        assert_kind: Box::new(AssertKind::MisalignedPointerDereference {
141            required: alignment,
142            found: Operand::Copy(addr),
143        }),
144    }
145}