| //! # 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, .. } => { | |
| 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(intrinsic) => match intrinsic { | |
| 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::Reborrow(target, mutability, place) => { | |
| self.visit_ty(target, location); | |
| let pcx = PlaceContext { is_mut: matches!(mutability, Mutability::Mut) }; | |
| self.visit_place(place, pcx, location); | |
| } | |
| Rvalue::Repeat(op, constant) => { | |
| self.visit_operand(op, location); | |
| self.visit_ty_const(constant, location); | |
| } | |
| Rvalue::ThreadLocalRef(_) => {} | |
| 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); | |
| } | |
| Operand::RuntimeChecks(_) => {} | |
| } | |
| } | |
| 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), | |
| } | |
| } | |
| }; | |
| () => { | |
| 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), | |
| } | |
| } | |
| }; | |
| } | |
| 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 | |
| } | |
| } |