diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 35a03823595..16f7f9903f7 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -125,6 +125,12 @@ declare_lint! { "detect private items in public interfaces not caught by the old implementation" } +declare_lint! { + pub LEAKED_PRIVATE_DEPENDENCY, + Warn, + "public interface leaks type from a private dependency" +} + declare_lint! { pub PUB_USE_OF_PRIVATE_EXTERN_CRATE, Deny, diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 4b1aefb2216..5c2aa882a06 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -411,6 +411,10 @@ top_level_options!( remap_path_prefix: Vec<(PathBuf, PathBuf)> [UNTRACKED], edition: Edition [TRACKED], + + // The list of crates to consider public for + // checking leaked private dependency types in public interfaces + extern_public: FxHashSet [UNTRACKED], } ); @@ -606,6 +610,7 @@ impl Default for Options { cli_forced_thinlto_off: false, remap_path_prefix: Vec::new(), edition: DEFAULT_EDITION, + extern_public: FxHashSet::default() } } } @@ -1648,6 +1653,13 @@ pub fn rustc_short_optgroups() -> Vec { for the compiler to emit", "[bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]", ), + opt::multi_s( + "", + "extern-public", + "Comma separated list of crates to consider 'public' + for linting purposes", + "CRATES", + ), opt::opt_s( "", "crate-name", @@ -1905,6 +1917,17 @@ pub fn build_session_options_and_crate_config( let crate_types = parse_crate_types_from_list(unparsed_crate_types) .unwrap_or_else(|e| early_error(error_format, &e[..])); + if matches.opt_present("extern-public") && !nightly_options::is_nightly_build() { + early_error( + ErrorOutputType::default(), + "'--extern-public' is unstable and only \ + available for nightly builds of rustc." + ) + } + + let extern_public: FxHashSet = matches.opt_strs("extern-public"). + iter().cloned().collect(); + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format); let mut debugging_opts = build_debugging_options(matches, error_format); @@ -2287,6 +2310,7 @@ pub fn build_session_options_and_crate_config( cli_forced_thinlto_off: disable_thinlto, remap_path_prefix, edition, + extern_public }, cfg, ) diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index 46e784c4099..b0846822b24 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -1883,3 +1883,5 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements { } } + + diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index 6607951d2cd..e2e63b418c7 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -229,6 +229,11 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) { reference: "issue #34537 ", edition: None, }, + FutureIncompatibleInfo { + id: LintId::of(LEAKED_PRIVATE_DEPENDENCY), + reference: "issue #44663 ", + edition: None, + }, FutureIncompatibleInfo { id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE), reference: "issue #34537 ", diff --git a/src/librustc_privacy/lib.rs b/src/librustc_privacy/lib.rs index dcbb9ff4a75..c7cdb30521b 100644 --- a/src/librustc_privacy/lib.rs +++ b/src/librustc_privacy/lib.rs @@ -1458,6 +1458,7 @@ struct SearchInterfaceForPrivateItemsVisitor<'a, 'tcx: 'a> { has_pub_restricted: bool, has_old_errors: bool, in_assoc_ty: bool, + public_crates: FxHashSet } impl<'a, 'tcx: 'a> SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> { @@ -1514,22 +1515,134 @@ impl<'a, 'tcx: 'a> SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> { self.tcx.lint_node(lint::builtin::PRIVATE_IN_PUBLIC, node_id, self.span, &format!("{} (error {})", msg, err_code)); } + + if self.leaks_private_dep(trait_ref.def_id) { + self.tcx.lint_node(lint::builtin::LEAKED_PRIVATE_DEPENDENCY, + node_id, + self.span, + &format!("trait `{}` from private dependency '{}' in public \ + interface", trait_ref, + trait_ref.def_id.krate)); + + } } - false + } + + /// An item is 'leaked' from a private dependency if all + /// of the following are true: + /// 1. It's contained within a public type + /// 2. It does not come from a crate marked as public + fn leaks_private_dep(&self, item_id: DefId) -> bool { + // Never do any leak checking if the feature is not enabled + if !self.tcx.features().public_private_dependencies { + return false + } + self.required_visibility == ty::Visibility::Public && + !item_id.is_local() && + !self.public_crates.contains(&item_id.krate) } } -impl<'a, 'tcx> DefIdVisitor<'a, 'tcx> for SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> { - fn tcx(&self) -> TyCtxt<'a, 'tcx, 'tcx> { self.tcx } - fn visit_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool { - self.check_def_id(def_id, kind, descr) +impl<'a, 'tcx: 'a> TypeVisitor<'tcx> for SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool { + let ty_def_id = match ty.sty { + ty::Adt(adt, _) => Some(adt.did), + ty::Foreign(did) => Some(did), + ty::Dynamic(ref obj, ..) => Some(obj.principal().def_id()), + ty::Projection(ref proj) => { + if self.required_visibility == ty::Visibility::Invisible { + // Conservatively approximate the whole type alias as public without + // recursing into its components when determining impl publicity. + // For example, `impl ::Alias {...}` may be a public impl + // even if both `Type` and `Trait` are private. + // Ideally, associated types should be substituted in the same way as + // free type aliases, but this isn't done yet. + return false; + } + let trait_ref = proj.trait_ref(self.tcx); + Some(trait_ref.def_id) + } + _ => None + }; + + if let Some(def_id) = ty_def_id { + // Non-local means public (private items can't leave their crate, modulo bugs). + if let Some(node_id) = self.tcx.hir().as_local_node_id(def_id) { + let hir_vis = match self.tcx.hir().find(node_id) { + Some(Node::Item(item)) => &item.vis, + Some(Node::ForeignItem(item)) => &item.vis, + _ => bug!("expected item of foreign item"), + }; + + let vis = ty::Visibility::from_hir(hir_vis, node_id, self.tcx); + + if !vis.is_at_least(self.min_visibility, self.tcx) { + self.min_visibility = vis; + } + if !vis.is_at_least(self.required_visibility, self.tcx) { + let vis_adj = match hir_vis.node { + hir::VisibilityKind::Crate(_) => "crate-visible", + hir::VisibilityKind::Restricted { .. } => "restricted", + _ => "private" + }; + + if self.has_pub_restricted || self.has_old_errors || self.in_assoc_ty { + let mut err = struct_span_err!(self.tcx.sess, self.span, E0446, + "{} type `{}` in public interface", vis_adj, ty); + err.span_label(self.span, format!("can't leak {} type", vis_adj)); + err.span_label(hir_vis.span, format!("`{}` declared as {}", ty, vis_adj)); + err.emit(); + } else { + self.tcx.lint_node(lint::builtin::PRIVATE_IN_PUBLIC, + node_id, + self.span, + &format!("{} type `{}` in public \ + interface (error E0446)", vis_adj, ty)); + } + } + + if self.leaks_private_dep(def_id) { + self.tcx.lint_node(lint::builtin::LEAKED_PRIVATE_DEPENDENCY, + node_id, + self.span, + &format!("type '{}' from private dependency '{}' in \ + public interface", ty, def_id.krate)); + } + } + } + + ty.super_visit_with(self) } } +/*struct LeakedPrivateDependenciesVisitor<'a, 'tcx: 'a> { + tcx: TyCtxt<'a, 'tcx, 'tcx>, + public_crates: FxHashSet +} + +impl<'a, 'tcx> LeakedPrivateDependenciesVisitor<'a, 'tcx> { + fn is_private_dep(&self, item_id: DefId) { + !item_id.is_local() && !self.public_crates.contains(item_id.krate) + } + +} + +impl<'a, 'tcx> Visitor<'tcx> for LeakedPrivateDependenciesVisitor<'a, 'tcx> { + fn nested_visit_map<'this>(&'this mut self) -> nestedvisitormap<'this, 'tcx> { + nestedvisitormap::onlybodies(&self.tcx.hir()) + } + + fn visit_item(&mut self, item: &'tcx hir::Item) { + + } + +}*/ + struct PrivateItemsInPublicInterfacesVisitor<'a, 'tcx: 'a> { tcx: TyCtxt<'a, 'tcx, 'tcx>, has_pub_restricted: bool, old_error_set: &'a NodeSet, + public_crates: FxHashSet } impl<'a, 'tcx> PrivateItemsInPublicInterfacesVisitor<'a, 'tcx> { @@ -1566,6 +1679,7 @@ impl<'a, 'tcx> PrivateItemsInPublicInterfacesVisitor<'a, 'tcx> { has_pub_restricted: self.has_pub_restricted, has_old_errors, in_assoc_ty: false, + public_crates: self.public_crates.clone() } } @@ -1690,6 +1804,10 @@ pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Lrc { fn check_mod_privacy<'tcx>(tcx: TyCtxt<'_, 'tcx, 'tcx>, module_def_id: DefId) { let empty_tables = ty::TypeckTables::empty(None); + let public_crates: FxHashSet = tcx.sess.opts.extern_public.iter().flat_map(|c| { + tcx.crates().iter().find(|&&krate| &tcx.crate_name(krate) == c).cloned() + }).collect(); + // Check privacy of names not checked in previous compilation stages. let mut visitor = NamePrivacyVisitor { tcx, @@ -1767,6 +1885,7 @@ fn privacy_access_levels<'tcx>( tcx, has_pub_restricted, old_error_set: &visitor.old_error_set, + public_crates }; krate.visit_all_item_likes(&mut DeepVisitor::new(&mut visitor)); } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 9dd17b420aa..55eb6723188 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -462,6 +462,9 @@ declare_features! ( // #[optimize(X)] (active, optimize_attribute, "1.34.0", Some(54882), None), + + // Allows using the 'leaked private dependencies' lint + (active, public_private_dependencies, "1.32.0", Some(44663), None), ); declare_features! (