]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/privacy.rs
a37ebdcfaa263345260123f5c382cb1979e32c9f
[rust.git] / src / librustc / middle / privacy.rs
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.
4 //
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.
10
11 // A pass that checks to make sure private fields and methods aren't used
12 // outside their scopes.
13
14 use metadata::csearch;
15 use middle::ty::{ty_struct, ty_enum};
16 use middle::ty;
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};
20
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};
28 use syntax::ast;
29 use syntax::ast_map::{node_foreign_item, node_item, node_method};
30 use syntax::ast_map::{node_trait_method};
31 use syntax::ast_map;
32 use syntax::ast_util::{Private, Public, is_local};
33 use syntax::ast_util::{variant_visibility_to_privacy, visibility_to_privacy};
34 use syntax::attr;
35 use syntax::codemap::span;
36 use syntax::visit;
37
38 pub fn check_crate(tcx: ty::ctxt,
39                    method_map: &method_map,
40                    crate: @ast::crate) {
41     let privileged_items = @mut ~[];
42
43     // Adds an item to its scope.
44     let add_privileged_item: @fn(@ast::item, &mut uint) = |item, count| {
45         match item.node {
46             item_struct(*) | item_trait(*) | item_enum(*) |
47             item_fn(*) => {
48                 privileged_items.push(item.id);
49                 *count += 1;
50             }
51             item_impl(_, _, _, ref methods) => {
52                 for methods.each |method| {
53                     privileged_items.push(method.id);
54                     *count += 1;
55                 }
56                 privileged_items.push(item.id);
57                 *count += 1;
58             }
59             item_foreign_mod(ref foreign_mod) => {
60                 for foreign_mod.items.each |foreign_item| {
61                     privileged_items.push(foreign_item.id);
62                     *count += 1;
63                 }
64             }
65             _ => {}
66         }
67     };
68
69     // Adds items that are privileged to this scope.
70     let add_privileged_items: @fn(&[@ast::item]) -> uint = |items| {
71         let mut count = 0;
72         for items.each |&item| {
73             add_privileged_item(item, &mut count);
74         }
75         count
76     };
77
78     // Checks that an enum variant is in scope
79     let check_variant: @fn(span: span, enum_id: ast::def_id) =
80             |span, enum_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,
84                                    |it| { it.vis },
85                                    ~"unbound enum parent when checking \
86                                     dereference of enum type");
87             visibility_to_privacy(parent_vis)
88         }
89         else {
90             // WRONG
91             Public
92         };
93         debug!("parental_privacy = %?", parental_privacy);
94         debug!("vis = %?, priv = %?",
95                variant_info.vis,
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)
100                                          == Private {
101             tcx.sess.span_err(span,
102                 ~"can only dereference enums \
103                   with a single, public variant");
104         }
105     };
106
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 =
111             |span, method_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,
115             Some(_) => {
116                 tcx.sess.span_bug(span,
117                                   fmt!("method was a %s?!",
118                                        ast_map::node_id_to_str(
119                                             tcx.items,
120                                             method_id,
121                                             tcx.sess.parse_sess.interner)));
122             }
123             None => {
124                 tcx.sess.span_bug(span, ~"method not found in \
125                                           AST map?!");
126             }
127         }
128     };
129
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 =
132             |span, method_id| {
133         let check = |vis: visibility, container_id: def_id| {
134             let mut is_private = false;
135             if vis == private {
136                 is_private = true;
137             } else if vis == public {
138                 is_private = false;
139             } else {
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 \
144                                         impl?!");
145                 }
146
147                 match tcx.items.find(&container_id.node) {
148                     Some(&node_item(item, _)) => {
149                         match item.node {
150                             item_impl(_, None, _, _)
151                                     if item.vis != public => {
152                                 is_private = true;
153                             }
154                             _ => {}
155                         }
156                     }
157                     Some(_) => {
158                         tcx.sess.span_bug(span, ~"impl wasn't an item?!");
159                     }
160                     None => {
161                         tcx.sess.span_bug(span, ~"impl wasn't in AST map?!");
162                     }
163                 }
164             }
165
166             is_private
167         };
168
169         match tcx.items.find(&method_id) {
170             Some(&node_method(method, impl_id, _)) => {
171                 check(method.vis, impl_id)
172             }
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),
177                 }
178             }
179             Some(_) => {
180                 tcx.sess.span_bug(span,
181                                   fmt!("method_is_private: method was a %s?!",
182                                        ast_map::node_id_to_str(
183                                             tcx.items,
184                                             method_id,
185                                             tcx.sess.parse_sess.interner)));
186             }
187             None => {
188                 tcx.sess.span_bug(span, ~"method not found in \
189                                           AST map?!");
190             }
191         }
192     };
193
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 =
196             |span, item_id| {
197         let mut f: &fn(node_id) -> bool = |_| false;
198         f = |item_id| {
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, _)) => {
203                     match method.vis {
204                         private => true,
205                         public => false,
206                         inherited => f(impl_did.node)
207                     }
208                 }
209                 Some(&node_trait_method(_, trait_did, _)) => f(trait_did.node),
210                 Some(_) => {
211                     tcx.sess.span_bug(span,
212                                       fmt!("local_item_is_private: item was \
213                                             a %s?!",
214                                            ast_map::node_id_to_str(
215                                                 tcx.items,
216                                                 item_id,
217                                                 tcx.sess
218                                                    .parse_sess
219                                                    .interner)));
220                 }
221                 None => {
222                     tcx.sess.span_bug(span, ~"item not found in AST map?!");
223                 }
224             }
225         };
226         f(item_id)
227     };
228
229     // Checks that a private field is in scope.
230     let check_field: @fn(span: span, id: ast::def_id, ident: ast::ident) =
231             |span, id, 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
238                                                  .get(ident)));
239             }
240             break;
241         }
242     };
243
244     // Given the ID of a method, checks to ensure it's in scope.
245     let check_method_common: @fn(span: span,
246                                  method_id: def_id,
247                                  name: &ident) =
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,
252                                                          method_id.node);
253             if is_private &&
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",
258                                        *tcx.sess
259                                            .parse_sess
260                                            .interner
261                                            .get(*name)));
262             }
263         } else {
264             let visibility =
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
270                                            .get(*name)));
271             }
272         }
273     };
274
275     // Checks that a private path is in scope.
276     let check_path: @fn(span: span, def: def, path: @Path) =
277             |span, def, path| {
278         debug!("checking path");
279         match def {
280             def_static_method(method_id, _, _) => {
281                 debug!("found static method def, checking it");
282                 check_method_common(span, method_id, path.idents.last())
283             }
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",
290                                                *tcx.sess
291                                                    .parse_sess
292                                                    .interner
293                                                    .get(copy *path
294                                                              .idents
295                                                              .last())));
296                     }
297                 } else if csearch::get_item_visibility(tcx.sess.cstore,
298                                                        def_id) != public {
299                     tcx.sess.span_err(span,
300                                       fmt!("function `%s` is private",
301                                            *tcx.sess
302                                                .parse_sess
303                                                .interner
304                                                .get(copy *path
305                                                          .idents
306                                                          .last())));
307                 }
308             }
309             _ => {}
310         }
311     };
312
313     // Checks that a private method is in scope.
314     let check_method: @fn(span: span,
315                           origin: &method_origin,
316                           ident: ast::ident) =
317             |span, origin, ident| {
318         match *origin {
319             method_static(method_id) => {
320                 check_method_common(span, method_id, &ident)
321             }
322             method_param(method_param {
323                 trait_id: trait_id,
324                  method_num: method_num,
325                  _
326             }) |
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, _)) => {
333                             match item.node {
334                                 item_trait(_, _, ref methods) => {
335                                     if method_num >= (*methods).len() {
336                                         tcx.sess.span_bug(span, ~"method \
337                                                                   number \
338                                                                   out of \
339                                                                   range?!");
340                                     }
341                                     match (*methods)[method_num] {
342                                         provided(method)
343                                              if method.vis == private &&
344                                              !privileged_items
345                                              .contains(&(trait_id.node)) => {
346                                             tcx.sess.span_err(span,
347                                                               fmt!("method
348                                                                     `%s` \
349                                                                     is \
350                                                                     private",
351                                                                    *tcx
352                                                                    .sess
353                                                                    .parse_sess
354                                                                    .interner
355                                                                    .get
356                                                                    (method
357                                                                     .ident)));
358                                         }
359                                         provided(_) | required(_) => {
360                                             // Required methods can't be
361                                             // private.
362                                         }
363                                     }
364                                 }
365                                 _ => {
366                                     tcx.sess.span_bug(span, ~"trait wasn't \
367                                                               actually a \
368                                                               trait?!");
369                                 }
370                             }
371                         }
372                         Some(_) => {
373                             tcx.sess.span_bug(span, ~"trait wasn't an \
374                                                       item?!");
375                         }
376                         None => {
377                             tcx.sess.span_bug(span, ~"trait item wasn't \
378                                                       found in the AST \
379                                                       map?!");
380                         }
381                     }
382                 } else {
383                     // FIXME #4732: External crates.
384                 }
385             }
386         }
387     };
388
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);
392
393             visit::visit_mod(the_module, span, node_id, method_map, visitor);
394
395             for n_added.times {
396                 ignore(privileged_items.pop());
397             }
398         },
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);
405             }
406         },
407         visit_block: |block, method_map, visitor| {
408             // Gather up all the privileged items.
409             let mut n_added = 0;
410             for block.node.stmts.each |stmt| {
411                 match stmt.node {
412                     stmt_decl(decl, _) => {
413                         match decl.node {
414                             decl_item(item) => {
415                                 add_privileged_item(item, &mut n_added);
416                             }
417                             _ => {}
418                         }
419                     }
420                     _ => {}
421                 }
422             }
423
424             visit::visit_block(block, method_map, visitor);
425
426             for n_added.times {
427                 ignore(privileged_items.pop());
428             }
429         },
430         visit_expr: |expr, method_map: &method_map, visitor| {
431             match expr.node {
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,
436                                                           base))).sty {
437                         ty_struct(id, _)
438                         if id.crate != local_crate ||
439                            !privileged_items.contains(&(id.node)) => {
440                             match method_map.find(&expr.id) {
441                                 None => {
442                                     debug!("(privacy checking) checking \
443                                             field access");
444                                     check_field(expr.span, id, ident);
445                                 }
446                                 Some(ref entry) => {
447                                     debug!("(privacy checking) checking \
448                                             impl method");
449                                     check_method(expr.span,
450                                                  &entry.origin,
451                                                  ident);
452                                 }
453                             }
454                         }
455                         _ => {}
456                     }
457                 }
458                 expr_method_call(base, ident, _, _, _) => {
459                     // Ditto
460                     match ty::get(ty::type_autoderef(tcx, ty::expr_ty(tcx,
461                                                           base))).sty {
462                         ty_struct(id, _)
463                         if id.crate != local_crate ||
464                            !privileged_items.contains(&(id.node)) => {
465                             match method_map.find(&expr.id) {
466                                 None => {
467                                     tcx.sess.span_bug(expr.span,
468                                                       ~"method call not in \
469                                                         method map");
470                                 }
471                                 Some(ref entry) => {
472                                     debug!("(privacy checking) checking \
473                                             impl method");
474                                     check_method(expr.span,
475                                                  &entry.origin,
476                                                  ident);
477                                 }
478                             }
479                         }
480                         _ => {}
481                     }
482                 }
483                 expr_path(path) => {
484                     check_path(expr.span, *tcx.def_map.get(&expr.id), path);
485                 }
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,
495                                                 field.node.ident);
496                                 }
497                             }
498                         }
499                         ty_enum(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) \
506                                                         checking field in \
507                                                         struct variant \
508                                                         literal");
509                                             check_field(expr.span, variant_id,
510                                                         field.node.ident);
511                                         }
512                                     }
513                                     _ => {
514                                         tcx.sess.span_bug(expr.span,
515                                                           ~"resolve didn't \
516                                                             map enum struct \
517                                                             constructor to a \
518                                                             variant def");
519                                     }
520                                 }
521                             }
522                         }
523                         _ => {
524                             tcx.sess.span_bug(expr.span, ~"struct expr \
525                                                            didn't have \
526                                                            struct type?!");
527                         }
528                     }
529                 }
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 {
536                         ty_enum(id, _) => {
537                             if id.crate != local_crate ||
538                                 !privileged_items.contains(&(id.node)) {
539                                 check_variant(expr.span, id);
540                             }
541                         }
542                         _ => { /* No check needed */ }
543                     }
544                 }
545                 _ => {}
546             }
547
548             visit::visit_expr(expr, method_map, visitor);
549         },
550         visit_pat: |pattern, method_map, visitor| {
551             match pattern.node {
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 \
559                                                 struct pattern");
560                                     check_field(pattern.span, id,
561                                                 field.ident);
562                                 }
563                             }
564                         }
565                         ty_enum(enum_id, _) => {
566                             if enum_id.crate != local_crate ||
567                                     !privileged_items.contains(
568                                         &enum_id.node) {
569                                 match tcx.def_map.find(&pattern.id) {
570                                     Some(&def_variant(_, variant_id)) => {
571                                         for fields.each |field| {
572                                             debug!("(privacy checking) \
573                                                     checking field in \
574                                                     struct variant pattern");
575                                             check_field(pattern.span,
576                                                         variant_id,
577                                                         field.ident);
578                                         }
579                                     }
580                                     _ => {
581                                         tcx.sess.span_bug(pattern.span,
582                                                           ~"resolve didn't \
583                                                             map enum struct \
584                                                             pattern to a \
585                                                             variant def");
586                                     }
587                                 }
588                             }
589                         }
590                         _ => {
591                             tcx.sess.span_bug(pattern.span,
592                                               ~"struct pattern didn't have \
593                                                 struct type?!");
594                         }
595                     }
596                 }
597                 _ => {}
598             }
599
600             visit::visit_pat(pattern, method_map, visitor);
601         },
602         .. *visit::default_visitor()
603     });
604     visit::visit_crate(crate, method_map, visitor);
605 }
606