1 // Copyright 2012-2014 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 // Verifies that the types and values of const and static items
12 // are safe. The rules enforced by this module are:
14 // - For each *mutable* static item, it checks that its **type**:
15 // - doesn't have a destructor
16 // - doesn't own a box
18 // - For each *immutable* static item, it checks that its **value**:
19 // - doesn't own a box
20 // - doesn't contain a struct literal or a call to an enum variant / struct constructor where
21 // - the type of the struct/enum has a dtor
23 // Rules Enforced Elsewhere:
24 // - It's not possible to take the address of a static item with unsafe interior. This is enforced
25 // by borrowck::gather_loans
27 use rustc::dep_graph::DepNode;
28 use rustc::ty::cast::{CastKind};
29 use rustc_const_eval::{ConstEvalErr, lookup_const_fn_by_id, compare_lit_exprs};
30 use rustc_const_eval::{eval_const_expr_partial, lookup_const_by_id};
31 use rustc_const_eval::ErrKind::{IndexOpFeatureGated, UnimplementedConstVal, MiscCatchAll, Math};
32 use rustc_const_eval::ErrKind::{ErroneousReferencedConstant, MiscBinaryOp, NonConstPath};
33 use rustc_const_eval::ErrKind::UnresolvedPath;
34 use rustc_const_eval::EvalHint::ExprTypeChecked;
35 use rustc_const_math::{ConstMathErr, Op};
36 use rustc::hir::def::Def;
37 use rustc::hir::def_id::DefId;
38 use rustc::middle::expr_use_visitor as euv;
39 use rustc::middle::mem_categorization as mc;
40 use rustc::middle::mem_categorization::Categorization;
41 use rustc::ty::{self, Ty, TyCtxt};
42 use rustc::traits::ProjectionMode;
43 use rustc::util::nodemap::NodeMap;
44 use rustc::middle::const_qualif::ConstQualif;
45 use rustc::lint::builtin::CONST_ERR;
47 use rustc::hir::{self, PatKind};
49 use syntax::codemap::Span;
50 use rustc::hir::intravisit::{self, FnKind, Visitor};
52 use std::collections::hash_map::Entry;
53 use std::cmp::Ordering;
55 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
62 // An expression that occurs outside of any constant context
63 // (i.e. `const`, `static`, array lengths, etc.). The value
64 // can be variable at runtime, but will be promotable to
65 // static memory if we can prove it is actually constant.
69 struct CheckCrateVisitor<'a, 'tcx: 'a> {
70 tcx: TyCtxt<'a, 'tcx, 'tcx>,
73 rvalue_borrows: NodeMap<hir::Mutability>
76 impl<'a, 'gcx> CheckCrateVisitor<'a, 'gcx> {
77 fn with_mode<F, R>(&mut self, mode: Mode, f: F) -> R where
78 F: FnOnce(&mut CheckCrateVisitor<'a, 'gcx>) -> R,
80 let (old_mode, old_qualif) = (self.mode, self.qualif);
82 self.qualif = ConstQualif::empty();
85 self.qualif = old_qualif;
89 fn with_euv<F, R>(&mut self, item_id: Option<ast::NodeId>, f: F) -> R where
90 F: for<'b, 'tcx> FnOnce(&mut euv::ExprUseVisitor<'b, 'gcx, 'tcx>) -> R,
92 let param_env = match item_id {
93 Some(item_id) => ty::ParameterEnvironment::for_item(self.tcx, item_id),
94 None => self.tcx.empty_parameter_environment()
97 self.tcx.infer_ctxt(None, Some(param_env), ProjectionMode::AnyFinal).enter(|infcx| {
98 f(&mut euv::ExprUseVisitor::new(self, &infcx))
102 fn global_expr(&mut self, mode: Mode, expr: &hir::Expr) -> ConstQualif {
103 assert!(mode != Mode::Var);
104 match self.tcx.const_qualif_map.borrow_mut().entry(expr.id) {
105 Entry::Occupied(entry) => return *entry.get(),
106 Entry::Vacant(entry) => {
107 // Prevent infinite recursion on re-entry.
108 entry.insert(ConstQualif::empty());
111 if let Err(err) = eval_const_expr_partial(self.tcx, expr, ExprTypeChecked, None) {
113 UnimplementedConstVal(_) => {},
114 IndexOpFeatureGated => {},
115 ErroneousReferencedConstant(_) => {},
116 _ => self.tcx.sess.add_lint(CONST_ERR, expr.id, expr.span,
117 format!("constant evaluation error: {}. This will \
118 become a HARD ERROR in the future",
122 self.with_mode(mode, |this| {
123 this.with_euv(None, |euv| euv.consume_expr(expr));
124 this.visit_expr(expr);
129 fn fn_like(&mut self,
136 match self.tcx.const_qualif_map.borrow_mut().entry(fn_id) {
137 Entry::Occupied(entry) => return *entry.get(),
138 Entry::Vacant(entry) => {
139 // Prevent infinite recursion on re-entry.
140 entry.insert(ConstQualif::empty());
144 let mode = match fk {
145 FnKind::ItemFn(_, _, _, hir::Constness::Const, _, _, _) => {
148 FnKind::Method(_, m, _, _) => {
149 if m.constness == hir::Constness::Const {
158 let qualif = self.with_mode(mode, |this| {
159 this.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b));
160 intravisit::walk_fn(this, fk, fd, b, s);
164 // Keep only bits that aren't affected by function body (NON_ZERO_SIZED),
165 // and bits that don't change semantics, just optimizations (PREFER_IN_PLACE).
166 let qualif = qualif & (ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE);
168 self.tcx.const_qualif_map.borrow_mut().insert(fn_id, qualif);
172 fn add_qualif(&mut self, qualif: ConstQualif) {
173 self.qualif = self.qualif | qualif;
176 /// Returns true if the call is to a const fn or method.
177 fn handle_const_fn_call(&mut self,
182 if let Some(fn_like) = lookup_const_fn_by_id(self.tcx, def_id) {
183 let qualif = self.fn_like(fn_like.kind(),
188 self.add_qualif(qualif);
190 if ret_ty.type_contents(self.tcx).interior_unsafe() {
191 self.add_qualif(ConstQualif::MUTABLE_MEM);
200 fn record_borrow(&mut self, id: ast::NodeId, mutbl: hir::Mutability) {
201 match self.rvalue_borrows.entry(id) {
202 Entry::Occupied(mut entry) => {
203 // Merge the two borrows, taking the most demanding
204 // one, mutability-wise.
205 if mutbl == hir::MutMutable {
209 Entry::Vacant(entry) => {
215 fn msg(&self) -> &'static str {
217 Mode::Const => "constant",
218 Mode::ConstFn => "constant function",
219 Mode::StaticMut | Mode::Static => "static",
225 impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> {
226 fn visit_item(&mut self, i: &hir::Item) {
227 debug!("visit_item(item={})", self.tcx.map.node_to_string(i.id));
228 assert_eq!(self.mode, Mode::Var);
230 hir::ItemStatic(_, hir::MutImmutable, ref expr) => {
231 self.global_expr(Mode::Static, &expr);
233 hir::ItemStatic(_, hir::MutMutable, ref expr) => {
234 self.global_expr(Mode::StaticMut, &expr);
236 hir::ItemConst(_, ref expr) => {
237 self.global_expr(Mode::Const, &expr);
239 hir::ItemEnum(ref enum_definition, _) => {
240 for var in &enum_definition.variants {
241 if let Some(ref ex) = var.node.disr_expr {
242 self.global_expr(Mode::Const, &ex);
247 intravisit::walk_item(self, i);
252 fn visit_trait_item(&mut self, t: &'v hir::TraitItem) {
254 hir::ConstTraitItem(_, ref default) => {
255 if let Some(ref expr) = *default {
256 self.global_expr(Mode::Const, &expr);
258 intravisit::walk_trait_item(self, t);
261 _ => self.with_mode(Mode::Var, |v| intravisit::walk_trait_item(v, t)),
265 fn visit_impl_item(&mut self, i: &'v hir::ImplItem) {
267 hir::ImplItemKind::Const(_, ref expr) => {
268 self.global_expr(Mode::Const, &expr);
270 _ => self.with_mode(Mode::Var, |v| intravisit::walk_impl_item(v, i)),
274 fn visit_fn(&mut self,
279 fn_id: ast::NodeId) {
280 self.fn_like(fk, fd, b, s, fn_id);
283 fn visit_pat(&mut self, p: &hir::Pat) {
285 PatKind::Lit(ref lit) => {
286 self.global_expr(Mode::Const, &lit);
288 PatKind::Range(ref start, ref end) => {
289 self.global_expr(Mode::Const, &start);
290 self.global_expr(Mode::Const, &end);
292 match compare_lit_exprs(self.tcx, start, end) {
293 Some(Ordering::Less) |
294 Some(Ordering::Equal) => {}
295 Some(Ordering::Greater) => {
296 span_err!(self.tcx.sess, start.span, E0030,
297 "lower range bound must be less than or equal to upper");
300 span_err!(self.tcx.sess, p.span, E0014,
301 "paths in {}s may only refer to constants",
306 _ => intravisit::walk_pat(self, p)
310 fn visit_block(&mut self, block: &hir::Block) {
311 // Check all statements in the block
312 for stmt in &block.stmts {
314 hir::StmtDecl(ref decl, _) => {
316 hir::DeclLocal(_) => {},
317 // Item statements are allowed
318 hir::DeclItem(_) => continue
321 hir::StmtExpr(_, _) => {},
322 hir::StmtSemi(_, _) => {},
324 self.add_qualif(ConstQualif::NOT_CONST);
326 intravisit::walk_block(self, block);
329 fn visit_expr(&mut self, ex: &hir::Expr) {
330 let mut outer = self.qualif;
331 self.qualif = ConstQualif::empty();
333 let node_ty = self.tcx.node_id_to_type(ex.id);
334 check_expr(self, ex, node_ty);
335 check_adjustments(self, ex);
337 // Special-case some expressions to avoid certain flags bubbling up.
339 hir::ExprCall(ref callee, ref args) => {
341 self.visit_expr(&arg)
344 let inner = self.qualif;
345 self.visit_expr(&callee);
346 // The callee's size doesn't count in the call.
347 let added = self.qualif - inner;
348 self.qualif = inner | (added - ConstQualif::NON_ZERO_SIZED);
350 hir::ExprRepeat(ref element, _) => {
351 self.visit_expr(&element);
352 // The count is checked elsewhere (typeck).
353 let count = match node_ty.sty {
354 ty::TyArray(_, n) => n,
357 // [element; 0] is always zero-sized.
359 self.qualif.remove(ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE);
362 hir::ExprMatch(ref discr, ref arms, _) => {
363 // Compute the most demanding borrow from all the arms'
364 // patterns and set that on the discriminator.
365 let mut borrow = None;
366 for pat in arms.iter().flat_map(|arm| &arm.pats) {
367 let pat_borrow = self.rvalue_borrows.remove(&pat.id);
368 match (borrow, pat_borrow) {
369 (None, _) | (_, Some(hir::MutMutable)) => {
375 if let Some(mutbl) = borrow {
376 self.record_borrow(discr.id, mutbl);
378 intravisit::walk_expr(self, ex);
380 _ => intravisit::walk_expr(self, ex)
383 // Handle borrows on (or inside the autorefs of) this expression.
384 match self.rvalue_borrows.remove(&ex.id) {
385 Some(hir::MutImmutable) => {
386 // Constants cannot be borrowed if they contain interior mutability as
387 // it means that our "silent insertion of statics" could change
388 // initializer values (very bad).
389 // If the type doesn't have interior mutability, then `ConstQualif::MUTABLE_MEM` has
390 // propagated from another error, so erroring again would be just noise.
391 let tc = node_ty.type_contents(self.tcx);
392 if self.qualif.intersects(ConstQualif::MUTABLE_MEM) && tc.interior_unsafe() {
393 outer = outer | ConstQualif::NOT_CONST;
395 // If the reference has to be 'static, avoid in-place initialization
396 // as that will end up pointing to the stack instead.
397 if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) {
398 self.qualif = self.qualif - ConstQualif::PREFER_IN_PLACE;
399 self.add_qualif(ConstQualif::HAS_STATIC_BORROWS);
402 Some(hir::MutMutable) => {
403 // `&mut expr` means expr could be mutated, unless it's zero-sized.
404 if self.qualif.intersects(ConstQualif::NON_ZERO_SIZED) {
405 if self.mode == Mode::Var {
406 outer = outer | ConstQualif::NOT_CONST;
407 self.add_qualif(ConstQualif::MUTABLE_MEM);
410 if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) {
411 self.add_qualif(ConstQualif::HAS_STATIC_BORROWS);
417 if self.mode == Mode::Var && !self.qualif.intersects(ConstQualif::NOT_CONST) {
418 match eval_const_expr_partial(self.tcx, ex, ExprTypeChecked, None) {
420 Err(ConstEvalErr { kind: UnimplementedConstVal(_), ..}) |
421 Err(ConstEvalErr { kind: MiscCatchAll, ..}) |
422 Err(ConstEvalErr { kind: MiscBinaryOp, ..}) |
423 Err(ConstEvalErr { kind: NonConstPath, ..}) |
424 Err(ConstEvalErr { kind: UnresolvedPath, ..}) |
425 Err(ConstEvalErr { kind: ErroneousReferencedConstant(_), ..}) |
426 Err(ConstEvalErr { kind: Math(ConstMathErr::Overflow(Op::Shr)), ..}) |
427 Err(ConstEvalErr { kind: Math(ConstMathErr::Overflow(Op::Shl)), ..}) |
428 Err(ConstEvalErr { kind: IndexOpFeatureGated, ..}) => {},
430 self.tcx.sess.add_lint(CONST_ERR, ex.id,
432 msg.description().into_owned())
437 self.tcx.const_qualif_map.borrow_mut().insert(ex.id, self.qualif);
438 // Don't propagate certain flags.
439 self.qualif = outer | (self.qualif - ConstQualif::HAS_STATIC_BORROWS);
443 /// This function is used to enforce the constraints on
444 /// const/static items. It walks through the *value*
445 /// of the item walking down the expression and evaluating
446 /// every nested expression. If the expression is not part
447 /// of a const/static item, it is qualified for promotion
448 /// instead of producing errors.
449 fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
450 e: &hir::Expr, node_ty: Ty<'tcx>) {
452 ty::TyStruct(def, _) |
453 ty::TyEnum(def, _) if def.has_dtor() => {
454 v.add_qualif(ConstQualif::NEEDS_DROP);
459 let method_call = ty::MethodCall::expr(e.id);
462 hir::ExprBinary(..) |
463 hir::ExprIndex(..) if v.tcx.tables.borrow().method_map.contains_key(&method_call) => {
464 v.add_qualif(ConstQualif::NOT_CONST);
467 v.add_qualif(ConstQualif::NOT_CONST);
469 hir::ExprUnary(op, ref inner) => {
470 match v.tcx.node_id_to_type(inner.id).sty {
472 assert!(op == hir::UnDeref);
474 v.add_qualif(ConstQualif::NOT_CONST);
479 hir::ExprBinary(op, ref lhs, _) => {
480 match v.tcx.node_id_to_type(lhs.id).sty {
482 assert!(op.node == hir::BiEq || op.node == hir::BiNe ||
483 op.node == hir::BiLe || op.node == hir::BiLt ||
484 op.node == hir::BiGe || op.node == hir::BiGt);
486 v.add_qualif(ConstQualif::NOT_CONST);
491 hir::ExprCast(ref from, _) => {
492 debug!("Checking const cast(id={})", from.id);
493 match v.tcx.cast_kinds.borrow().get(&from.id) {
494 None => span_bug!(e.span, "no kind for cast"),
495 Some(&CastKind::PtrAddrCast) | Some(&CastKind::FnPtrAddrCast) => {
496 v.add_qualif(ConstQualif::NOT_CONST);
501 hir::ExprPath(..) => {
502 match v.tcx.expect_def(e.id) {
503 Def::Variant(..) => {
504 // Count the discriminator or function pointer.
505 v.add_qualif(ConstQualif::NON_ZERO_SIZED);
508 if let ty::TyFnDef(..) = node_ty.sty {
509 // Count the function pointer.
510 v.add_qualif(ConstQualif::NON_ZERO_SIZED);
513 Def::Fn(..) | Def::Method(..) => {
514 // Count the function pointer.
515 v.add_qualif(ConstQualif::NON_ZERO_SIZED);
519 Mode::Static | Mode::StaticMut => {}
520 Mode::Const | Mode::ConstFn => {}
521 Mode::Var => v.add_qualif(ConstQualif::NOT_CONST)
524 Def::Const(did) | Def::AssociatedConst(did) => {
525 let substs = Some(v.tcx.node_id_item_substs(e.id).substs);
526 if let Some((expr, _)) = lookup_const_by_id(v.tcx, did, substs) {
527 let inner = v.global_expr(Mode::Const, expr);
531 Def::Local(..) if v.mode == Mode::ConstFn => {
532 // Sadly, we can't determine whether the types are zero-sized.
533 v.add_qualif(ConstQualif::NOT_CONST | ConstQualif::NON_ZERO_SIZED);
536 v.add_qualif(ConstQualif::NOT_CONST);
540 hir::ExprCall(ref callee, _) => {
541 let mut callee = &**callee;
543 callee = match callee.node {
544 hir::ExprBlock(ref block) => match block.expr {
545 Some(ref tail) => &tail,
551 // The callee is an arbitrary expression, it doesn't necessarily have a definition.
552 let is_const = match v.tcx.expect_def_or_none(callee.id) {
553 Some(Def::Struct(..)) => true,
554 Some(Def::Variant(..)) => {
555 // Count the discriminator.
556 v.add_qualif(ConstQualif::NON_ZERO_SIZED);
559 Some(Def::Fn(did)) => {
560 v.handle_const_fn_call(e, did, node_ty)
562 Some(Def::Method(did)) => {
563 match v.tcx.impl_or_trait_item(did).container() {
564 ty::ImplContainer(_) => {
565 v.handle_const_fn_call(e, did, node_ty)
567 ty::TraitContainer(_) => false
573 v.add_qualif(ConstQualif::NOT_CONST);
576 hir::ExprMethodCall(..) => {
577 let method = v.tcx.tables.borrow().method_map[&method_call];
578 let is_const = match v.tcx.impl_or_trait_item(method.def_id).container() {
579 ty::ImplContainer(_) => v.handle_const_fn_call(e, method.def_id, node_ty),
580 ty::TraitContainer(_) => false
583 v.add_qualif(ConstQualif::NOT_CONST);
586 hir::ExprStruct(..) => {
587 // unsafe_cell_type doesn't necessarily exist with no_core
588 if Some(v.tcx.expect_def(e.id).def_id()) == v.tcx.lang_items.unsafe_cell_type() {
589 v.add_qualif(ConstQualif::MUTABLE_MEM);
594 hir::ExprAddrOf(..) => {
595 v.add_qualif(ConstQualif::NON_ZERO_SIZED);
598 hir::ExprRepeat(..) => {
599 v.add_qualif(ConstQualif::PREFER_IN_PLACE);
602 hir::ExprClosure(..) => {
603 // Paths in constant contexts cannot refer to local variables,
604 // as there are none, and thus closures can't have upvars there.
605 if v.tcx.with_freevars(e.id, |fv| !fv.is_empty()) {
606 assert!(v.mode == Mode::Var,
607 "global closures can't capture anything");
608 v.add_qualif(ConstQualif::NOT_CONST);
615 hir::ExprTupField(..) |
618 hir::ExprTup(..) => {}
620 // Conditional control flow (possible to implement).
624 // Loops (not very meaningful in constants).
628 // More control flow (also not very meaningful).
633 // Expressions with side-effects.
634 hir::ExprAssign(..) |
635 hir::ExprAssignOp(..) |
636 hir::ExprInlineAsm(..) => {
637 v.add_qualif(ConstQualif::NOT_CONST);
642 /// Check the adjustments of an expression
643 fn check_adjustments<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr) {
644 match v.tcx.tables.borrow().adjustments.get(&e.id) {
646 Some(&ty::adjustment::AdjustReifyFnPointer) |
647 Some(&ty::adjustment::AdjustUnsafeFnPointer) |
648 Some(&ty::adjustment::AdjustMutToConstPointer) => {}
650 Some(&ty::adjustment::AdjustDerefRef(
651 ty::adjustment::AutoDerefRef { autoderefs, .. }
653 if (0..autoderefs as u32).any(|autoderef| {
654 v.tcx.is_overloaded_autoderef(e.id, autoderef)
656 v.add_qualif(ConstQualif::NOT_CONST);
662 pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
663 tcx.visit_all_items_in_krate(DepNode::CheckConst, &mut CheckCrateVisitor {
666 qualif: ConstQualif::NOT_CONST,
667 rvalue_borrows: NodeMap()
669 tcx.sess.abort_if_errors();
672 impl<'a, 'gcx, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'gcx> {
673 fn consume(&mut self,
674 _consume_id: ast::NodeId,
677 _mode: euv::ConsumeMode) {
681 Categorization::StaticItem => {
684 Categorization::Deref(ref cmt, _, _) |
685 Categorization::Downcast(ref cmt, _) |
686 Categorization::Interior(ref cmt, _) => cur = cmt,
688 Categorization::Rvalue(..) |
689 Categorization::Upvar(..) |
690 Categorization::Local(..) => break
695 borrow_id: ast::NodeId,
698 _loan_region: ty::Region,
700 loan_cause: euv::LoanCause)
702 // Kind of hacky, but we allow Unsafe coercions in constants.
703 // These occur when we convert a &T or *T to a *U, as well as
704 // when making a thin pointer (e.g., `*T`) into a fat pointer
707 euv::LoanCause::AutoUnsafe => {
716 Categorization::Rvalue(..) => {
717 if loan_cause == euv::MatchDiscriminant {
718 // Ignore the dummy immutable borrow created by EUV.
721 let mutbl = bk.to_mutbl_lossy();
722 if mutbl == hir::MutMutable && self.mode == Mode::StaticMut {
723 // Mutable slices are the only `&mut` allowed in
724 // globals, but only in `static mut`, nowhere else.
725 // FIXME: This exception is really weird... there isn't
726 // any fundamental reason to restrict this based on
727 // type of the expression. `&mut [1]` has exactly the
728 // same representation as &mut 1.
730 ty::TyArray(_, _) | ty::TySlice(_) => break,
734 self.record_borrow(borrow_id, mutbl);
737 Categorization::StaticItem => {
740 Categorization::Deref(ref cmt, _, _) |
741 Categorization::Downcast(ref cmt, _) |
742 Categorization::Interior(ref cmt, _) => {
746 Categorization::Upvar(..) |
747 Categorization::Local(..) => break
752 fn decl_without_init(&mut self,
756 _assignment_id: ast::NodeId,
757 _assignment_span: Span,
758 _assignee_cmt: mc::cmt,
759 _mode: euv::MutateMode) {}
761 fn matched_pat(&mut self,
764 _: euv::MatchMode) {}
766 fn consume_pat(&mut self,
767 _consume_pat: &hir::Pat,
769 _mode: euv::ConsumeMode) {}