Skip to content

Commit 8ae8bc2

Browse files
committed
Fix various issues with making items reachable through macros
* Allow items to be accessible through private modules and fields when a macro can access them. * Don't mark type-private items as reachable. * Never make items exported/public via macros
1 parent d727071 commit 8ae8bc2

File tree

9 files changed

+276
-31
lines changed

9 files changed

+276
-31
lines changed

src/librustc_privacy/lib.rs

Lines changed: 159 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,13 @@ fn def_id_visibility<'tcx>(
229229
let vis = match tcx.hir().get(hir_id) {
230230
Node::Item(item) => &item.vis,
231231
Node::ForeignItem(foreign_item) => &foreign_item.vis,
232+
Node::MacroDef(macro_def) => {
233+
if attr::contains_name(&macro_def.attrs, sym::macro_export) {
234+
return (ty::Visibility::Public, macro_def.span, "public");
235+
} else {
236+
&macro_def.vis
237+
}
238+
},
232239
Node::TraitItem(..) | Node::Variant(..) => {
233240
return def_id_visibility(tcx, tcx.hir().get_parent_did(hir_id));
234241
}
@@ -433,11 +440,24 @@ impl VisibilityLike for Option<AccessLevel> {
433440
struct EmbargoVisitor<'tcx> {
434441
tcx: TyCtxt<'tcx>,
435442

436-
// Accessibility levels for reachable nodes.
443+
/// Accessibility levels for reachable nodes.
437444
access_levels: AccessLevels,
438-
// Previous accessibility level; `None` means unreachable.
445+
/// A set of pairs corresponding to modules, where the first module is
446+
/// reachable via a macro that's defined in the second module. This cannot
447+
/// be represented as reachable because it can't handle the following case:
448+
///
449+
/// pub mod n { // Should be `Public`
450+
/// pub(crate) mod p { // Should *not* be accessible
451+
/// pub fn f() -> i32 { 12 } // Must be `Reachable`
452+
/// }
453+
/// }
454+
/// pub macro m() {
455+
/// n::p::f()
456+
/// }
457+
macro_reachable: FxHashSet<(hir::HirId, DefId)>,
458+
/// Previous accessibility level; `None` means unreachable.
439459
prev_level: Option<AccessLevel>,
440-
// Has something changed in the level map?
460+
/// Has something changed in the level map?
441461
changed: bool,
442462
}
443463

@@ -452,7 +472,7 @@ impl EmbargoVisitor<'tcx> {
452472
self.access_levels.map.get(&id).cloned()
453473
}
454474

455-
// Updates node level and returns the updated level.
475+
/// Updates node level and returns the updated level.
456476
fn update(&mut self, id: hir::HirId, level: Option<AccessLevel>) -> Option<AccessLevel> {
457477
let old_level = self.get(id);
458478
// Accessibility levels can only grow.
@@ -477,6 +497,127 @@ impl EmbargoVisitor<'tcx> {
477497
}
478498
}
479499

500+
/// Updates the item as being reachable through a macro defined in the given
501+
/// module. Returns `true` if the level has changed.
502+
fn update_macro_reachable(&mut self, reachable_mod: hir::HirId, defining_mod: DefId) -> bool {
503+
if self.macro_reachable.insert((reachable_mod, defining_mod)) {
504+
self.update_macro_reachable_mod(reachable_mod, defining_mod);
505+
true
506+
} else {
507+
false
508+
}
509+
}
510+
511+
fn update_macro_reachable_mod(
512+
&mut self,
513+
reachable_mod: hir::HirId,
514+
defining_mod: DefId,
515+
) {
516+
let module_def_id = self.tcx.hir().local_def_id(reachable_mod);
517+
let module = self.tcx.hir().get_module(module_def_id).0;
518+
for item_id in &module.item_ids {
519+
let hir_id = item_id.id;
520+
let item_def_id = self.tcx.hir().local_def_id(hir_id);
521+
if let Some(def_kind) = self.tcx.def_kind(item_def_id) {
522+
let item = self.tcx.hir().expect_item(hir_id);
523+
let vis = ty::Visibility::from_hir(&item.vis, hir_id, self.tcx);
524+
self.update_macro_reachable_def(hir_id, def_kind, vis, defining_mod);
525+
}
526+
}
527+
528+
if let Some(exports) = self.tcx.module_exports(module_def_id) {
529+
for export in exports {
530+
if export.vis.is_accessible_from(defining_mod, self.tcx) {
531+
if let Res::Def(def_kind, def_id) = export.res {
532+
let vis = def_id_visibility(self.tcx, def_id).0;
533+
if let Some(hir_id) = self.tcx.hir().as_local_hir_id(def_id) {
534+
self.update_macro_reachable_def(
535+
hir_id,
536+
def_kind,
537+
vis,
538+
defining_mod,
539+
);
540+
}
541+
}
542+
}
543+
}
544+
}
545+
}
546+
547+
fn update_macro_reachable_def(
548+
&mut self,
549+
hir_id: hir::HirId,
550+
def_kind: DefKind,
551+
vis: ty::Visibility,
552+
module: DefId,
553+
) {
554+
let level = Some(AccessLevel::Reachable);
555+
if let ty::Visibility::Public = vis {
556+
self.update(hir_id, level);
557+
}
558+
match def_kind {
559+
// No type privacy, so can be directly marked as reachable.
560+
DefKind::Const
561+
| DefKind::Macro(_)
562+
| DefKind::Static
563+
| DefKind::TraitAlias
564+
| DefKind::TyAlias => {
565+
if vis.is_accessible_from(module, self.tcx) {
566+
self.update(hir_id, level);
567+
}
568+
},
569+
570+
// We can't use a module name as the final segment of a path, except
571+
// in use statements. Since re-export checking doesn't consider
572+
// hygiene these don't need to be marked reachable. The contents of
573+
// the module, however may be reachable.
574+
DefKind::Mod => {
575+
if vis.is_accessible_from(module, self.tcx) {
576+
self.update_macro_reachable(hir_id, module);
577+
}
578+
}
579+
580+
DefKind::Struct | DefKind::Union => {
581+
// While structs and unions have type privacy, their fields do
582+
// not.
583+
if let ty::Visibility::Public = vis {
584+
let item = self.tcx.hir().expect_item(hir_id);
585+
if let hir::ItemKind::Struct(ref struct_def, _)
586+
| hir::ItemKind::Union(ref struct_def, _) = item.node
587+
{
588+
for field in struct_def.fields() {
589+
let field_vis = ty::Visibility::from_hir(
590+
&field.vis,
591+
field.hir_id,
592+
self.tcx,
593+
);
594+
if field_vis.is_accessible_from(module, self.tcx) {
595+
self.reach(field.hir_id, level).ty();
596+
}
597+
}
598+
} else {
599+
bug!("item {:?} with DefKind {:?}", item, def_kind);
600+
}
601+
}
602+
}
603+
604+
// These have type privacy, so are not reachable unless they're
605+
// public
606+
DefKind::AssocConst
607+
| DefKind::AssocTy
608+
| DefKind::AssocOpaqueTy
609+
| DefKind::ConstParam
610+
| DefKind::Ctor(_, _)
611+
| DefKind::Enum
612+
| DefKind::ForeignTy
613+
| DefKind::Fn
614+
| DefKind::OpaqueTy
615+
| DefKind::Method
616+
| DefKind::Trait
617+
| DefKind::TyParam
618+
| DefKind::Variant => (),
619+
}
620+
}
480621

481622
/// Given the path segments of a `ItemKind::Use`, then we need
482623
/// to update the visibility of the intermediate use so that it isn't linted
@@ -746,40 +887,21 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
746887
return
747888
}
748889

749-
let module_did = ty::DefIdTree::parent(
890+
let macro_module_def_id = ty::DefIdTree::parent(
750891
self.tcx,
751892
self.tcx.hir().local_def_id(md.hir_id)
752893
).unwrap();
753-
let mut module_id = self.tcx.hir().as_local_hir_id(module_did).unwrap();
894+
let mut module_id = self.tcx.hir().as_local_hir_id(macro_module_def_id).unwrap();
754895
let level = if md.vis.node.is_pub() { self.get(module_id) } else { None };
755-
let level = self.update(md.hir_id, level);
756-
if level.is_none() {
896+
let new_level = self.update(md.hir_id, level);
897+
if new_level.is_none() {
757898
return
758899
}
759900

760901
loop {
761-
let module = if module_id == hir::CRATE_HIR_ID {
762-
&self.tcx.hir().krate().module
763-
} else if let hir::ItemKind::Mod(ref module) =
764-
self.tcx.hir().expect_item(module_id).node {
765-
module
766-
} else {
767-
unreachable!()
768-
};
769-
for id in &module.item_ids {
770-
self.update(id.id, level);
771-
}
772-
let def_id = self.tcx.hir().local_def_id(module_id);
773-
if let Some(exports) = self.tcx.module_exports(def_id) {
774-
for export in exports.iter() {
775-
if let Some(hir_id) = self.tcx.hir().as_local_hir_id(export.res.def_id()) {
776-
self.update(hir_id, level);
777-
}
778-
}
779-
}
780-
781-
if module_id == hir::CRATE_HIR_ID {
782-
break
902+
let changed_reachability = self.update_macro_reachable(module_id, macro_module_def_id);
903+
if changed_reachability || module_id == hir::CRATE_HIR_ID {
904+
break;
783905
}
784906
module_id = self.tcx.hir().get_parent_node(module_id);
785907
}
@@ -826,7 +948,12 @@ impl DefIdVisitor<'tcx> for ReachEverythingInTheInterfaceVisitor<'_, 'tcx> {
826948
fn tcx(&self) -> TyCtxt<'tcx> { self.ev.tcx }
827949
fn visit_def_id(&mut self, def_id: DefId, _kind: &str, _descr: &dyn fmt::Display) -> bool {
828950
if let Some(hir_id) = self.ev.tcx.hir().as_local_hir_id(def_id) {
829-
self.ev.update(hir_id, self.access_level);
951+
if let ((ty::Visibility::Public, ..), _)
952+
| (_, Some(AccessLevel::ReachableFromImplTrait))
953+
= (def_id_visibility(self.tcx(), def_id), self.access_level)
954+
{
955+
self.ev.update(hir_id, self.access_level);
956+
}
830957
}
831958
false
832959
}
@@ -1860,6 +1987,7 @@ fn privacy_access_levels(tcx: TyCtxt<'_>, krate: CrateNum) -> &AccessLevels {
18601987
let mut visitor = EmbargoVisitor {
18611988
tcx,
18621989
access_levels: Default::default(),
1990+
macro_reachable: Default::default(),
18631991
prev_level: Some(AccessLevel::Public),
18641992
changed: false,
18651993
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![feature(decl_macro)]
2+
3+
mod n {
4+
pub struct B(pub(crate) p::C);
5+
impl B {
6+
pub fn new() -> Self {
7+
B(p::C)
8+
}
9+
}
10+
mod p {
11+
pub struct C;
12+
13+
impl C {
14+
pub fn foo(&self) -> i32 {
15+
33
16+
}
17+
}
18+
}
19+
}
20+
21+
pub macro m() {
22+
n::B::new().0.foo()
23+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![feature(decl_macro)]
2+
3+
mod n {
4+
pub(crate) mod p {
5+
pub fn f() -> i32 { 12 }
6+
}
7+
}
8+
9+
pub macro m() {
10+
n::p::f()
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#![feature(decl_macro)]
2+
3+
mod n {
4+
pub static S: i32 = 57;
5+
}
6+
7+
use n::S;
8+
9+
pub macro m() {
10+
S
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Check that functions accessible through a field visible to a macro are
2+
// considered reachable
3+
4+
// aux-build:nested-fn-macro.rs
5+
// run-pass
6+
7+
extern crate nested_fn_macro;
8+
9+
fn main() {
10+
assert_eq!(nested_fn_macro::m!(), 12);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Check that functions visible to macros through paths with >2 segements are
2+
// considered reachable
3+
4+
// aux-build:field-method-macro.rs
5+
// run-pass
6+
7+
extern crate field_method_macro;
8+
9+
fn main() {
10+
assert_eq!(field_method_macro::m!(), 33);
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Check that we don't require stability annotations for private modules,
2+
// imports and fields that are accessible to opaque macros.
3+
4+
// check-pass
5+
6+
#![feature(decl_macro, staged_api)]
7+
#![stable(feature = "test", since = "1.0.0")]
8+
9+
extern crate std as local_std;
10+
use local_std::marker::Copy as LocalCopy;
11+
mod private_mod {
12+
#[stable(feature = "test", since = "1.0.0")]
13+
pub struct A {
14+
pub(crate) f: i32,
15+
}
16+
}
17+
18+
#[stable(feature = "test", since = "1.0.0")]
19+
pub macro m() {}
20+
21+
fn main() {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Check that type privacy is taken into account when considering reachability
2+
3+
// check-pass
4+
5+
#![feature(decl_macro, staged_api)]
6+
#![stable(feature = "test", since = "1.0.0")]
7+
8+
// Type privacy should prevent use of these in other crates, so we shouldn't
9+
// need a stability annotation.
10+
fn private_function() {}
11+
struct PrivateStruct { f: () }
12+
enum PrivateEnum { V }
13+
union PrivateUnion { g: () }
14+
trait PrivateTrait {}
15+
16+
#[stable(feature = "test", since = "1.0.0")]
17+
pub macro m() {}
18+
19+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Check that private use statements can be used by
2+
3+
// run-pass
4+
// aux-build:private-use-macro.rs
5+
6+
extern crate private_use_macro;
7+
8+
fn main() {
9+
assert_eq!(private_use_macro::m!(), 57);
10+
}

0 commit comments

Comments
 (0)