Check types for privacy

This commit is contained in:
Vadim Petrochenkov 2017-04-22 22:46:34 +03:00
parent cd72f2e269
commit 85fb9e0d91
11 changed files with 581 additions and 7 deletions

View File

@ -18,13 +18,13 @@
#![feature(rustc_diagnostic_macros)]
extern crate rustc;
#[macro_use] extern crate rustc;
#[macro_use] extern crate syntax;
extern crate syntax_pos;
use rustc::hir::{self, PatKind};
use rustc::hir::def::Def;
use rustc::hir::def_id::{LOCAL_CRATE, CrateNum, DefId};
use rustc::hir::def_id::{CRATE_DEF_INDEX, LOCAL_CRATE, CrateNum, DefId};
use rustc::hir::intravisit::{self, Visitor, NestedVisitorMap};
use rustc::hir::itemlikevisit::DeepVisitor;
use rustc::lint;
@ -537,6 +537,324 @@ impl<'a, 'tcx> Visitor<'tcx> for NamePrivacyVisitor<'a, 'tcx> {
}
}
////////////////////////////////////////////////////////////////////////////////////////////
/// Type privacy visitor, checks types for privacy and reports violations.
/// Both explicitly written types and inferred types of expressions and patters are checked.
/// Checks are performed on "semantic" types regardless of names and their hygiene.
////////////////////////////////////////////////////////////////////////////////////////////
struct TypePrivacyVisitor<'a, 'tcx: 'a> {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
tables: &'a ty::TypeckTables<'tcx>,
current_item: DefId,
span: Span,
}
impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> {
fn def_id_visibility(&self, did: DefId) -> ty::Visibility {
match self.tcx.hir.as_local_node_id(did) {
Some(node_id) => {
let vis = match self.tcx.hir.get(node_id) {
hir::map::NodeItem(item) => &item.vis,
hir::map::NodeForeignItem(foreign_item) => &foreign_item.vis,
hir::map::NodeImplItem(impl_item) => &impl_item.vis,
hir::map::NodeTraitItem(..) |
hir::map::NodeVariant(..) => {
return self.def_id_visibility(self.tcx.hir.get_parent_did(node_id));
}
hir::map::NodeStructCtor(vdata) => {
let struct_node_id = self.tcx.hir.get_parent(node_id);
let struct_vis = match self.tcx.hir.get(struct_node_id) {
hir::map::NodeItem(item) => &item.vis,
node => bug!("unexpected node kind: {:?}", node),
};
let mut ctor_vis
= ty::Visibility::from_hir(struct_vis, struct_node_id, self.tcx);
for field in vdata.fields() {
let field_vis = ty::Visibility::from_hir(&field.vis, node_id, self.tcx);
if ctor_vis.is_at_least(field_vis, self.tcx) {
ctor_vis = field_vis;
}
}
return ctor_vis;
}
node => bug!("unexpected node kind: {:?}", node)
};
ty::Visibility::from_hir(vis, node_id, self.tcx)
}
None => self.tcx.sess.cstore.visibility(did),
}
}
fn item_is_accessible(&self, did: DefId) -> bool {
self.def_id_visibility(did).is_accessible_from(self.current_item, self.tcx)
}
fn check_expr_pat_type(&mut self, id: ast::NodeId, span: Span) -> bool {
if let Some(ty) = self.tables.node_id_to_type_opt(id) {
self.span = span;
ty.visit_with(self)
} else {
false
}
}
fn check_item(&mut self, item_id: ast::NodeId) -> &mut Self {
self.current_item = self.tcx.hir.local_def_id(item_id);
self.span = self.tcx.hir.span(item_id);
self
}
// Convenience methods for checking item interfaces
fn ty(&mut self) -> &mut Self {
self.tcx.type_of(self.current_item).visit_with(self);
self
}
fn generics(&mut self) -> &mut Self {
for def in &self.tcx.generics_of(self.current_item).types {
if def.has_default {
self.tcx.type_of(def.def_id).visit_with(self);
}
}
self
}
fn predicates(&mut self) -> &mut Self {
self.tcx.predicates_of(self.current_item).visit_with(self);
self
}
fn impl_trait_ref(&mut self) -> &mut Self {
self.tcx.impl_trait_ref(self.current_item).visit_with(self);
self
}
}
impl<'a, 'tcx> Visitor<'tcx> for TypePrivacyVisitor<'a, 'tcx> {
/// We want to visit items in the context of their containing
/// module and so forth, so supply a crate for doing a deep walk.
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::All(&self.tcx.hir)
}
fn visit_nested_body(&mut self, body: hir::BodyId) {
let orig_tables = replace(&mut self.tables, self.tcx.body_tables(body));
let body = self.tcx.hir.body(body);
self.visit_body(body);
self.tables = orig_tables;
}
// Check types of expressions
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
if self.check_expr_pat_type(expr.id, expr.span) {
// Do not check nested expressions if the error already happened.
return;
}
match expr.node {
hir::ExprAssign(.., ref rhs) | hir::ExprMatch(ref rhs, ..) => {
// Do not report duplicate errors for `x = y` and `match x { ... }`.
if self.check_expr_pat_type(rhs.id, rhs.span) {
return;
}
}
hir::ExprMethodCall(name, ..) => {
// Method calls have to be checked specially.
if let Some(method) = self.tables.method_map.get(&ty::MethodCall::expr(expr.id)) {
self.span = name.span;
if method.ty.visit_with(self) || method.substs.visit_with(self) {
return;
}
}
}
_ => {}
}
intravisit::walk_expr(self, expr);
}
// Check types of patterns
fn visit_pat(&mut self, pattern: &'tcx hir::Pat) {
if self.check_expr_pat_type(pattern.id, pattern.span) {
// Do not check nested patterns if the error already happened.
return;
}
intravisit::walk_pat(self, pattern);
}
fn visit_local(&mut self, local: &'tcx hir::Local) {
if let Some(ref init) = local.init {
if self.check_expr_pat_type(init.id, init.span) {
// Do not report duplicate errors for `let x = y`.
return;
}
}
intravisit::walk_local(self, local);
}
// Check types in item interfaces
fn visit_item(&mut self, item: &'tcx hir::Item) {
let orig_current_item = self.current_item;
match item.node {
hir::ItemExternCrate(..) | hir::ItemMod(..) |
hir::ItemUse(..) | hir::ItemGlobalAsm(..) => {}
hir::ItemConst(..) | hir::ItemStatic(..) |
hir::ItemTy(..) | hir::ItemFn(..) => {
self.check_item(item.id).generics().predicates().ty();
}
hir::ItemTrait(.., ref trait_item_refs) => {
self.check_item(item.id).generics().predicates();
for trait_item_ref in trait_item_refs {
let mut check = self.check_item(trait_item_ref.id.node_id);
check.generics().predicates();
if trait_item_ref.kind != hir::AssociatedItemKind::Type ||
trait_item_ref.defaultness.has_value() {
check.ty();
}
}
}
hir::ItemEnum(ref def, _) => {
self.check_item(item.id).generics().predicates();
for variant in &def.variants {
for field in variant.node.data.fields() {
self.check_item(field.id).ty();
}
}
}
hir::ItemForeignMod(ref foreign_mod) => {
for foreign_item in &foreign_mod.items {
self.check_item(foreign_item.id).generics().predicates().ty();
}
}
hir::ItemStruct(ref struct_def, _) |
hir::ItemUnion(ref struct_def, _) => {
self.check_item(item.id).generics().predicates();
for field in struct_def.fields() {
self.check_item(field.id).ty();
}
}
hir::ItemDefaultImpl(..) => {
self.check_item(item.id).impl_trait_ref();
}
hir::ItemImpl(.., ref trait_ref, _, ref impl_item_refs) => {
{
let mut check = self.check_item(item.id);
check.ty().generics().predicates();
if trait_ref.is_some() {
check.impl_trait_ref();
}
}
for impl_item_ref in impl_item_refs {
let impl_item = self.tcx.hir.impl_item(impl_item_ref.id);
self.check_item(impl_item.id).generics().predicates().ty();
}
}
}
self.current_item = self.tcx.hir.local_def_id(item.id);
intravisit::walk_item(self, item);
self.current_item = orig_current_item;
}
}
impl<'a, 'tcx> TypeVisitor<'tcx> for TypePrivacyVisitor<'a, 'tcx> {
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
match ty.sty {
ty::TyAdt(&ty::AdtDef { did: def_id, .. }, ..) | ty::TyFnDef(def_id, ..) => {
if !self.item_is_accessible(def_id) {
let msg = format!("type `{}` is private", ty);
self.tcx.sess.span_err(self.span, &msg);
return true;
}
if let ty::TyFnDef(..) = ty.sty {
// Inherent static methods don't have self type in substs,
// we have to check it additionally.
let mut impl_def_id = None;
if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
if let hir::map::NodeImplItem(..) = self.tcx.hir.get(node_id) {
impl_def_id = Some(self.tcx.hir.get_parent_did(node_id));
}
} else if let Some(Def::Method(..)) = self.tcx.describe_def(def_id) {
let candidate_impl_def_id = self.tcx.parent_def_id(def_id)
.expect("no parent for method def_id");
// `is_none` means it's an impl, not a trait
if self.tcx.describe_def(candidate_impl_def_id).is_none() {
impl_def_id = Some(candidate_impl_def_id)
}
}
if let Some(impl_def_id) = impl_def_id {
let self_ty = self.tcx.type_of(impl_def_id);
if self_ty.visit_with(self) {
return true;
}
}
}
}
ty::TyDynamic(ref predicates, ..) => {
let is_private = predicates.skip_binder().iter().any(|predicate| {
let def_id = match *predicate {
ty::ExistentialPredicate::Trait(trait_ref) => trait_ref.def_id,
ty::ExistentialPredicate::Projection(proj) => proj.trait_ref.def_id,
ty::ExistentialPredicate::AutoTrait(def_id) => def_id,
};
!self.item_is_accessible(def_id)
});
if is_private {
let msg = format!("type `{}` is private", ty);
self.tcx.sess.span_err(self.span, &msg);
return true;
}
}
ty::TyAnon(def_id, ..) => {
for predicate in &self.tcx.predicates_of(def_id).predicates {
let trait_ref = match *predicate {
ty::Predicate::Trait(ref poly_trait_predicate) => {
Some(poly_trait_predicate.skip_binder().trait_ref)
}
ty::Predicate::Projection(ref poly_projection_predicate) => {
if poly_projection_predicate.skip_binder().ty.visit_with(self) {
return true;
}
Some(poly_projection_predicate.skip_binder().projection_ty.trait_ref)
}
ty::Predicate::TypeOutlives(..) => None,
_ => bug!("unexpected predicate: {:?}", predicate),
};
if let Some(trait_ref) = trait_ref {
if !self.item_is_accessible(trait_ref.def_id) {
let msg = format!("trait `{}` is private", trait_ref);
self.tcx.sess.span_err(self.span, &msg);
return true;
}
// Skip `Self` to avoid infinite recursion
for subst in trait_ref.substs.iter().skip(1) {
if subst.visit_with(self) {
return true;
}
}
}
}
}
_ => {}
}
ty.super_visit_with(self)
}
fn visit_trait_ref(&mut self, trait_ref: ty::TraitRef<'tcx>) -> bool {
if !self.item_is_accessible(trait_ref.def_id) {
let msg = format!("trait `{}` is private", trait_ref);
self.tcx.sess.span_err(self.span, &msg);
return true;
}
trait_ref.super_visit_with(self)
}
}
///////////////////////////////////////////////////////////////////////////////
/// Obsolete visitors for checking for private items in public interfaces.
/// These visitors are supposed to be kept in frozen state and produce an
@ -1225,6 +1543,16 @@ fn privacy_access_levels<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
};
intravisit::walk_crate(&mut visitor, krate);
// Check privacy of explicitly written types and traits as well as
// inferred types of expressions and patterns.
let mut visitor = TypePrivacyVisitor {
tcx: tcx,
tables: &ty::TypeckTables::empty(),
current_item: DefId::local(CRATE_DEF_INDEX),
span: krate.span,
};
intravisit::walk_crate(&mut visitor, krate);
// Build up a set of all exported items in the AST. This is a set of all
// items which are reachable from external crates based on visibility.
let mut visitor = EmbargoVisitor {

View File

@ -0,0 +1,41 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(decl_macro)]
fn priv_fn() {}
enum PrivEnum { Variant }
pub enum PubEnum { Variant }
trait PrivTrait { fn method() {} }
impl PrivTrait for u8 {}
pub trait PubTrait { fn method() {} }
impl PubTrait for u8 {}
struct PrivTupleStruct(u8);
pub struct PubTupleStruct(u8);
impl PubTupleStruct { fn method() {} }
struct Priv;
pub type Alias = Priv;
pub struct Pub<T = Alias>(pub T);
impl Pub<Priv> {
pub fn static_method() {}
}
pub macro m() {
priv_fn;
PrivEnum::Variant;
PubEnum::Variant;
<u8 as PrivTrait>::method;
<u8 as PubTrait>::method;
PrivTupleStruct;
PubTupleStruct;
Pub::static_method;
}

View File

@ -22,11 +22,11 @@ mod foo {
x: i32,
}
let s = S { x: 0 };
let _ = s.x;
let s = S { x: 0 }; //~ ERROR type `foo::S` is private
let _ = s.x; //~ ERROR type `foo::S` is private
let t = T(0);
let _ = t.0;
let t = T(0); //~ ERROR type `foo::T` is private
let _ = t.0; //~ ERROR type `foo::T` is private
let s = $S { $x: 0, x: 1 };
assert_eq!((s.$x, s.x), (0, 1));

View File

@ -19,7 +19,7 @@ mod foo {
}
pub macro m() {
let _: () = S.f();
let _: () = S.f(); //~ ERROR type `fn(&foo::S) {foo::S::f}` is private
}
}

View File

@ -12,6 +12,8 @@
// aux-build:intercrate.rs
// error-pattern:type `fn() -> u32 {intercrate::foo::bar::f}` is private
#![feature(decl_macro)]
extern crate intercrate;

View File

@ -0,0 +1,28 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
trait Arr0 {
fn arr0_secret(&self);
}
trait TyParam {
fn ty_param_secret(&self);
}
mod m {
struct Priv;
impl ::Arr0 for [Priv; 0] { fn arr0_secret(&self) {} }
impl ::TyParam for Option<Priv> { fn ty_param_secret(&self) {} }
}
fn main() {
[].arr0_secret(); //~ ERROR type `m::Priv` is private
None.ty_param_secret(); //~ ERROR type `m::Priv` is private
}

View File

@ -0,0 +1,29 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:private-inferred-type.rs
extern crate private_inferred_type as ext;
mod m {
struct Priv;
pub struct Pub<T>(pub T);
impl Pub<Priv> {
pub fn get_priv() -> Priv { Priv }
pub fn static_method() {}
}
}
fn main() {
m::Pub::get_priv; //~ ERROR type `m::Priv` is private
m::Pub::static_method; //~ ERROR type `m::Priv` is private
ext::Pub::static_method; //~ ERROR type `ext::Priv` is private
}

View File

@ -0,0 +1,26 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:private-inferred-type.rs
// error-pattern:type `fn() {ext::priv_fn}` is private
// error-pattern:type `ext::PrivEnum` is private
// error-pattern:type `fn() {<u8 as ext::PrivTrait>::method}` is private
// error-pattern:type `fn(u8) -> ext::PrivTupleStruct {ext::PrivTupleStruct::{{constructor}}}` is pr
// error-pattern:type `fn(u8) -> ext::PubTupleStruct {ext::PubTupleStruct::{{constructor}}}` is priv
// error-pattern:type `ext::Priv` is private
#![feature(decl_macro)]
extern crate private_inferred_type as ext;
fn main() {
ext::m!();
}

View File

@ -0,0 +1,83 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(conservative_impl_trait)]
#![feature(decl_macro)]
mod m {
fn priv_fn() {}
enum PrivEnum { Variant }
pub enum PubEnum { Variant }
trait PrivTrait { fn method() {} }
impl PrivTrait for u8 {}
pub trait PubTrait { fn method() {} }
impl PubTrait for u8 {}
struct PrivTupleStruct(u8);
pub struct PubTupleStruct(u8);
impl PubTupleStruct { fn method() {} }
struct Priv;
pub type Alias = Priv;
pub struct Pub<T = Alias>(pub T);
impl Pub<Priv> {
pub fn static_method() {}
}
pub macro m() {
priv_fn; //~ ERROR type `fn() {m::priv_fn}` is private
PrivEnum::Variant; //~ ERROR type `m::PrivEnum` is private
PubEnum::Variant; // OK
<u8 as PrivTrait>::method; //~ ERROR type `fn() {<u8 as m::PrivTrait>::method}` is private
<u8 as PubTrait>::method; // OK
PrivTupleStruct;
//~^ ERROR type `fn(u8) -> m::PrivTupleStruct {m::PrivTupleStruct::{{constructor}}}` is priv
PubTupleStruct;
//~^ ERROR type `fn(u8) -> m::PubTupleStruct {m::PubTupleStruct::{{constructor}}}` is privat
Pub::static_method; //~ ERROR type `m::Priv` is private
}
trait Trait {}
pub trait TraitWithTyParam<T> {}
pub trait TraitWithAssocTy { type X; }
impl Trait for u8 {}
impl<T> TraitWithTyParam<T> for u8 {}
impl TraitWithAssocTy for u8 { type X = Priv; }
pub fn leak_anon1() -> impl Trait + 'static { 0 }
pub fn leak_anon2() -> impl TraitWithTyParam<Alias> { 0 }
pub fn leak_anon3() -> impl TraitWithAssocTy<X = Alias> { 0 }
pub fn leak_dyn1() -> Box<Trait + 'static> { Box::new(0) }
pub fn leak_dyn2() -> Box<TraitWithTyParam<Alias>> { Box::new(0) }
pub fn leak_dyn3() -> Box<TraitWithAssocTy<X = Alias>> { Box::new(0) }
}
fn main() {
m::Alias {}; //~ ERROR type `m::Priv` is private
m::Pub { 0: m::Alias {} }; //~ ERROR type `m::Priv` is private
m::m!();
m::leak_anon1(); //~ ERROR trait `m::Trait` is private
m::leak_anon2(); //~ ERROR type `m::Priv` is private
m::leak_anon3(); //~ ERROR type `m::Priv` is private
m::leak_dyn1(); //~ ERROR type `m::Trait + 'static` is private
m::leak_dyn2(); //~ ERROR type `m::Priv` is private
m::leak_dyn3(); //~ ERROR type `m::Priv` is private
// Check that messages are not duplicated for various kinds of assignments
let a = m::Alias {}; //~ ERROR type `m::Priv` is private
let mut b = a; //~ ERROR type `m::Priv` is private
b = a; //~ ERROR type `m::Priv` is private
match a { //~ ERROR type `m::Priv` is private
_ => {}
}
}

View File

@ -0,0 +1,37 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:private-inferred-type.rs
#![feature(conservative_impl_trait)]
extern crate private_inferred_type as ext;
mod m {
struct Priv;
pub type Alias = Priv;
}
fn f(_: m::Alias) {} //~ ERROR type `m::Priv` is private
//~^ ERROR type `m::Priv` is private
fn f_ext(_: ext::Alias) {} //~ ERROR type `ext::Priv` is private
//~^ ERROR type `ext::Priv` is private
trait Tr1 {}
impl m::Alias {} //~ ERROR type `m::Priv` is private
impl Tr1 for ext::Alias {} //~ ERROR type `ext::Priv` is private
//~^ ERROR type `ext::Priv` is private
trait Tr2<T> {}
impl<T> Tr2<T> for u8 {}
fn g() -> impl Tr2<m::Alias> { 0 } //~ ERROR type `m::Priv` is private
fn g_ext() -> impl Tr2<ext::Alias> { 0 } //~ ERROR type `ext::Priv` is private
fn main() {}