1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // A pass that checks to make sure private fields and methods aren't used
12 // outside their scopes.
14 use metadata::csearch;
15 use middle::ty::{ty_struct, ty_enum};
17 use middle::typeck::{method_map, method_origin, method_param, method_self};
18 use middle::typeck::{method_super};
19 use middle::typeck::{method_static, method_trait};
21 use core::util::ignore;
22 use syntax::ast::{decl_item, def, def_fn, def_id, def_static_method};
23 use syntax::ast::{def_variant, expr_field, expr_method_call, expr_path};
24 use syntax::ast::{expr_struct, expr_unary, ident, inherited, item_enum};
25 use syntax::ast::{item_foreign_mod, item_fn, item_impl, item_struct};
26 use syntax::ast::{item_trait, local_crate, node_id, pat_struct, Path};
27 use syntax::ast::{private, provided, public, required, stmt_decl, visibility};
29 use syntax::ast_map::{node_foreign_item, node_item, node_method};
30 use syntax::ast_map::{node_trait_method};
32 use syntax::ast_util::{Private, Public, is_local};
33 use syntax::ast_util::{variant_visibility_to_privacy, visibility_to_privacy};
35 use syntax::codemap::span;
38 pub fn check_crate(tcx: ty::ctxt,
39 method_map: &method_map,
41 let privileged_items = @mut ~[];
43 // Adds an item to its scope.
44 let add_privileged_item: @fn(@ast::item, &mut uint) = |item, count| {
46 item_struct(*) | item_trait(*) | item_enum(*) |
48 privileged_items.push(item.id);
51 item_impl(_, _, _, ref methods) => {
52 for methods.each |method| {
53 privileged_items.push(method.id);
56 privileged_items.push(item.id);
59 item_foreign_mod(ref foreign_mod) => {
60 for foreign_mod.items.each |foreign_item| {
61 privileged_items.push(foreign_item.id);
69 // Adds items that are privileged to this scope.
70 let add_privileged_items: @fn(&[@ast::item]) -> uint = |items| {
72 for items.each |&item| {
73 add_privileged_item(item, &mut count);
78 // Checks that an enum variant is in scope
79 let check_variant: @fn(span: span, enum_id: ast::def_id) =
81 let variant_info = ty::enum_variants(tcx, enum_id)[0];
82 let parental_privacy = if is_local(enum_id) {
83 let parent_vis = ast_map::node_item_query(tcx.items, enum_id.node,
85 ~"unbound enum parent when checking \
86 dereference of enum type");
87 visibility_to_privacy(parent_vis)
93 debug!("parental_privacy = %?", parental_privacy);
94 debug!("vis = %?, priv = %?",
96 visibility_to_privacy(variant_info.vis))
97 // inherited => privacy of the enum item
98 if variant_visibility_to_privacy(variant_info.vis,
99 parental_privacy == Public)
101 tcx.sess.span_err(span,
102 ~"can only dereference enums \
103 with a single, public variant");
107 // Returns the ID of the container (impl or trait) that a crate-local
108 // method belongs to.
109 let local_method_container_id:
110 @fn(span: span, method_id: node_id) -> def_id =
112 match tcx.items.find(&method_id) {
113 Some(&node_method(_, impl_id, _)) => impl_id,
114 Some(&node_trait_method(_, trait_id, _)) => trait_id,
116 tcx.sess.span_bug(span,
117 fmt!("method was a %s?!",
118 ast_map::node_id_to_str(
121 tcx.sess.parse_sess.interner)));
124 tcx.sess.span_bug(span, ~"method not found in \
130 // Returns true if a crate-local method is private and false otherwise.
131 let method_is_private: @fn(span: span, method_id: node_id) -> bool =
133 let check = |vis: visibility, container_id: def_id| {
134 let mut is_private = false;
137 } else if vis == public {
140 // Look up the enclosing impl.
141 if container_id.crate != local_crate {
142 tcx.sess.span_bug(span,
143 ~"local method isn't in local \
147 match tcx.items.find(&container_id.node) {
148 Some(&node_item(item, _)) => {
150 item_impl(_, None, _, _)
151 if item.vis != public => {
158 tcx.sess.span_bug(span, ~"impl wasn't an item?!");
161 tcx.sess.span_bug(span, ~"impl wasn't in AST map?!");
169 match tcx.items.find(&method_id) {
170 Some(&node_method(method, impl_id, _)) => {
171 check(method.vis, impl_id)
173 Some(&node_trait_method(trait_method, trait_id, _)) => {
174 match *trait_method {
175 required(_) => check(public, trait_id),
176 provided(method) => check(method.vis, trait_id),
180 tcx.sess.span_bug(span,
181 fmt!("method_is_private: method was a %s?!",
182 ast_map::node_id_to_str(
185 tcx.sess.parse_sess.interner)));
188 tcx.sess.span_bug(span, ~"method not found in \
194 // Returns true if the given local item is private and false otherwise.
195 let local_item_is_private: @fn(span: span, item_id: node_id) -> bool =
197 let mut f: &fn(node_id) -> bool = |_| false;
199 match tcx.items.find(&item_id) {
200 Some(&node_item(item, _)) => item.vis != public,
201 Some(&node_foreign_item(_, _, vis, _)) => vis != public,
202 Some(&node_method(method, impl_did, _)) => {
206 inherited => f(impl_did.node)
209 Some(&node_trait_method(_, trait_did, _)) => f(trait_did.node),
211 tcx.sess.span_bug(span,
212 fmt!("local_item_is_private: item was \
214 ast_map::node_id_to_str(
222 tcx.sess.span_bug(span, ~"item not found in AST map?!");
229 // Checks that a private field is in scope.
230 let check_field: @fn(span: span, id: ast::def_id, ident: ast::ident) =
232 let fields = ty::lookup_struct_fields(tcx, id);
233 for fields.each |field| {
234 if field.ident != ident { loop; }
235 if field.vis == private {
236 tcx.sess.span_err(span, fmt!("field `%s` is private",
237 *tcx.sess.parse_sess.interner
244 // Given the ID of a method, checks to ensure it's in scope.
245 let check_method_common: @fn(span: span,
248 |span, method_id, name| {
249 if method_id.crate == local_crate {
250 let is_private = method_is_private(span, method_id.node);
251 let container_id = local_method_container_id(span,
254 (container_id.crate != local_crate ||
255 !privileged_items.contains(&(container_id.node))) {
256 tcx.sess.span_err(span,
257 fmt!("method `%s` is private",
265 csearch::get_item_visibility(tcx.sess.cstore, method_id);
266 if visibility != public {
267 tcx.sess.span_err(span,
268 fmt!("method `%s` is private",
269 *tcx.sess.parse_sess.interner
275 // Checks that a private path is in scope.
276 let check_path: @fn(span: span, def: def, path: @Path) =
278 debug!("checking path");
280 def_static_method(method_id, _, _) => {
281 debug!("found static method def, checking it");
282 check_method_common(span, method_id, path.idents.last())
284 def_fn(def_id, _) => {
285 if def_id.crate == local_crate {
286 if local_item_is_private(span, def_id.node) &&
287 !privileged_items.contains(&def_id.node) {
288 tcx.sess.span_err(span,
289 fmt!("function `%s` is private",
297 } else if csearch::get_item_visibility(tcx.sess.cstore,
299 tcx.sess.span_err(span,
300 fmt!("function `%s` is private",
313 // Checks that a private method is in scope.
314 let check_method: @fn(span: span,
315 origin: &method_origin,
317 |span, origin, ident| {
319 method_static(method_id) => {
320 check_method_common(span, method_id, &ident)
322 method_param(method_param {
324 method_num: method_num,
327 method_trait(trait_id, method_num, _) |
328 method_self(trait_id, method_num) |
329 method_super(trait_id, method_num) => {
330 if trait_id.crate == local_crate {
331 match tcx.items.find(&trait_id.node) {
332 Some(&node_item(item, _)) => {
334 item_trait(_, _, ref methods) => {
335 if method_num >= (*methods).len() {
336 tcx.sess.span_bug(span, ~"method \
341 match (*methods)[method_num] {
343 if method.vis == private &&
345 .contains(&(trait_id.node)) => {
346 tcx.sess.span_err(span,
359 provided(_) | required(_) => {
360 // Required methods can't be
366 tcx.sess.span_bug(span, ~"trait wasn't \
373 tcx.sess.span_bug(span, ~"trait wasn't an \
377 tcx.sess.span_bug(span, ~"trait item wasn't \
383 // FIXME #4732: External crates.
389 let visitor = visit::mk_vt(@visit::Visitor {
390 visit_mod: |the_module, span, node_id, method_map, visitor| {
391 let n_added = add_privileged_items(the_module.items);
393 visit::visit_mod(the_module, span, node_id, method_map, visitor);
396 ignore(privileged_items.pop());
399 visit_item: |item, method_map, visitor| {
400 // Do not check privacy inside items with the resolve_unexported
401 // attribute. This is used for the test runner.
402 if !attr::contains_name(attr::attr_metas(/*bad*/copy item.attrs),
403 ~"!resolve_unexported") {
404 visit::visit_item(item, method_map, visitor);
407 visit_block: |block, method_map, visitor| {
408 // Gather up all the privileged items.
410 for block.node.stmts.each |stmt| {
412 stmt_decl(decl, _) => {
415 add_privileged_item(item, &mut n_added);
424 visit::visit_block(block, method_map, visitor);
427 ignore(privileged_items.pop());
430 visit_expr: |expr, method_map: &method_map, visitor| {
432 expr_field(base, ident, _) => {
433 // With type_autoderef, make sure we don't
434 // allow pointers to violate privacy
435 match ty::get(ty::type_autoderef(tcx, ty::expr_ty(tcx,
438 if id.crate != local_crate ||
439 !privileged_items.contains(&(id.node)) => {
440 match method_map.find(&expr.id) {
442 debug!("(privacy checking) checking \
444 check_field(expr.span, id, ident);
447 debug!("(privacy checking) checking \
449 check_method(expr.span,
458 expr_method_call(base, ident, _, _, _) => {
460 match ty::get(ty::type_autoderef(tcx, ty::expr_ty(tcx,
463 if id.crate != local_crate ||
464 !privileged_items.contains(&(id.node)) => {
465 match method_map.find(&expr.id) {
467 tcx.sess.span_bug(expr.span,
468 ~"method call not in \
472 debug!("(privacy checking) checking \
474 check_method(expr.span,
484 check_path(expr.span, *tcx.def_map.get(&expr.id), path);
486 expr_struct(_, ref fields, _) => {
487 match ty::get(ty::expr_ty(tcx, expr)).sty {
488 ty_struct(id, _) => {
489 if id.crate != local_crate ||
490 !privileged_items.contains(&(id.node)) {
491 for (*fields).each |field| {
492 debug!("(privacy checking) checking \
493 field in struct literal");
494 check_field(expr.span, id,
500 if id.crate != local_crate ||
501 !privileged_items.contains(&(id.node)) {
502 match *tcx.def_map.get(&expr.id) {
503 def_variant(_, variant_id) => {
504 for (*fields).each |field| {
505 debug!("(privacy checking) \
509 check_field(expr.span, variant_id,
514 tcx.sess.span_bug(expr.span,
524 tcx.sess.span_bug(expr.span, ~"struct expr \
530 expr_unary(ast::deref, operand) => {
531 // In *e, we need to check that if e's type is an
532 // enum type t, then t's first variant is public or
533 // privileged. (We can assume it has only one variant
534 // since typeck already happened.)
535 match ty::get(ty::expr_ty(tcx, operand)).sty {
537 if id.crate != local_crate ||
538 !privileged_items.contains(&(id.node)) {
539 check_variant(expr.span, id);
542 _ => { /* No check needed */ }
548 visit::visit_expr(expr, method_map, visitor);
550 visit_pat: |pattern, method_map, visitor| {
552 pat_struct(_, ref fields, _) => {
553 match ty::get(ty::pat_ty(tcx, pattern)).sty {
554 ty_struct(id, _) => {
555 if id.crate != local_crate ||
556 !privileged_items.contains(&(id.node)) {
557 for fields.each |field| {
558 debug!("(privacy checking) checking \
560 check_field(pattern.span, id,
565 ty_enum(enum_id, _) => {
566 if enum_id.crate != local_crate ||
567 !privileged_items.contains(
569 match tcx.def_map.find(&pattern.id) {
570 Some(&def_variant(_, variant_id)) => {
571 for fields.each |field| {
572 debug!("(privacy checking) \
574 struct variant pattern");
575 check_field(pattern.span,
581 tcx.sess.span_bug(pattern.span,
591 tcx.sess.span_bug(pattern.span,
592 ~"struct pattern didn't have \
600 visit::visit_pat(pattern, method_map, visitor);
602 .. *visit::default_visitor()
604 visit::visit_crate(crate, method_map, visitor);