rust/src/librustc/middle/check_static.rs
Felix S. Klock II 09d67fd777 Track what drop obligations are established on match arms.
This is accomplished by:

1. Add `MatchMode` enum to `expr_use_visitor`.

2. Computing the match mode for each pattern via a pre-pass, and then
   passing the mode along when visiting the pattern in
   expr_use_visitor.

3. Adding a `fn matched_pat` callback to expr_use_visitor, which is
   called on interior struct and enum nodes of the pattern (as opposed
   to `fn consume_pat`, which is only invoked for identifiers at the
   leaves of the pattern), and invoking it accordingly.

Of particular interest are the `cat_downcast` instances established
when matching enum variants.
2014-11-25 15:26:16 +01:00

339 lines
12 KiB
Rust

// Copyright 2014 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.
// Verifies that the types and values of static items
// are safe. The rules enforced by this module are:
//
// - For each *mutable* static item, it checks that its **type**:
// - doesn't have a destructor
// - doesn't own an owned pointer
//
// - For each *immutable* static item, it checks that its **value**:
// - doesn't own owned, managed pointers
// - doesn't contain a struct literal or a call to an enum variant / struct constructor where
// - the type of the struct/enum has a dtor
//
// Rules Enforced Elsewhere:
// - It's not possible to take the address of a static item with unsafe interior. This is enforced
// by borrowck::gather_loans
use self::Mode::*;
use middle::ty;
use middle::def;
use middle::typeck;
use middle::traits;
use middle::mem_categorization as mc;
use middle::expr_use_visitor as euv;
use util::nodemap::NodeSet;
use syntax::ast;
use syntax::print::pprust;
use syntax::visit::Visitor;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::visit;
#[deriving(Eq, PartialEq)]
enum Mode {
InConstant,
InStatic,
InStaticMut,
InNothing,
}
struct CheckStaticVisitor<'a, 'tcx: 'a> {
tcx: &'a ty::ctxt<'tcx>,
mode: Mode,
checker: &'a mut GlobalChecker,
}
struct GlobalVisitor<'a, 'b, 'tcx: 'b>(euv::ExprUseVisitor<'a, 'b, 'tcx, ty::ctxt<'tcx>>);
struct GlobalChecker {
static_consumptions: NodeSet,
const_borrows: NodeSet,
static_interior_borrows: NodeSet,
static_local_borrows: NodeSet,
}
pub fn check_crate(tcx: &ty::ctxt) {
let mut checker = GlobalChecker {
static_consumptions: NodeSet::new(),
const_borrows: NodeSet::new(),
static_interior_borrows: NodeSet::new(),
static_local_borrows: NodeSet::new(),
};
{
let visitor = euv::ExprUseVisitor::new(&mut checker, tcx);
visit::walk_crate(&mut GlobalVisitor(visitor), tcx.map.krate());
}
visit::walk_crate(&mut CheckStaticVisitor {
tcx: tcx,
mode: InNothing,
checker: &mut checker,
}, tcx.map.krate());
}
impl<'a, 'tcx> CheckStaticVisitor<'a, 'tcx> {
fn with_mode(&mut self, mode: Mode, f: |&mut CheckStaticVisitor<'a, 'tcx>|) {
let old = self.mode;
self.mode = mode;
f(self);
self.mode = old;
}
fn msg(&self) -> &'static str {
match self.mode {
InConstant => "constants",
InStaticMut | InStatic => "statics",
InNothing => unreachable!(),
}
}
fn check_static_mut_type(&self, e: &ast::Expr) {
let node_ty = ty::node_id_to_type(self.tcx, e.id);
let tcontents = ty::type_contents(self.tcx, node_ty);
let suffix = if tcontents.has_dtor() {
"destructors"
} else if tcontents.owns_owned() {
"owned pointers"
} else {
return
};
self.tcx.sess.span_err(e.span, format!("mutable statics are not allowed \
to have {}", suffix).as_slice());
}
fn check_static_type(&self, e: &ast::Expr) {
let ty = ty::node_id_to_type(self.tcx, e.id);
let infcx = typeck::infer::new_infer_ctxt(self.tcx);
let mut fulfill_cx = traits::FulfillmentContext::new();
let cause = traits::ObligationCause::misc(DUMMY_SP);
let obligation = traits::obligation_for_builtin_bound(self.tcx, cause, ty,
ty::BoundSync);
fulfill_cx.register_obligation(self.tcx, obligation.unwrap());
let env = ty::empty_parameter_environment();
let result = fulfill_cx.select_all_or_error(&infcx, &env, self.tcx).is_ok();
if !result {
self.tcx.sess.span_err(e.span, "shared static items must have a \
type which implements Sync");
}
}
}
impl<'a, 'tcx, 'v> Visitor<'v> for CheckStaticVisitor<'a, 'tcx> {
fn visit_item(&mut self, i: &ast::Item) {
debug!("visit_item(item={})", pprust::item_to_string(i));
match i.node {
ast::ItemStatic(_, ast::MutImmutable, ref expr) => {
self.check_static_type(&**expr);
self.with_mode(InStatic, |v| v.visit_expr(&**expr));
}
ast::ItemStatic(_, ast::MutMutable, ref expr) => {
self.check_static_mut_type(&**expr);
self.with_mode(InStaticMut, |v| v.visit_expr(&**expr));
}
ast::ItemConst(_, ref expr) => {
self.with_mode(InConstant, |v| v.visit_expr(&**expr));
}
_ => {
self.with_mode(InNothing, |v| visit::walk_item(v, i));
}
}
}
/// This method is used to enforce the constraints on
/// immutable static items. It walks through the *value*
/// of the item walking down the expression and evaluating
/// every nested expression. if the expression is not part
/// of a static item, this method does nothing but walking
/// down through it.
fn visit_expr(&mut self, e: &ast::Expr) {
if self.mode == InNothing {
return visit::walk_expr(self, e);
}
let node_ty = ty::node_id_to_type(self.tcx, e.id);
match node_ty.sty {
ty::ty_struct(did, _) |
ty::ty_enum(did, _) if ty::has_dtor(self.tcx, did) => {
self.tcx.sess.span_err(e.span,
format!("{} are not allowed to have \
destructors", self.msg()).as_slice())
}
_ => {}
}
// statics cannot be consumed by value at any time, that would imply
// that they're an initializer (what a const is for) or kept in sync
// over time (not feasible), so deny it outright.
if self.checker.static_consumptions.remove(&e.id) {
self.tcx.sess.span_err(e.span, "cannot refer to other statics by \
value, use the address-of operator \
or a constant instead");
}
// Borrowed statics can specifically *only* have their address taken,
// not any number of other borrows such as borrowing fields, reading
// elements of an array, etc.
if self.checker.static_interior_borrows.remove(&e.id) {
self.tcx.sess.span_err(e.span, "cannot refer to the interior of \
another static, use a constant \
instead");
}
// constants cannot be borrowed if they contain interior mutability as
// it means that our "silent insertion of statics" could change
// initializer values (very bad).
if self.checker.const_borrows.remove(&e.id) {
let node_ty = ty::node_id_to_type(self.tcx, e.id);
let tcontents = ty::type_contents(self.tcx, node_ty);
if tcontents.interior_unsafe() {
self.tcx.sess.span_err(e.span, "cannot borrow a constant which \
contains interior mutability, \
create a static instead");
}
}
// local variables in a block expression in a static context (i.e. being
// assigned to a static variable) cannot be borrowed.
if self.checker.static_local_borrows.remove(&e.id) {
self.tcx.sess.span_err(e.span, "cannot borrow a local variable inside \
a static block, define a separate static \
instead");
}
match e.node {
ast::ExprAddrOf(ast::MutMutable, _) => {
if self.mode != InStaticMut {
span_err!(self.tcx.sess, e.span, E0020,
"{} are not allowed to have mutable references",
self.msg());
}
},
ast::ExprBox(..) |
ast::ExprUnary(ast::UnUniq, _) => {
span_err!(self.tcx.sess, e.span, E0022,
"{} are not allowed to have custom pointers",
self.msg());
}
ast::ExprPath(..) => {
match ty::resolve_expr(self.tcx, e) {
def::DefStatic(..) if self.mode == InConstant => {
let msg = "constants cannot refer to other statics, \
insert an intermediate constant \
instead";
self.tcx.sess.span_err(e.span, msg.as_slice());
}
_ => {}
}
}
_ => {}
}
visit::walk_expr(self, e);
}
}
impl<'a, 'b, 't, 'v> Visitor<'v> for GlobalVisitor<'a, 'b, 't> {
fn visit_item(&mut self, item: &ast::Item) {
match item.node {
ast::ItemConst(_, ref e) |
ast::ItemStatic(_, _, ref e) => {
let GlobalVisitor(ref mut v) = *self;
v.consume_expr(&**e);
}
_ => {}
}
visit::walk_item(self, item);
}
}
impl<'tcx> euv::Delegate<'tcx> for GlobalChecker {
fn consume(&mut self,
consume_id: ast::NodeId,
_consume_span: Span,
cmt: mc::cmt,
_mode: euv::ConsumeMode) {
let mut cur = &cmt;
loop {
match cur.cat {
mc::cat_static_item => {
self.static_consumptions.insert(consume_id);
break
}
mc::cat_deref(ref cmt, _, _) |
mc::cat_downcast(ref cmt, _) |
mc::cat_interior(ref cmt, _) => cur = cmt,
mc::cat_rvalue(..) |
mc::cat_upvar(..) |
mc::cat_local(..) => break,
}
}
}
fn borrow(&mut self,
borrow_id: ast::NodeId,
_borrow_span: Span,
cmt: mc::cmt,
_loan_region: ty::Region,
_bk: ty::BorrowKind,
_loan_cause: euv::LoanCause) {
let mut cur = &cmt;
let mut is_interior = false;
loop {
match cur.cat {
mc::cat_rvalue(..) => {
self.const_borrows.insert(borrow_id);
break
}
mc::cat_static_item => {
if is_interior {
self.static_interior_borrows.insert(borrow_id);
}
break
}
mc::cat_deref(ref cmt, _, _) |
mc::cat_interior(ref cmt, _) => {
is_interior = true;
cur = cmt;
}
mc::cat_downcast(..) |
mc::cat_upvar(..) => unreachable!(),
mc::cat_local(..) => {
self.static_local_borrows.insert(borrow_id);
break
}
}
}
}
fn decl_without_init(&mut self,
_id: ast::NodeId,
_span: Span) {}
fn mutate(&mut self,
_assignment_id: ast::NodeId,
_assignment_span: Span,
_assignee_cmt: mc::cmt,
_mode: euv::MutateMode) {}
fn matched_pat(&mut self,
_: &ast::Pat,
_: mc::cmt,
_: euv::MatchMode) {}
fn consume_pat(&mut self,
_consume_pat: &ast::Pat,
_cmt: mc::cmt,
_mode: euv::ConsumeMode) {}
}