| use rustc_abi::Align; | |
| use rustc_hir::attrs::lang_items::LangItem; | |
| 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 crate::PassPolicy; | |
| use crate::check_pointers::{BorrowedFieldProjectionMode, PointerCheck, check_pointers}; | |
| pub(super) struct CheckAlignment; | |
| impl<'tcx> crate::MirPass<'tcx> for CheckAlignment { | |
| fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy { | |
| // When UB checks are enabled this is part of their semantics, not an optimization. | |
| PassPolicy::optional(ctx.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, | |
| ); | |
| } | |
| } | |
| /// 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<Local, LocalDecl<'tcx>>, | |
| stmts: &mut Vec<Statement<'tcx>>, | |
| 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::new(source_info, 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::new(source_info, StatementKind::Assign(Box::new((addr, rvalue))))); | |
| // Get the alignment of the pointee | |
| let align_def_id = tcx.require_lang_item(LangItem::AlignOf, source_info.span); | |
| let alignment = | |
| Operand::unevaluated_constant(tcx, align_def_id, &[pointee_ty.into()], source_info.span); | |
| // 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::new( | |
| source_info, | |
| StatementKind::Assign(Box::new(( | |
| alignment_mask, | |
| Rvalue::BinaryOp(BinOp::Sub, Box::new((alignment.clone(), one))), | |
| ))), | |
| )); | |
| // If this target does not have reliable alignment, further limit the mask by anding it with | |
| // the mask for the highest reliable alignment. | |
| 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::new( | |
| source_info, | |
| 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::new( | |
| source_info, | |
| 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::new( | |
| source_info, | |
| 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: alignment, | |
| found: Operand::Copy(addr), | |
| }), | |
| } | |
| } |