| //! A pass that checks to make sure private fields and methods aren't used | |
| //! outside their scopes. This pass will also generate a set of exported items | |
| //! which are available for use externally when compiled as a library. | |
| use std::cmp::Ordering; | |
| use std::hash::Hash; | |
| use rustc_data_structures::fx::{FxIndexMap, IndexEntry}; | |
| use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; | |
| use rustc_hir::def::DefKind; | |
| use rustc_hir::{ItemKind, Node, UseKind}; | |
| use rustc_macros::StableHash; | |
| use rustc_span::def_id::{CRATE_DEF_ID, LocalDefId}; | |
| use crate::ty::{TyCtxt, Visibility}; | |
| /// Represents the levels of effective visibility an item can have. | |
| /// | |
| /// The variants are sorted in ascending order of directness. | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, StableHash)] | |
| pub enum Level { | |
| /// Superset of `Reachable` including items leaked through return position `impl Trait`. | |
| ReachableThroughImplTrait, | |
| /// Item is either reexported, or leaked through any kind of interface. | |
| /// For example, if function `fn f() -> T {...}` is directly public, then type `T` is publicly | |
| /// reachable and its values can be obtained by other crates even if the type itself is not | |
| /// nameable. | |
| Reachable, | |
| /// Item is accessible either directly, or with help of `use` reexports. | |
| Reexported, | |
| /// Item is directly accessible, without help of reexports. | |
| Direct, | |
| } | |
| impl Level { | |
| pub fn all_levels() -> [Level; 4] { | |
| [Level::Direct, Level::Reexported, Level::Reachable, Level::ReachableThroughImplTrait] | |
| } | |
| } | |
| #[derive(Clone, Copy, PartialEq, Eq, Debug, StableHash)] | |
| pub struct EffectiveVisibility { | |
| direct: Visibility, | |
| reexported: Visibility, | |
| reachable: Visibility, | |
| reachable_through_impl_trait: Visibility, | |
| } | |
| impl EffectiveVisibility { | |
| pub fn at_level(&self, level: Level) -> &Visibility { | |
| match level { | |
| Level::Direct => &self.direct, | |
| Level::Reexported => &self.reexported, | |
| Level::Reachable => &self.reachable, | |
| Level::ReachableThroughImplTrait => &self.reachable_through_impl_trait, | |
| } | |
| } | |
| fn at_level_mut(&mut self, level: Level) -> &mut Visibility { | |
| match level { | |
| Level::Direct => &mut self.direct, | |
| Level::Reexported => &mut self.reexported, | |
| Level::Reachable => &mut self.reachable, | |
| Level::ReachableThroughImplTrait => &mut self.reachable_through_impl_trait, | |
| } | |
| } | |
| pub fn public_at_level(&self) -> Option<Level> { | |
| Level::all_levels().into_iter().find(|&level| self.is_public_at_level(level)) | |
| } | |
| pub fn is_public_at_level(&self, level: Level) -> bool { | |
| self.at_level(level).is_public() | |
| } | |
| pub const fn from_vis(vis: Visibility) -> EffectiveVisibility { | |
| EffectiveVisibility { | |
| direct: vis, | |
| reexported: vis, | |
| reachable: vis, | |
| reachable_through_impl_trait: vis, | |
| } | |
| } | |
| #[must_use] | |
| pub fn min(mut self, lhs: EffectiveVisibility, tcx: TyCtxt<'_>) -> Self { | |
| for l in Level::all_levels() { | |
| let rhs_vis = self.at_level_mut(l); | |
| let lhs_vis = *lhs.at_level(l); | |
| // FIXME: figure out why unordered visibilities occur here, | |
| // and what the behavior for them should be. | |
| if rhs_vis.partial_cmp(lhs_vis, tcx) == Some(Ordering::Greater) { | |
| *rhs_vis = lhs_vis; | |
| }; | |
| } | |
| self | |
| } | |
| } | |
| /// Holds a map of effective visibilities for reachable HIR nodes. | |
| #[derive(Clone, Debug)] | |
| pub struct EffectiveVisibilities<Id = LocalDefId> { | |
| map: FxIndexMap<Id, EffectiveVisibility>, | |
| } | |
| impl EffectiveVisibilities { | |
| pub fn is_public_at_level(&self, id: LocalDefId, level: Level) -> bool { | |
| self.effective_vis(id).is_some_and(|effective_vis| effective_vis.is_public_at_level(level)) | |
| } | |
| /// See `Level::Reachable`. | |
| pub fn is_reachable(&self, id: LocalDefId) -> bool { | |
| self.is_public_at_level(id, Level::Reachable) | |
| } | |
| /// See `Level::Reexported`. | |
| pub fn is_exported(&self, id: LocalDefId) -> bool { | |
| self.is_public_at_level(id, Level::Reexported) | |
| } | |
| /// See `Level::Direct`. | |
| pub fn is_directly_public(&self, id: LocalDefId) -> bool { | |
| self.is_public_at_level(id, Level::Direct) | |
| } | |
| pub fn public_at_level(&self, id: LocalDefId) -> Option<Level> { | |
| self.effective_vis(id).and_then(|effective_vis| effective_vis.public_at_level()) | |
| } | |
| pub fn update_root(&mut self) { | |
| self.map.insert(CRATE_DEF_ID, EffectiveVisibility::from_vis(Visibility::Public)); | |
| } | |
| // FIXME: Share code with `fn update`. | |
| pub fn update_eff_vis( | |
| &mut self, | |
| def_id: LocalDefId, | |
| eff_vis: &EffectiveVisibility, | |
| tcx: TyCtxt<'_>, | |
| ) { | |
| match self.map.entry(def_id) { | |
| IndexEntry::Occupied(mut occupied) => { | |
| let old_eff_vis = occupied.get_mut(); | |
| for l in Level::all_levels() { | |
| let vis_at_level = eff_vis.at_level(l); | |
| let old_vis_at_level = old_eff_vis.at_level_mut(l); | |
| if vis_at_level.greater_than(*old_vis_at_level, tcx) { | |
| *old_vis_at_level = *vis_at_level | |
| } | |
| } | |
| old_eff_vis | |
| } | |
| IndexEntry::Vacant(vacant) => vacant.insert(*eff_vis), | |
| }; | |
| } | |
| pub fn check_invariants(&self, tcx: TyCtxt<'_>) { | |
| if !cfg!(debug_assertions) { | |
| return; | |
| } | |
| for (&def_id, ev) in &self.map { | |
| // More direct visibility levels can never go farther than less direct ones, | |
| // and all effective visibilities are larger or equal than private visibility. | |
| let private_vis = Visibility::Restricted(tcx.parent_module_from_def_id(def_id)); | |
| let span = tcx.def_span(def_id.to_def_id()); | |
| if private_vis.greater_than(ev.direct, tcx) { | |
| span_bug!(span, "private {:?} > direct {:?}", private_vis, ev.direct); | |
| } | |
| if ev.direct.greater_than(ev.reexported, tcx) { | |
| span_bug!(span, "direct {:?} > reexported {:?}", ev.direct, ev.reexported); | |
| } | |
| if ev.reexported.greater_than(ev.reachable, tcx) { | |
| span_bug!(span, "reexported {:?} > reachable {:?}", ev.reexported, ev.reachable); | |
| } | |
| if ev.reachable.greater_than(ev.reachable_through_impl_trait, tcx) { | |
| span_bug!( | |
| span, | |
| "reachable {:?} > reachable_through_impl_trait {:?}", | |
| ev.reachable, | |
| ev.reachable_through_impl_trait | |
| ); | |
| } | |
| // All effective visibilities except `reachable_through_impl_trait` are limited to | |
| // nominal visibility. For some items nominal visibility doesn't make sense so we | |
| // don't check this condition for them. | |
| let is_impl = matches!(tcx.def_kind(def_id), DefKind::Impl { .. }); | |
| if !is_impl && tcx.trait_impl_of_assoc(def_id.to_def_id()).is_none() { | |
| let nominal_vis = tcx.visibility(def_id); | |
| if ev.reachable.greater_than(nominal_vis, tcx) { | |
| if let Node::Item(item) = tcx.hir_node_by_def_id(def_id) | |
| && let ItemKind::Use(_, UseKind::Glob) = item.kind | |
| { | |
| // Glob import visibilities can be increased by other | |
| // more public glob imports in cases of ambiguity. | |
| } else { | |
| span_bug!( | |
| span, | |
| "{:?}: reachable {:?} > nominal {:?}", | |
| def_id, | |
| ev.reachable, | |
| nominal_vis, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| impl<Id: Eq + Hash> EffectiveVisibilities<Id> { | |
| pub fn iter(&self) -> impl Iterator<Item = (&Id, &EffectiveVisibility)> { | |
| self.map.iter() | |
| } | |
| pub fn effective_vis(&self, id: Id) -> Option<&EffectiveVisibility> { | |
| self.map.get(&id) | |
| } | |
| pub fn effective_vis_or_private( | |
| &mut self, | |
| id: Id, | |
| lazy_private_vis: impl FnOnce() -> Visibility, | |
| ) -> &mut EffectiveVisibility { | |
| self.map.entry(id).or_insert_with(|| EffectiveVisibility::from_vis(lazy_private_vis())) | |
| } | |
| pub fn update( | |
| &mut self, | |
| id: Id, | |
| max_vis: Option<Visibility>, | |
| private_vis: Visibility, | |
| inherited_effective_vis: EffectiveVisibility, | |
| level: Level, | |
| tcx: TyCtxt<'_>, | |
| ) -> bool { | |
| let mut changed = false; | |
| let current_effective_vis = self.effective_vis_or_private(id, || private_vis); | |
| let mut inherited_effective_vis_at_prev_level = *inherited_effective_vis.at_level(level); | |
| let mut calculated_effective_vis = inherited_effective_vis_at_prev_level; | |
| for l in Level::all_levels() { | |
| if level >= l { | |
| let inherited_effective_vis_at_level = *inherited_effective_vis.at_level(l); | |
| let current_effective_vis_at_level = current_effective_vis.at_level_mut(l); | |
| // effective visibility for id shouldn't be recalculated if | |
| // inherited from parent_id effective visibility isn't changed at next level | |
| if !(inherited_effective_vis_at_prev_level == inherited_effective_vis_at_level | |
| && level != l) | |
| { | |
| // FIXME: figure out why unordered visibilities occur here, | |
| // and what the behavior for them should be. | |
| calculated_effective_vis = if let Some(max_vis) = max_vis | |
| && inherited_effective_vis_at_level.partial_cmp(max_vis, tcx) | |
| == Some(Ordering::Greater) | |
| { | |
| max_vis | |
| } else { | |
| inherited_effective_vis_at_level | |
| } | |
| } | |
| // effective visibility can't be decreased at next update call for the | |
| // same id | |
| // FIXME: figure out why unordered visibilities occur here, | |
| // and what the behavior for them should be. | |
| if calculated_effective_vis.partial_cmp(*current_effective_vis_at_level, tcx) | |
| == Some(Ordering::Greater) | |
| { | |
| changed = true; | |
| *current_effective_vis_at_level = calculated_effective_vis; | |
| } | |
| inherited_effective_vis_at_prev_level = inherited_effective_vis_at_level; | |
| } | |
| } | |
| changed | |
| } | |
| } | |
| impl<Id> Default for EffectiveVisibilities<Id> { | |
| fn default() -> Self { | |
| EffectiveVisibilities { map: Default::default() } | |
| } | |
| } | |
| impl StableHash for EffectiveVisibilities { | |
| fn stable_hash<Hcx: StableHashCtxt>(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { | |
| let EffectiveVisibilities { ref map } = *self; | |
| map.stable_hash(hcx, hasher); | |
| } | |
| } |