rustc_mir_transform/
check_alignment.rs

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