use rustc_abi::Align; use rustc_index::IndexVec; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::visit::PlaceContext; use rustc_middle::mir::*; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_session::Session; use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers}; pub(super) struct CheckAlignment; impl<'tcx> crate::MirPass<'tcx> for CheckAlignment { fn is_enabled(&self, sess: &Session) -> bool { sess.ub_checks() } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // Skip trivially aligned place types. let excluded_pointees = [tcx.types.bool, tcx.types.i8, tcx.types.u8]; // When checking the alignment of references to field projections (`&(*ptr).a`), // we need to make sure that the reference is aligned according to the field type // and not to the pointer type. check_pointers( tcx, body, &excluded_pointees, insert_alignment_check, BorrowedFieldProjectionMode::FollowProjections, ); } fn is_required(&self) -> bool { true } } /// Inserts the actual alignment check's logic. Returns a /// [AssertKind::MisalignedPointerDereference] on failure. fn insert_alignment_check<'tcx>( tcx: TyCtxt<'tcx>, pointer: Place<'tcx>, pointee_ty: Ty<'tcx>, _context: PlaceContext, local_decls: &mut IndexVec>, stmts: &mut Vec>, source_info: SourceInfo, ) -> PointerCheck<'tcx> { // Cast the pointer to a *const (). let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit); let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr); let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into(); stmts .push(Statement { source_info, kind: StatementKind::Assign(Box::new((thin_ptr, rvalue))) }); // Transmute the pointer to a usize (equivalent to `ptr.addr()`). let rvalue = Rvalue::Cast(CastKind::Transmute, Operand::Copy(thin_ptr), tcx.types.usize); let addr = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into(); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new((addr, rvalue))) }); // Get the alignment of the pointee let alignment = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into(); let rvalue = Rvalue::NullaryOp(NullOp::AlignOf, pointee_ty); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new((alignment, rvalue))), }); // Subtract 1 from the alignment to get the alignment mask let alignment_mask = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into(); let one = Operand::Constant(Box::new(ConstOperand { span: source_info.span, user_ty: None, const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(1, &tcx)), tcx.types.usize), })); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new(( alignment_mask, Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(alignment), one))), ))), }); // If this target does not have reliable alignment, further limit the mask by anding it with // the mask for the highest reliable alignment. #[allow(irrefutable_let_patterns)] if let max_align = tcx.sess.target.max_reliable_alignment() && max_align < Align::MAX { let max_mask = max_align.bytes() - 1; let max_mask = Operand::Constant(Box::new(ConstOperand { span: source_info.span, user_ty: None, const_: Const::Val( ConstValue::Scalar(Scalar::from_target_usize(max_mask, &tcx)), tcx.types.usize, ), })); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new(( alignment_mask, Rvalue::BinaryOp( BinOp::BitAnd, Box::new((Operand::Copy(alignment_mask), max_mask)), ), ))), }); } // BitAnd the alignment mask with the pointer let alignment_bits = local_decls.push(LocalDecl::with_source_info(tcx.types.usize, source_info)).into(); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new(( alignment_bits, Rvalue::BinaryOp( BinOp::BitAnd, Box::new((Operand::Copy(addr), Operand::Copy(alignment_mask))), ), ))), }); // Check if the alignment bits are all zero let is_ok = local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into(); let zero = Operand::Constant(Box::new(ConstOperand { span: source_info.span, user_ty: None, const_: Const::Val(ConstValue::Scalar(Scalar::from_target_usize(0, &tcx)), tcx.types.usize), })); stmts.push(Statement { source_info, kind: StatementKind::Assign(Box::new(( is_ok, Rvalue::BinaryOp(BinOp::Eq, Box::new((Operand::Copy(alignment_bits), zero.clone()))), ))), }); // Emit a check that asserts on the alignment and otherwise triggers a // AssertKind::MisalignedPointerDereference. PointerCheck { cond: Operand::Copy(is_ok), assert_kind: Box::new(AssertKind::MisalignedPointerDereference { required: Operand::Copy(alignment), found: Operand::Copy(addr), }), } }