Initial implementation work
This commit is contained in:
parent
1aa250635e
commit
8b6f3ddf23
6 changed files with 164 additions and 5 deletions
|
@ -125,6 +125,12 @@ declare_lint! {
|
||||||
"detect private items in public interfaces not caught by the old implementation"
|
"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! {
|
declare_lint! {
|
||||||
pub PUB_USE_OF_PRIVATE_EXTERN_CRATE,
|
pub PUB_USE_OF_PRIVATE_EXTERN_CRATE,
|
||||||
Deny,
|
Deny,
|
||||||
|
|
|
@ -411,6 +411,10 @@ top_level_options!(
|
||||||
remap_path_prefix: Vec<(PathBuf, PathBuf)> [UNTRACKED],
|
remap_path_prefix: Vec<(PathBuf, PathBuf)> [UNTRACKED],
|
||||||
|
|
||||||
edition: Edition [TRACKED],
|
edition: Edition [TRACKED],
|
||||||
|
|
||||||
|
// The list of crates to consider public for
|
||||||
|
// checking leaked private dependency types in public interfaces
|
||||||
|
extern_public: FxHashSet<String> [UNTRACKED],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -606,6 +610,7 @@ impl Default for Options {
|
||||||
cli_forced_thinlto_off: false,
|
cli_forced_thinlto_off: false,
|
||||||
remap_path_prefix: Vec::new(),
|
remap_path_prefix: Vec::new(),
|
||||||
edition: DEFAULT_EDITION,
|
edition: DEFAULT_EDITION,
|
||||||
|
extern_public: FxHashSet::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1648,6 +1653,13 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
|
||||||
for the compiler to emit",
|
for the compiler to emit",
|
||||||
"[bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]",
|
"[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(
|
opt::opt_s(
|
||||||
"",
|
"",
|
||||||
"crate-name",
|
"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)
|
let crate_types = parse_crate_types_from_list(unparsed_crate_types)
|
||||||
.unwrap_or_else(|e| early_error(error_format, &e[..]));
|
.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<String> = matches.opt_strs("extern-public").
|
||||||
|
iter().cloned().collect();
|
||||||
|
|
||||||
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
|
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
|
||||||
|
|
||||||
let mut debugging_opts = build_debugging_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,
|
cli_forced_thinlto_off: disable_thinlto,
|
||||||
remap_path_prefix,
|
remap_path_prefix,
|
||||||
edition,
|
edition,
|
||||||
|
extern_public
|
||||||
},
|
},
|
||||||
cfg,
|
cfg,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1883,3 +1883,5 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExplicitOutlivesRequirements {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -229,6 +229,11 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
|
||||||
reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
|
reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
|
||||||
edition: None,
|
edition: None,
|
||||||
},
|
},
|
||||||
|
FutureIncompatibleInfo {
|
||||||
|
id: LintId::of(LEAKED_PRIVATE_DEPENDENCY),
|
||||||
|
reference: "issue #44663 <https://github.com/rust-lang/rust/issues/44663>",
|
||||||
|
edition: None,
|
||||||
|
},
|
||||||
FutureIncompatibleInfo {
|
FutureIncompatibleInfo {
|
||||||
id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE),
|
id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE),
|
||||||
reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
|
reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
|
||||||
|
|
|
@ -1458,6 +1458,7 @@ struct SearchInterfaceForPrivateItemsVisitor<'a, 'tcx: 'a> {
|
||||||
has_pub_restricted: bool,
|
has_pub_restricted: bool,
|
||||||
has_old_errors: bool,
|
has_old_errors: bool,
|
||||||
in_assoc_ty: bool,
|
in_assoc_ty: bool,
|
||||||
|
public_crates: FxHashSet<CrateNum>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx: 'a> SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> {
|
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,
|
self.tcx.lint_node(lint::builtin::PRIVATE_IN_PUBLIC, node_id, self.span,
|
||||||
&format!("{} (error {})", msg, err_code));
|
&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> {
|
impl<'a, 'tcx: 'a> TypeVisitor<'tcx> for SearchInterfaceForPrivateItemsVisitor<'a, 'tcx> {
|
||||||
fn tcx(&self) -> TyCtxt<'a, 'tcx, 'tcx> { self.tcx }
|
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
|
||||||
fn visit_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
|
let ty_def_id = match ty.sty {
|
||||||
self.check_def_id(def_id, kind, descr)
|
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 <Type as Trait>::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<CrateNum>
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
struct PrivateItemsInPublicInterfacesVisitor<'a, 'tcx: 'a> {
|
||||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||||
has_pub_restricted: bool,
|
has_pub_restricted: bool,
|
||||||
old_error_set: &'a NodeSet,
|
old_error_set: &'a NodeSet,
|
||||||
|
public_crates: FxHashSet<CrateNum>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> PrivateItemsInPublicInterfacesVisitor<'a, 'tcx> {
|
impl<'a, 'tcx> PrivateItemsInPublicInterfacesVisitor<'a, 'tcx> {
|
||||||
|
@ -1566,6 +1679,7 @@ impl<'a, 'tcx> PrivateItemsInPublicInterfacesVisitor<'a, 'tcx> {
|
||||||
has_pub_restricted: self.has_pub_restricted,
|
has_pub_restricted: self.has_pub_restricted,
|
||||||
has_old_errors,
|
has_old_errors,
|
||||||
in_assoc_ty: false,
|
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<AccessLevels> {
|
||||||
fn check_mod_privacy<'tcx>(tcx: TyCtxt<'_, 'tcx, 'tcx>, module_def_id: DefId) {
|
fn check_mod_privacy<'tcx>(tcx: TyCtxt<'_, 'tcx, 'tcx>, module_def_id: DefId) {
|
||||||
let empty_tables = ty::TypeckTables::empty(None);
|
let empty_tables = ty::TypeckTables::empty(None);
|
||||||
|
|
||||||
|
let public_crates: FxHashSet<CrateNum> = 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.
|
// Check privacy of names not checked in previous compilation stages.
|
||||||
let mut visitor = NamePrivacyVisitor {
|
let mut visitor = NamePrivacyVisitor {
|
||||||
tcx,
|
tcx,
|
||||||
|
@ -1767,6 +1885,7 @@ fn privacy_access_levels<'tcx>(
|
||||||
tcx,
|
tcx,
|
||||||
has_pub_restricted,
|
has_pub_restricted,
|
||||||
old_error_set: &visitor.old_error_set,
|
old_error_set: &visitor.old_error_set,
|
||||||
|
public_crates
|
||||||
};
|
};
|
||||||
krate.visit_all_item_likes(&mut DeepVisitor::new(&mut visitor));
|
krate.visit_all_item_likes(&mut DeepVisitor::new(&mut visitor));
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,6 +462,9 @@ declare_features! (
|
||||||
|
|
||||||
// #[optimize(X)]
|
// #[optimize(X)]
|
||||||
(active, optimize_attribute, "1.34.0", Some(54882), None),
|
(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! (
|
declare_features! (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue