| //! # The rustc_public's IR Visitor |
| //! |
| //! ## Overview |
| //! |
| //! We currently only support an immutable visitor. |
| //! The structure of this visitor is similar to the ones internal to `rustc`, |
| //! and it follows the following conventions: |
| //! |
| //! For every mir item, the trait has a `visit_<item>` and a `super_<item>` method. |
| //! - `visit_<item>`, by default, calls `super_<item>` |
| //! - `super_<item>`, by default, destructures the `<item>` and calls `visit_<sub_item>` for |
| //! all sub-items that compose the original item. |
| //! |
| //! In order to implement a visitor, override the `visit_*` methods for the types you are |
| //! interested in analyzing, and invoke (within that method call) |
| //! `self.super_*` to continue to the traverse. |
| //! Avoid calling `super` methods in other circumstances. |
| //! |
| //! For the most part, we do not destructure things external to the |
| //! MIR, e.g., types, spans, etc, but simply visit them and stop. |
| //! This avoids duplication with other visitors like `TypeFoldable`. |
| //! |
| //! ## Updating |
| //! |
| //! The code is written in a very deliberate style intended to minimize |
| //! the chance of things being overlooked. |
| //! |
| //! Use pattern matching to reference fields and ensure that all |
| //! matches are exhaustive. |
| //! |
| //! For this to work, ALL MATCHES MUST BE EXHAUSTIVE IN FIELDS AND VARIANTS. |
| //! That means you never write `..` to skip over fields, nor do you write `_` |
| //! to skip over variants in a `match`. |
| //! |
| //! The only place that `_` is acceptable is to match a field (or |
| //! variant argument) that does not require visiting. |
| |
| use crate::mir::*; |
| use crate::ty::{GenericArgs, MirConst, Region, Ty, TyConst}; |
| use crate::{Error, Opaque, Span}; |
| |
| macro_rules! make_mir_visitor { |
| ($visitor_trait_name:ident, $($mutability:ident)?) => { |
| pub trait $visitor_trait_name { |
| fn visit_body(&mut self, body: &$($mutability)? Body) { |
| self.super_body(body) |
| } |
| |
| fn visit_basic_block(&mut self, bb: &$($mutability)? BasicBlock) { |
| self.super_basic_block(bb) |
| } |
| |
| fn visit_ret_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| self.super_ret_decl(local, decl) |
| } |
| |
| fn visit_arg_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| self.super_arg_decl(local, decl) |
| } |
| |
| fn visit_local_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| self.super_local_decl(local, decl) |
| } |
| |
| fn visit_statement(&mut self, stmt: &$($mutability)? Statement, location: Location) { |
| self.super_statement(stmt, location) |
| } |
| |
| fn visit_terminator(&mut self, term: &$($mutability)? Terminator, location: Location) { |
| self.super_terminator(term, location) |
| } |
| |
| fn visit_span(&mut self, span: &$($mutability)? Span) { |
| self.super_span(span) |
| } |
| |
| fn visit_place(&mut self, place: &$($mutability)? Place, ptx: PlaceContext, location: Location) { |
| self.super_place(place, ptx, location) |
| } |
| |
| visit_place_fns!($($mutability)?); |
| |
| fn visit_local(&mut self, local: &$($mutability)? Local, ptx: PlaceContext, location: Location) { |
| let _ = (local, ptx, location); |
| } |
| |
| fn visit_rvalue(&mut self, rvalue: &$($mutability)? Rvalue, location: Location) { |
| self.super_rvalue(rvalue, location) |
| } |
| |
| fn visit_operand(&mut self, operand: &$($mutability)? Operand, location: Location) { |
| self.super_operand(operand, location) |
| } |
| |
| fn visit_user_type_projection(&mut self, projection: &$($mutability)? UserTypeProjection) { |
| self.super_user_type_projection(projection) |
| } |
| |
| fn visit_ty(&mut self, ty: &$($mutability)? Ty, location: Location) { |
| let _ = location; |
| self.super_ty(ty) |
| } |
| |
| fn visit_const_operand(&mut self, constant: &$($mutability)? ConstOperand, location: Location) { |
| self.super_const_operand(constant, location) |
| } |
| |
| fn visit_mir_const(&mut self, constant: &$($mutability)? MirConst, location: Location) { |
| self.super_mir_const(constant, location) |
| } |
| |
| fn visit_ty_const(&mut self, constant: &$($mutability)? TyConst, location: Location) { |
| let _ = location; |
| self.super_ty_const(constant) |
| } |
| |
| fn visit_region(&mut self, region: &$($mutability)? Region, location: Location) { |
| let _ = location; |
| self.super_region(region) |
| } |
| |
| fn visit_args(&mut self, args: &$($mutability)? GenericArgs, location: Location) { |
| let _ = location; |
| self.super_args(args) |
| } |
| |
| fn visit_assert_msg(&mut self, msg: &$($mutability)? AssertMessage, location: Location) { |
| self.super_assert_msg(msg, location) |
| } |
| |
| fn visit_var_debug_info(&mut self, var_debug_info: &$($mutability)? VarDebugInfo) { |
| self.super_var_debug_info(var_debug_info); |
| } |
| |
| fn super_body(&mut self, body: &$($mutability)? Body) { |
| super_body!(self, body, $($mutability)?); |
| } |
| |
| fn super_basic_block(&mut self, bb: &$($mutability)? BasicBlock) { |
| let BasicBlock { statements, terminator } = bb; |
| for stmt in statements { |
| self.visit_statement(stmt, Location(stmt.span)); |
| } |
| self.visit_terminator(terminator, Location(terminator.span)); |
| } |
| |
| fn super_local_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| let _ = local; |
| let LocalDecl { ty, span, .. } = decl; |
| self.visit_ty(ty, Location(*span)); |
| } |
| |
| fn super_ret_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| self.super_local_decl(local, decl) |
| } |
| |
| fn super_arg_decl(&mut self, local: Local, decl: &$($mutability)? LocalDecl) { |
| self.super_local_decl(local, decl) |
| } |
| |
| fn super_statement(&mut self, stmt: &$($mutability)? Statement, location: Location) { |
| let Statement { kind, span } = stmt; |
| self.visit_span(span); |
| match kind { |
| StatementKind::Assign(place, rvalue) => { |
| self.visit_place(place, PlaceContext::MUTATING, location); |
| self.visit_rvalue(rvalue, location); |
| } |
| StatementKind::FakeRead(_, place) | StatementKind::PlaceMention(place) => { |
| self.visit_place(place, PlaceContext::NON_MUTATING, location); |
| } |
| StatementKind::SetDiscriminant { place, .. } |
| | StatementKind::Deinit(place) |
| | StatementKind::Retag(_, place) => { |
| self.visit_place(place, PlaceContext::MUTATING, location); |
| } |
| StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { |
| self.visit_local(local, PlaceContext::NON_USE, location); |
| } |
| StatementKind::AscribeUserType { place, projections, variance: _ } => { |
| self.visit_place(place, PlaceContext::NON_USE, location); |
| self.visit_user_type_projection(projections); |
| } |
| StatementKind::Coverage(coverage) => visit_opaque(coverage), |
| StatementKind::Intrinsic(intrisic) => match intrisic { |
| NonDivergingIntrinsic::Assume(operand) => { |
| self.visit_operand(operand, location); |
| } |
| NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { |
| src, |
| dst, |
| count, |
| }) => { |
| self.visit_operand(src, location); |
| self.visit_operand(dst, location); |
| self.visit_operand(count, location); |
| } |
| }, |
| StatementKind::ConstEvalCounter | StatementKind::Nop => {} |
| } |
| } |
| |
| fn super_terminator(&mut self, term: &$($mutability)? Terminator, location: Location) { |
| let Terminator { kind, span } = term; |
| self.visit_span(span); |
| match kind { |
| TerminatorKind::Goto { .. } |
| | TerminatorKind::Resume |
| | TerminatorKind::Abort |
| | TerminatorKind::Unreachable => {} |
| TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { |
| self.visit_operand(cond, location); |
| self.visit_assert_msg(msg, location); |
| } |
| TerminatorKind::Drop { place, target: _, unwind: _ } => { |
| self.visit_place(place, PlaceContext::MUTATING, location); |
| } |
| TerminatorKind::Call { func, args, destination, target: _, unwind: _ } => { |
| self.visit_operand(func, location); |
| for arg in args { |
| self.visit_operand(arg, location); |
| } |
| self.visit_place(destination, PlaceContext::MUTATING, location); |
| } |
| TerminatorKind::InlineAsm { operands, .. } => { |
| for op in operands { |
| let InlineAsmOperand { in_value, out_place, raw_rpr: _ } = op; |
| if let Some(input) = in_value { |
| self.visit_operand(input, location); |
| } |
| if let Some(output) = out_place { |
| self.visit_place(output, PlaceContext::MUTATING, location); |
| } |
| } |
| } |
| TerminatorKind::Return => { |
| let $($mutability)? local = RETURN_LOCAL; |
| self.visit_local(&$($mutability)? local, PlaceContext::NON_MUTATING, location); |
| } |
| TerminatorKind::SwitchInt { discr, targets: _ } => { |
| self.visit_operand(discr, location); |
| } |
| } |
| } |
| |
| fn super_span(&mut self, span: &$($mutability)? Span) { |
| let _ = span; |
| } |
| |
| fn super_rvalue(&mut self, rvalue: &$($mutability)? Rvalue, location: Location) { |
| match rvalue { |
| Rvalue::AddressOf(mutability, place) => { |
| let pcx = PlaceContext { is_mut: *mutability == RawPtrKind::Mut }; |
| self.visit_place(place, pcx, location); |
| } |
| Rvalue::Aggregate(_, operands) => { |
| for op in operands { |
| self.visit_operand(op, location); |
| } |
| } |
| Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => { |
| self.visit_operand(lhs, location); |
| self.visit_operand(rhs, location); |
| } |
| Rvalue::Cast(_, op, ty) => { |
| self.visit_operand(op, location); |
| self.visit_ty(ty, location); |
| } |
| Rvalue::CopyForDeref(place) | Rvalue::Discriminant(place) | Rvalue::Len(place) => { |
| self.visit_place(place, PlaceContext::NON_MUTATING, location); |
| } |
| Rvalue::Ref(region, kind, place) => { |
| self.visit_region(region, location); |
| let pcx = PlaceContext { is_mut: matches!(kind, BorrowKind::Mut { .. }) }; |
| self.visit_place(place, pcx, location); |
| } |
| Rvalue::Repeat(op, constant) => { |
| self.visit_operand(op, location); |
| self.visit_ty_const(constant, location); |
| } |
| Rvalue::ShallowInitBox(op, ty) => { |
| self.visit_ty(ty, location); |
| self.visit_operand(op, location) |
| } |
| Rvalue::ThreadLocalRef(_) => {} |
| Rvalue::NullaryOp(_, ty) => { |
| self.visit_ty(ty, location); |
| } |
| Rvalue::UnaryOp(_, op) | Rvalue::Use(op) => { |
| self.visit_operand(op, location); |
| } |
| } |
| } |
| |
| fn super_operand(&mut self, operand: &$($mutability)? Operand, location: Location) { |
| match operand { |
| Operand::Copy(place) | Operand::Move(place) => { |
| self.visit_place(place, PlaceContext::NON_MUTATING, location) |
| } |
| Operand::Constant(constant) => { |
| self.visit_const_operand(constant, location); |
| } |
| } |
| } |
| |
| fn super_user_type_projection(&mut self, projection: &$($mutability)? UserTypeProjection) { |
| // This is a no-op on mir::Visitor. |
| let _ = projection; |
| } |
| |
| fn super_ty(&mut self, ty: &$($mutability)? Ty) { |
| let _ = ty; |
| } |
| |
| fn super_const_operand(&mut self, constant: &$($mutability)? ConstOperand, location: Location) { |
| let ConstOperand { span, user_ty: _, const_ } = constant; |
| self.visit_span(span); |
| self.visit_mir_const(const_, location); |
| } |
| |
| fn super_mir_const(&mut self, constant: &$($mutability)? MirConst, location: Location) { |
| let MirConst { kind: _, ty, id: _ } = constant; |
| self.visit_ty(ty, location); |
| } |
| |
| fn super_ty_const(&mut self, constant: &$($mutability)? TyConst) { |
| let _ = constant; |
| } |
| |
| fn super_region(&mut self, region: &$($mutability)? Region) { |
| let _ = region; |
| } |
| |
| fn super_args(&mut self, args: &$($mutability)? GenericArgs) { |
| let _ = args; |
| } |
| |
| fn super_var_debug_info(&mut self, var_debug_info: &$($mutability)? VarDebugInfo) { |
| let VarDebugInfo { source_info, composite, value, name: _, argument_index: _ } = |
| var_debug_info; |
| self.visit_span(&$($mutability)? source_info.span); |
| let location = Location(source_info.span); |
| if let Some(composite) = composite { |
| self.visit_ty(&$($mutability)? composite.ty, location); |
| } |
| match value { |
| VarDebugInfoContents::Place(place) => { |
| self.visit_place(place, PlaceContext::NON_USE, location); |
| } |
| VarDebugInfoContents::Const(constant) => { |
| self.visit_mir_const(&$($mutability)? constant.const_, location); |
| } |
| } |
| } |
| |
| fn super_assert_msg(&mut self, msg: &$($mutability)? AssertMessage, location: Location) { |
| match msg { |
| AssertMessage::BoundsCheck { len, index } => { |
| self.visit_operand(len, location); |
| self.visit_operand(index, location); |
| } |
| AssertMessage::Overflow(_, left, right) => { |
| self.visit_operand(left, location); |
| self.visit_operand(right, location); |
| } |
| AssertMessage::OverflowNeg(op) |
| | AssertMessage::DivisionByZero(op) |
| | AssertMessage::RemainderByZero(op) |
| | AssertMessage::InvalidEnumConstruction(op) => { |
| self.visit_operand(op, location); |
| } |
| AssertMessage::ResumedAfterReturn(_) |
| | AssertMessage::ResumedAfterPanic(_) |
| | AssertMessage::NullPointerDereference |
| | AssertMessage::ResumedAfterDrop(_) => { |
| //nothing to visit |
| } |
| AssertMessage::MisalignedPointerDereference { required, found } => { |
| self.visit_operand(required, location); |
| self.visit_operand(found, location); |
| } |
| } |
| } |
| } |
| }; |
| } |
| |
| macro_rules! super_body { |
| ($self:ident, $body:ident, mut) => { |
| for bb in $body.blocks.iter_mut() { |
| $self.visit_basic_block(bb); |
| } |
| |
| $self.visit_ret_decl(RETURN_LOCAL, $body.ret_local_mut()); |
| |
| for (idx, arg) in $body.arg_locals_mut().iter_mut().enumerate() { |
| $self.visit_arg_decl(idx + 1, arg) |
| } |
| |
| let local_start = $body.arg_count + 1; |
| for (idx, arg) in $body.inner_locals_mut().iter_mut().enumerate() { |
| $self.visit_local_decl(idx + local_start, arg) |
| } |
| |
| for info in $body.var_debug_info.iter_mut() { |
| $self.visit_var_debug_info(info); |
| } |
| |
| $self.visit_span(&mut $body.span) |
| }; |
| |
| ($self:ident, $body:ident, ) => { |
| let Body { blocks, locals: _, arg_count, var_debug_info, spread_arg: _, span } = $body; |
| |
| for bb in blocks { |
| $self.visit_basic_block(bb); |
| } |
| |
| $self.visit_ret_decl(RETURN_LOCAL, $body.ret_local()); |
| |
| for (idx, arg) in $body.arg_locals().iter().enumerate() { |
| $self.visit_arg_decl(idx + 1, arg) |
| } |
| |
| let local_start = arg_count + 1; |
| for (idx, arg) in $body.inner_locals().iter().enumerate() { |
| $self.visit_local_decl(idx + local_start, arg) |
| } |
| |
| for info in var_debug_info.iter() { |
| $self.visit_var_debug_info(info); |
| } |
| |
| $self.visit_span(span) |
| }; |
| } |
| |
| macro_rules! visit_place_fns { |
| (mut) => { |
| fn super_place(&mut self, place: &mut Place, ptx: PlaceContext, location: Location) { |
| self.visit_local(&mut place.local, ptx, location); |
| |
| for elem in place.projection.iter_mut() { |
| self.visit_projection_elem(elem, ptx, location); |
| } |
| } |
| |
| // We don't have to replicate the `process_projection()` like we did in |
| // `rustc_middle::mir::visit.rs` here because the `projection` field in `Place` |
| // of Stable-MIR is not an immutable borrow, unlike in `Place` of MIR. |
| fn visit_projection_elem( |
| &mut self, |
| elem: &mut ProjectionElem, |
| ptx: PlaceContext, |
| location: Location, |
| ) { |
| self.super_projection_elem(elem, ptx, location) |
| } |
| |
| fn super_projection_elem( |
| &mut self, |
| elem: &mut ProjectionElem, |
| ptx: PlaceContext, |
| location: Location, |
| ) { |
| match elem { |
| ProjectionElem::Deref => {} |
| ProjectionElem::Field(_idx, ty) => self.visit_ty(ty, location), |
| ProjectionElem::Index(local) => self.visit_local(local, ptx, location), |
| ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } => {} |
| ProjectionElem::Subslice { from: _, to: _, from_end: _ } => {} |
| ProjectionElem::Downcast(_idx) => {} |
| ProjectionElem::OpaqueCast(ty) => self.visit_ty(ty, location), |
| ProjectionElem::Subtype(ty) => self.visit_ty(ty, location), |
| } |
| } |
| }; |
| |
| () => { |
| fn super_place(&mut self, place: &Place, ptx: PlaceContext, location: Location) { |
| self.visit_local(&place.local, ptx, location); |
| |
| for (idx, elem) in place.projection.iter().enumerate() { |
| let place_ref = |
| PlaceRef { local: place.local, projection: &place.projection[..idx] }; |
| self.visit_projection_elem(place_ref, elem, ptx, location); |
| } |
| } |
| |
| fn visit_projection_elem<'a>( |
| &mut self, |
| place_ref: PlaceRef<'a>, |
| elem: &ProjectionElem, |
| ptx: PlaceContext, |
| location: Location, |
| ) { |
| let _ = place_ref; |
| self.super_projection_elem(elem, ptx, location); |
| } |
| |
| fn super_projection_elem( |
| &mut self, |
| elem: &ProjectionElem, |
| ptx: PlaceContext, |
| location: Location, |
| ) { |
| match elem { |
| ProjectionElem::Deref => {} |
| ProjectionElem::Field(_idx, ty) => self.visit_ty(ty, location), |
| ProjectionElem::Index(local) => self.visit_local(local, ptx, location), |
| ProjectionElem::ConstantIndex { offset: _, min_length: _, from_end: _ } => {} |
| ProjectionElem::Subslice { from: _, to: _, from_end: _ } => {} |
| ProjectionElem::Downcast(_idx) => {} |
| ProjectionElem::OpaqueCast(ty) => self.visit_ty(ty, location), |
| ProjectionElem::Subtype(ty) => self.visit_ty(ty, location), |
| } |
| } |
| }; |
| } |
| |
| make_mir_visitor!(MirVisitor,); |
| make_mir_visitor!(MutMirVisitor, mut); |
| |
| /// This function is a no-op that gets used to ensure this visitor is kept up-to-date. |
| /// |
| /// The idea is that whenever we replace an Opaque type by a real type, the compiler will fail |
| /// when trying to invoke `visit_opaque`. |
| /// |
| /// If you are here because your compilation is broken, replace the failing call to `visit_opaque()` |
| /// by a `visit_<CONSTRUCT>` for your construct. |
| fn visit_opaque(_: &Opaque) {} |
| |
| /// The location of a statement / terminator in the code and the CFG. |
| #[derive(Clone, Copy, PartialEq, Eq, Debug)] |
| pub struct Location(Span); |
| |
| impl Location { |
| pub fn span(&self) -> Span { |
| self.0 |
| } |
| } |
| |
| /// Location of the statement at the given index for a given basic block. Assumes that `stmt_idx` |
| /// and `bb_idx` are valid for a given body. |
| pub fn statement_location(body: &Body, bb_idx: &BasicBlockIdx, stmt_idx: usize) -> Location { |
| let bb = &body.blocks[*bb_idx]; |
| let stmt = &bb.statements[stmt_idx]; |
| Location(stmt.span) |
| } |
| |
| /// Location of the terminator for a given basic block. Assumes that `bb_idx` is valid for a given |
| /// body. |
| pub fn terminator_location(body: &Body, bb_idx: &BasicBlockIdx) -> Location { |
| let bb = &body.blocks[*bb_idx]; |
| let terminator = &bb.terminator; |
| Location(terminator.span) |
| } |
| |
| /// Reference to a place used to represent a partial projection. |
| pub struct PlaceRef<'a> { |
| pub local: Local, |
| pub projection: &'a [ProjectionElem], |
| } |
| |
| impl PlaceRef<'_> { |
| /// Get the type of this place. |
| pub fn ty(&self, locals: &[LocalDecl]) -> Result<Ty, Error> { |
| self.projection.iter().try_fold(locals[self.local].ty, |place_ty, elem| elem.ty(place_ty)) |
| } |
| } |
| |
| /// Information about a place's usage. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] |
| pub struct PlaceContext { |
| /// Whether the access is mutable or not. Keep this private so we can increment the type in a |
| /// backward compatible manner. |
| is_mut: bool, |
| } |
| |
| impl PlaceContext { |
| const MUTATING: Self = PlaceContext { is_mut: true }; |
| const NON_MUTATING: Self = PlaceContext { is_mut: false }; |
| const NON_USE: Self = PlaceContext { is_mut: false }; |
| |
| pub fn is_mutating(&self) -> bool { |
| self.is_mut |
| } |
| } |