-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rustc_privacy: Expand public node set, build exported node set more correctly #29291
Changes from 2 commits
599df16
43b4373
8cef018
243a524
61cbc84
ab7b345
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,7 +162,7 @@ struct EmbargoVisitor<'a, 'tcx: 'a> { | |
// This is a list of all exported items in the AST. An exported item is any | ||
// function/method/item which is usable by external crates. This essentially | ||
// means that the result is "public all the way down", but the "path down" | ||
// may jump across private boundaries through reexport statements. | ||
// may jump across private boundaries through reexport statements or type aliases. | ||
exported_items: ExportedItems, | ||
|
||
// This sets contains all the destination nodes which are publicly | ||
|
@@ -172,9 +172,12 @@ struct EmbargoVisitor<'a, 'tcx: 'a> { | |
// destination must also be exported. | ||
reexports: NodeSet, | ||
|
||
// Items that are directly public without help of reexports or type aliases. | ||
// These two fields are closely related to one another in that they are only | ||
// used for generation of the 'PublicItems' set, not for privacy checking at | ||
// all | ||
// used for generation of the `public_items` set, not for privacy checking at | ||
// all. Public items are mostly a subset of exported items with exception of | ||
// fields and exported macros - they are public, but not exported. | ||
// FIXME: Make fields and exported macros exported as well (requires fixing resulting ICEs) | ||
public_items: PublicItems, | ||
prev_public: bool, | ||
} | ||
|
@@ -194,58 +197,103 @@ impl<'a, 'tcx> EmbargoVisitor<'a, 'tcx> { | |
fn exported_trait(&self, _id: ast::NodeId) -> bool { | ||
true | ||
} | ||
|
||
fn is_public_exported_ty(&self, ty: &hir::Ty) -> (bool, bool) { | ||
if let hir::TyPath(..) = ty.node { | ||
match self.tcx.def_map.borrow().get(&ty.id).unwrap().full_def() { | ||
def::DefPrimTy(..) | def::DefSelfTy(..) => (true, true), | ||
def => { | ||
if let Some(node_id) = self.tcx.map.as_local_node_id(def.def_id()) { | ||
(self.public_items.contains(&node_id), | ||
self.exported_items.contains(&node_id)) | ||
} else { | ||
(true, true) | ||
} | ||
} | ||
} | ||
} else { | ||
(true, true) | ||
} | ||
} | ||
|
||
fn is_public_exported_trait(&self, trait_ref: &hir::TraitRef) -> (bool, bool) { | ||
let did = self.tcx.trait_ref_to_def_id(trait_ref); | ||
if let Some(node_id) = self.tcx.map.as_local_node_id(did) { | ||
(self.public_items.contains(&node_id), self.exported_items.contains(&node_id)) | ||
} else { | ||
(true, true) | ||
} | ||
} | ||
|
||
fn maybe_insert_id(&mut self, id: ast::NodeId) { | ||
if self.prev_public { self.public_items.insert(id); } | ||
if self.prev_exported { self.exported_items.insert(id); } | ||
} | ||
} | ||
|
||
impl<'a, 'tcx, 'v> Visitor<'v> for EmbargoVisitor<'a, 'tcx> { | ||
fn visit_item(&mut self, item: &hir::Item) { | ||
let orig_all_pub = self.prev_public; | ||
self.prev_public = orig_all_pub && item.vis == hir::Public; | ||
if self.prev_public { | ||
self.public_items.insert(item.id); | ||
} | ||
|
||
let orig_all_public = self.prev_public; | ||
let orig_all_exported = self.prev_exported; | ||
match item.node { | ||
// impls/extern blocks do not break the "public chain" because they | ||
// cannot have visibility qualifiers on them anyway | ||
// cannot have visibility qualifiers on them anyway. They are also not | ||
// added to public/exported sets based on inherited publicity. | ||
hir::ItemImpl(..) | hir::ItemDefaultImpl(..) | hir::ItemForeignMod(..) => {} | ||
|
||
// Traits are a little special in that even if they themselves are | ||
// not public they may still be exported. | ||
hir::ItemTrait(..) => { | ||
self.prev_public = self.prev_public && item.vis == hir::Public; | ||
self.prev_exported = self.exported_trait(item.id); | ||
|
||
self.maybe_insert_id(item.id); | ||
} | ||
|
||
// Private by default, hence we only retain the "public chain" if | ||
// `pub` is explicitly listed. | ||
_ => { | ||
self.prev_exported = | ||
(orig_all_exported && item.vis == hir::Public) || | ||
self.reexports.contains(&item.id); | ||
self.prev_public = self.prev_public && item.vis == hir::Public; | ||
self.prev_exported = self.prev_exported && item.vis == hir::Public || | ||
self.reexports.contains(&item.id); | ||
|
||
self.maybe_insert_id(item.id); | ||
} | ||
} | ||
|
||
let public_first = self.prev_exported && | ||
self.exported_items.insert(item.id); | ||
|
||
match item.node { | ||
// Enum variants inherit from their parent, so if the enum is | ||
// public all variants are public unless they're explicitly priv | ||
hir::ItemEnum(ref def, _) if public_first => { | ||
// public all variants are public | ||
hir::ItemEnum(ref def, _) => { | ||
for variant in &def.variants { | ||
self.exported_items.insert(variant.node.data.id()); | ||
self.public_items.insert(variant.node.data.id()); | ||
self.maybe_insert_id(variant.node.data.id()); | ||
for field in variant.node.data.fields() { | ||
// Variant fields are always public | ||
if self.prev_public { self.public_items.insert(field.node.id); } | ||
// FIXME: Make fields exported (requires fixing resulting ICEs) | ||
// if self.prev_exported { self.exported_items.insert(field.node.id); } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little worried by the number of FIXME annotations present here, especially in cases like this where the nodes were previously in the set of exported items. Could you elaborate on the ICEs you ran into? Could they be fixed as part of this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fields weren't previously in the exported set, i.e. it's not a regression. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh fields, I missed that part! I would still like to have a better handle on what all these mysterious ICEs are. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ICE I see is due to absence of field ids from |
||
} | ||
} | ||
} | ||
|
||
// * Inherent impls for public types only have public methods exported | ||
// * Inherent impls for private types do not need to export their methods | ||
// * Inherent impls themselves are not exported, they are nothing more than | ||
// containers for other items | ||
hir::ItemImpl(_, _, _, None, ref ty, ref impl_items) => { | ||
let (public_ty, exported_ty) = self.is_public_exported_ty(&ty); | ||
|
||
for impl_item in impl_items { | ||
if impl_item.vis == hir::Public { | ||
if public_ty { self.public_items.insert(impl_item.id); } | ||
if exported_ty { self.exported_items.insert(impl_item.id); } | ||
} | ||
} | ||
} | ||
|
||
// Implementations are a little tricky to determine what's exported | ||
// out of them. Here's a few cases which are currently defined: | ||
// | ||
// * Impls for private types do not need to export their methods | ||
// (either public or private methods) | ||
// | ||
// * Impls for public types only have public methods exported | ||
// | ||
// * Public trait impls for public types must have all methods | ||
// exported. | ||
// | ||
|
@@ -257,83 +305,51 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EmbargoVisitor<'a, 'tcx> { | |
// undefined symbols at linkage time if this case is not handled. | ||
// | ||
// * Private trait impls for private types can be completely ignored | ||
hir::ItemImpl(_, _, _, _, ref ty, ref impl_items) => { | ||
let public_ty = match ty.node { | ||
hir::TyPath(..) => { | ||
match self.tcx.def_map.borrow().get(&ty.id).unwrap().full_def() { | ||
def::DefPrimTy(..) => true, | ||
def::DefSelfTy(..) => true, | ||
def => { | ||
let did = def.def_id(); | ||
if let Some(node_id) = self.tcx.map.as_local_node_id(did) { | ||
self.exported_items.contains(&node_id) | ||
} else { | ||
true | ||
} | ||
} | ||
} | ||
} | ||
_ => true, | ||
}; | ||
let tr = self.tcx.impl_trait_ref(self.tcx.map.local_def_id(item.id)); | ||
let public_trait = tr.clone().map_or(false, |tr| { | ||
if let Some(node_id) = self.tcx.map.as_local_node_id(tr.def_id) { | ||
self.exported_items.contains(&node_id) | ||
} else { | ||
true | ||
} | ||
}); | ||
hir::ItemImpl(_, _, _, Some(ref trait_ref), ref ty, ref impl_items) => { | ||
let (public_ty, _exported_ty) = self.is_public_exported_ty(&ty); | ||
let (public_trait, exported_trait) = self.is_public_exported_trait(trait_ref); | ||
|
||
if public_ty || public_trait { | ||
for impl_item in impl_items { | ||
match impl_item.node { | ||
hir::ConstImplItem(..) => { | ||
if (public_ty && impl_item.vis == hir::Public) | ||
|| tr.is_some() { | ||
self.exported_items.insert(impl_item.id); | ||
} | ||
} | ||
hir::MethodImplItem(ref sig, _) => { | ||
let meth_public = match sig.explicit_self.node { | ||
hir::SelfStatic => public_ty, | ||
_ => true, | ||
} && impl_item.vis == hir::Public; | ||
if meth_public || tr.is_some() { | ||
self.exported_items.insert(impl_item.id); | ||
} | ||
} | ||
hir::TypeImplItem(_) => {} | ||
} | ||
} | ||
if public_ty && public_trait { self.public_items.insert(item.id); } | ||
if exported_trait { self.exported_items.insert(item.id); } | ||
|
||
for impl_item in impl_items { | ||
if public_ty && public_trait { self.public_items.insert(impl_item.id); } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh hm, I guess exported is handled the same as it was before. I guess I don't quite understand the implication of this, what does it mean for a method in a trait impl to be a public item? The answer to that probably answers the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trait impls are public if both the trait and the type are public, in this case we can require stability annotations on them and document them in rustdoc. Regarding the exported set, if the trait is exported, then impl is exported (with accordance to the comment above). This comment - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rustdoc uses the exported set currently, but only due to deficiencies of the public set, I guess. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all impl methods need to be exported, because inference can be made to pick them. For example: in crate foo #![crate_type="rlib"]
pub trait Foo { fn poke(self); }
struct Bar;
impl Foo for Option<Bar> { fn poke(self) { } } then in some other crate: extern crate foo;
use foo::Foo;
fn main() {
None.poke();
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't happen with inherent impls for some reason: Crate 1:
Crate 2:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm interested, why do non-exported free functions used in public generic functions don't result in linker errors, but trait methods do? Because the set of external linker symbols is built somewhere before monomorphization? (Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Your example is a different issue - the problem is that reachability checking does not handle private traits correctly (this is indeed the job of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @arielb1 By the way, does rustc have a function like
|
||
if exported_trait { self.exported_items.insert(impl_item.id); } | ||
} | ||
} | ||
|
||
// Default trait impls are exported for public traits | ||
hir::ItemDefaultImpl(_, ref trait_ref) => { | ||
let (public_trait, exported_trait) = self.is_public_exported_trait(trait_ref); | ||
|
||
if public_trait { self.public_items.insert(item.id); } | ||
if exported_trait { self.exported_items.insert(item.id); } | ||
} | ||
|
||
// Default methods on traits are all public so long as the trait | ||
// is public | ||
hir::ItemTrait(_, _, _, ref trait_items) if public_first => { | ||
hir::ItemTrait(_, _, _, ref trait_items) => { | ||
for trait_item in trait_items { | ||
debug!("trait item {}", trait_item.id); | ||
self.exported_items.insert(trait_item.id); | ||
self.maybe_insert_id(trait_item.id); | ||
} | ||
} | ||
|
||
// Struct constructors are public if the struct is all public. | ||
hir::ItemStruct(ref def, _) if public_first => { | ||
hir::ItemStruct(ref def, _) => { | ||
if !def.is_struct() { | ||
self.exported_items.insert(def.id()); | ||
self.maybe_insert_id(def.id()); | ||
} | ||
// fields can be public or private, so lets check | ||
for field in def.fields() { | ||
let vis = match field.node.kind { | ||
hir::NamedField(_, vis) | hir::UnnamedField(vis) => vis | ||
}; | ||
if vis == hir::Public { | ||
self.public_items.insert(field.node.id); | ||
// Struct fields can be public or private, so lets check | ||
if field.node.kind.visibility() == hir::Public { | ||
if self.prev_public { self.public_items.insert(field.node.id); } | ||
// FIXME: Make fields exported (requires fixing resulting ICEs) | ||
// if self.prev_exported { self.exported_items.insert(field.node.id); } | ||
} | ||
} | ||
} | ||
|
||
hir::ItemTy(ref ty, _) if public_first => { | ||
hir::ItemTy(ref ty, _) if self.prev_exported => { | ||
if let hir::TyPath(..) = ty.node { | ||
match self.tcx.def_map.borrow().get(&ty.id).unwrap().full_def() { | ||
def::DefPrimTy(..) | def::DefSelfTy(..) | def::DefTyParam(..) => {}, | ||
|
@@ -347,19 +363,37 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EmbargoVisitor<'a, 'tcx> { | |
} | ||
} | ||
|
||
hir::ItemForeignMod(ref foreign_mod) => { | ||
for foreign_item in &foreign_mod.items { | ||
let public = self.prev_public && foreign_item.vis == hir::Public; | ||
let exported = self.prev_exported && foreign_item.vis == hir::Public || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add parens around one of the clauses here? I always forget the precedence of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. |
||
self.reexports.contains(&foreign_item.id); | ||
|
||
if public { self.public_items.insert(foreign_item.id); } | ||
if exported { self.exported_items.insert(foreign_item.id); } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think stylistically in rustfmt it never formats There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay (although I'm a fan of one-liners for short blocks) |
||
} | ||
} | ||
|
||
_ => {} | ||
} | ||
|
||
visit::walk_item(self, item); | ||
|
||
self.prev_public = orig_all_public; | ||
self.prev_exported = orig_all_exported; | ||
self.prev_public = orig_all_pub; | ||
} | ||
|
||
fn visit_foreign_item(&mut self, a: &hir::ForeignItem) { | ||
if (self.prev_exported && a.vis == hir::Public) || self.reexports.contains(&a.id) { | ||
self.exported_items.insert(a.id); | ||
} | ||
fn visit_block(&mut self, b: &'v hir::Block) { | ||
let orig_all_public = replace(&mut self.prev_public, false); | ||
let orig_all_exported = replace(&mut self.prev_exported, false); | ||
|
||
// Blocks can have exported and public items, for example impls, but they always | ||
// start as non-public and non-exported regardless of publicity of a function, | ||
// constant, type, field, etc. in which this block resides | ||
visit::walk_block(self, b); | ||
|
||
self.prev_public = orig_all_public; | ||
self.prev_exported = orig_all_exported; | ||
} | ||
|
||
fn visit_mod(&mut self, m: &hir::Mod, _sp: Span, id: ast::NodeId) { | ||
|
@@ -375,6 +409,12 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EmbargoVisitor<'a, 'tcx> { | |
} | ||
visit::walk_mod(self, m) | ||
} | ||
|
||
fn visit_macro_def(&mut self, md: &'v hir::MacroDef) { | ||
self.public_items.insert(md.id); | ||
// FIXME: Make exported macros exported (requires fixing resulting ICEs) | ||
// self.exported_items.insert(md.id); | ||
} | ||
} | ||
|
||
//////////////////////////////////////////////////////////////////////////////// | ||
|
@@ -1447,11 +1487,13 @@ impl<'a, 'tcx, 'v> Visitor<'v> for VisiblePrivateTypesVisitor<'a, 'tcx> { | |
} | ||
} | ||
|
||
|
||
// we don't need to introspect into these at all: an | ||
// expression/block context can't possibly contain exported things. | ||
// (Making them no-ops stops us from traversing the whole AST without | ||
// having to be super careful about our `walk_...` calls above.) | ||
// FIXME: Unfortunately this ^^^ is not true, blocks can contain | ||
// exported items (e.g. impls) and actual code in rustc itself breaks | ||
// if we don't traverse blocks in `EmbargoVisitor` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not without auditing the rest of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It contains some logic assuming that it is not... very recursive. (It is even mentioned in the comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it kinda odd to document inside code why code is wrong, can you instead open an issue with an instance of this and then reference it from here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done: #29524 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add that here in the comment as well? |
||
fn visit_block(&mut self, _: &hir::Block) {} | ||
fn visit_expr(&mut self, _: &hir::Expr) {} | ||
} | ||
|
@@ -1501,9 +1543,12 @@ pub fn check_crate(tcx: &ty::ctxt, | |
prev_public: true, | ||
}; | ||
loop { | ||
let before = visitor.exported_items.len(); | ||
let before = (visitor.exported_items.len(), visitor.public_items.len(), | ||
visitor.reexports.len()); | ||
visit::walk_crate(&mut visitor, krate); | ||
if before == visitor.exported_items.len() { | ||
let after = (visitor.exported_items.len(), visitor.public_items.len(), | ||
visitor.reexports.len()); | ||
if after == before { | ||
break | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment to this and the function below explaining the tuple return value? Just saying which bool is which is fine