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