| // Copyright 2012-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 const and 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 a box |
| // |
| // - For each *immutable* static item, it checks that its **value**: |
| // - doesn't own a box |
| // - 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 dep_graph::DepNode; |
| use middle::ty::cast::{CastKind}; |
| use middle::const_eval::{self, ConstEvalErr}; |
| use middle::const_eval::ErrKind::IndexOpFeatureGated; |
| use middle::const_eval::EvalHint::ExprTypeChecked; |
| use middle::def; |
| use middle::def_id::DefId; |
| use middle::expr_use_visitor as euv; |
| use middle::infer; |
| use middle::mem_categorization as mc; |
| use middle::mem_categorization::Categorization; |
| use middle::traits; |
| use middle::ty::{self, Ty}; |
| use util::nodemap::NodeMap; |
| |
| use rustc_front::hir; |
| use syntax::ast; |
| use syntax::codemap::Span; |
| use syntax::feature_gate::UnstableFeatures; |
| use rustc_front::intravisit::{self, FnKind, Visitor}; |
| |
| use std::collections::hash_map::Entry; |
| use std::cmp::Ordering; |
| |
| // Const qualification, from partial to completely promotable. |
| bitflags! { |
| #[derive(RustcEncodable, RustcDecodable)] |
| flags ConstQualif: u8 { |
| // Inner mutability (can not be placed behind a reference) or behind |
| // &mut in a non-global expression. Can be copied from static memory. |
| const MUTABLE_MEM = 1 << 0, |
| // Constant value with a type that implements Drop. Can be copied |
| // from static memory, similar to MUTABLE_MEM. |
| const NEEDS_DROP = 1 << 1, |
| // Even if the value can be placed in static memory, copying it from |
| // there is more expensive than in-place instantiation, and/or it may |
| // be too large. This applies to [T; N] and everything containing it. |
| // N.B.: references need to clear this flag to not end up on the stack. |
| const PREFER_IN_PLACE = 1 << 2, |
| // May use more than 0 bytes of memory, doesn't impact the constness |
| // directly, but is not allowed to be borrowed mutably in a constant. |
| const NON_ZERO_SIZED = 1 << 3, |
| // Actually borrowed, has to always be in static memory. Does not |
| // propagate, and requires the expression to behave like a 'static |
| // lvalue. The set of expressions with this flag is the minimum |
| // that have to be promoted. |
| const HAS_STATIC_BORROWS = 1 << 4, |
| // Invalid const for miscellaneous reasons (e.g. not implemented). |
| const NOT_CONST = 1 << 5, |
| |
| // Borrowing the expression won't produce &'static T if any of these |
| // bits are set, though the value could be copied from static memory |
| // if `NOT_CONST` isn't set. |
| const NON_STATIC_BORROWS = ConstQualif::MUTABLE_MEM.bits | |
| ConstQualif::NEEDS_DROP.bits | |
| ConstQualif::NOT_CONST.bits |
| } |
| } |
| |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| enum Mode { |
| Const, |
| ConstFn, |
| Static, |
| StaticMut, |
| |
| // An expression that occurs outside of any constant context |
| // (i.e. `const`, `static`, array lengths, etc.). The value |
| // can be variable at runtime, but will be promotable to |
| // static memory if we can prove it is actually constant. |
| Var, |
| } |
| |
| struct CheckCrateVisitor<'a, 'tcx: 'a> { |
| tcx: &'a ty::ctxt<'tcx>, |
| mode: Mode, |
| qualif: ConstQualif, |
| rvalue_borrows: NodeMap<hir::Mutability> |
| } |
| |
| impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> { |
| fn with_mode<F, R>(&mut self, mode: Mode, f: F) -> R where |
| F: FnOnce(&mut CheckCrateVisitor<'a, 'tcx>) -> R, |
| { |
| let (old_mode, old_qualif) = (self.mode, self.qualif); |
| self.mode = mode; |
| self.qualif = ConstQualif::empty(); |
| let r = f(self); |
| self.mode = old_mode; |
| self.qualif = old_qualif; |
| r |
| } |
| |
| fn with_euv<'b, F, R>(&'b mut self, item_id: Option<ast::NodeId>, f: F) -> R where |
| F: for<'t> FnOnce(&mut euv::ExprUseVisitor<'b, 't, 'b, 'tcx>) -> R, |
| { |
| let param_env = match item_id { |
| Some(item_id) => ty::ParameterEnvironment::for_item(self.tcx, item_id), |
| None => self.tcx.empty_parameter_environment() |
| }; |
| |
| let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, Some(param_env), false); |
| |
| f(&mut euv::ExprUseVisitor::new(self, &infcx)) |
| } |
| |
| fn global_expr(&mut self, mode: Mode, expr: &hir::Expr) -> ConstQualif { |
| assert!(mode != Mode::Var); |
| match self.tcx.const_qualif_map.borrow_mut().entry(expr.id) { |
| Entry::Occupied(entry) => return *entry.get(), |
| Entry::Vacant(entry) => { |
| // Prevent infinite recursion on re-entry. |
| entry.insert(ConstQualif::empty()); |
| } |
| } |
| self.with_mode(mode, |this| { |
| this.with_euv(None, |euv| euv.consume_expr(expr)); |
| this.visit_expr(expr); |
| this.qualif |
| }) |
| } |
| |
| fn fn_like(&mut self, |
| fk: FnKind, |
| fd: &hir::FnDecl, |
| b: &hir::Block, |
| s: Span, |
| fn_id: ast::NodeId) |
| -> ConstQualif { |
| match self.tcx.const_qualif_map.borrow_mut().entry(fn_id) { |
| Entry::Occupied(entry) => return *entry.get(), |
| Entry::Vacant(entry) => { |
| // Prevent infinite recursion on re-entry. |
| entry.insert(ConstQualif::empty()); |
| } |
| } |
| |
| let mode = match fk { |
| FnKind::ItemFn(_, _, _, hir::Constness::Const, _, _) => { |
| Mode::ConstFn |
| } |
| FnKind::Method(_, m, _) => { |
| if m.constness == hir::Constness::Const { |
| Mode::ConstFn |
| } else { |
| Mode::Var |
| } |
| } |
| _ => Mode::Var |
| }; |
| |
| let qualif = self.with_mode(mode, |this| { |
| this.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b)); |
| intravisit::walk_fn(this, fk, fd, b, s); |
| this.qualif |
| }); |
| |
| // Keep only bits that aren't affected by function body (NON_ZERO_SIZED), |
| // and bits that don't change semantics, just optimizations (PREFER_IN_PLACE). |
| let qualif = qualif & (ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE); |
| |
| self.tcx.const_qualif_map.borrow_mut().insert(fn_id, qualif); |
| qualif |
| } |
| |
| fn add_qualif(&mut self, qualif: ConstQualif) { |
| self.qualif = self.qualif | qualif; |
| } |
| |
| /// Returns true if the call is to a const fn or method. |
| fn handle_const_fn_call(&mut self, |
| expr: &hir::Expr, |
| def_id: DefId, |
| ret_ty: Ty<'tcx>) |
| -> bool { |
| if let Some(fn_like) = const_eval::lookup_const_fn_by_id(self.tcx, def_id) { |
| if |
| // we are in a static/const initializer |
| self.mode != Mode::Var && |
| |
| // feature-gate is not enabled |
| !self.tcx.sess.features.borrow().const_fn && |
| |
| // this doesn't come from a macro that has #[allow_internal_unstable] |
| !self.tcx.sess.codemap().span_allows_unstable(expr.span) |
| { |
| let mut err = self.tcx.sess.struct_span_err( |
| expr.span, |
| "const fns are an unstable feature"); |
| fileline_help!( |
| &mut err, |
| expr.span, |
| "in Nightly builds, add `#![feature(const_fn)]` to the crate \ |
| attributes to enable"); |
| err.emit(); |
| } |
| |
| let qualif = self.fn_like(fn_like.kind(), |
| fn_like.decl(), |
| fn_like.body(), |
| fn_like.span(), |
| fn_like.id()); |
| self.add_qualif(qualif); |
| |
| if ret_ty.type_contents(self.tcx).interior_unsafe() { |
| self.add_qualif(ConstQualif::MUTABLE_MEM); |
| } |
| |
| true |
| } else { |
| false |
| } |
| } |
| |
| fn record_borrow(&mut self, id: ast::NodeId, mutbl: hir::Mutability) { |
| match self.rvalue_borrows.entry(id) { |
| Entry::Occupied(mut entry) => { |
| // Merge the two borrows, taking the most demanding |
| // one, mutability-wise. |
| if mutbl == hir::MutMutable { |
| entry.insert(mutbl); |
| } |
| } |
| Entry::Vacant(entry) => { |
| entry.insert(mutbl); |
| } |
| } |
| } |
| |
| fn msg(&self) -> &'static str { |
| match self.mode { |
| Mode::Const => "constant", |
| Mode::ConstFn => "constant function", |
| Mode::StaticMut | Mode::Static => "static", |
| Mode::Var => unreachable!(), |
| } |
| } |
| |
| fn check_static_mut_type(&self, e: &hir::Expr) { |
| let node_ty = self.tcx.node_id_to_type(e.id); |
| let tcontents = node_ty.type_contents(self.tcx); |
| |
| let suffix = if tcontents.has_dtor() { |
| "destructors" |
| } else if tcontents.owns_owned() { |
| "boxes" |
| } else { |
| return |
| }; |
| |
| span_err!(self.tcx.sess, e.span, E0397, |
| "mutable statics are not allowed to have {}", suffix); |
| } |
| |
| fn check_static_type(&self, e: &hir::Expr) { |
| let ty = self.tcx.node_id_to_type(e.id); |
| let infcx = infer::new_infer_ctxt(self.tcx, &self.tcx.tables, None, false); |
| let cause = traits::ObligationCause::new(e.span, e.id, traits::SharedStatic); |
| let mut fulfill_cx = infcx.fulfillment_cx.borrow_mut(); |
| fulfill_cx.register_builtin_bound(&infcx, ty, ty::BoundSync, cause); |
| match fulfill_cx.select_all_or_error(&infcx) { |
| Ok(()) => { }, |
| Err(ref errors) => { |
| traits::report_fulfillment_errors(&infcx, errors); |
| } |
| } |
| } |
| } |
| |
| impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> { |
| fn visit_item(&mut self, i: &hir::Item) { |
| debug!("visit_item(item={})", self.tcx.map.node_to_string(i.id)); |
| assert_eq!(self.mode, Mode::Var); |
| match i.node { |
| hir::ItemStatic(_, hir::MutImmutable, ref expr) => { |
| self.check_static_type(&**expr); |
| self.global_expr(Mode::Static, &**expr); |
| } |
| hir::ItemStatic(_, hir::MutMutable, ref expr) => { |
| self.check_static_mut_type(&**expr); |
| self.global_expr(Mode::StaticMut, &**expr); |
| } |
| hir::ItemConst(_, ref expr) => { |
| self.global_expr(Mode::Const, &**expr); |
| } |
| hir::ItemEnum(ref enum_definition, _) => { |
| for var in &enum_definition.variants { |
| if let Some(ref ex) = var.node.disr_expr { |
| self.global_expr(Mode::Const, &**ex); |
| } |
| } |
| } |
| _ => { |
| intravisit::walk_item(self, i); |
| } |
| } |
| } |
| |
| fn visit_trait_item(&mut self, t: &'v hir::TraitItem) { |
| match t.node { |
| hir::ConstTraitItem(_, ref default) => { |
| if let Some(ref expr) = *default { |
| self.global_expr(Mode::Const, &*expr); |
| } else { |
| intravisit::walk_trait_item(self, t); |
| } |
| } |
| _ => self.with_mode(Mode::Var, |v| intravisit::walk_trait_item(v, t)), |
| } |
| } |
| |
| fn visit_impl_item(&mut self, i: &'v hir::ImplItem) { |
| match i.node { |
| hir::ImplItemKind::Const(_, ref expr) => { |
| self.global_expr(Mode::Const, &*expr); |
| } |
| _ => self.with_mode(Mode::Var, |v| intravisit::walk_impl_item(v, i)), |
| } |
| } |
| |
| fn visit_fn(&mut self, |
| fk: FnKind<'v>, |
| fd: &'v hir::FnDecl, |
| b: &'v hir::Block, |
| s: Span, |
| fn_id: ast::NodeId) { |
| self.fn_like(fk, fd, b, s, fn_id); |
| } |
| |
| fn visit_pat(&mut self, p: &hir::Pat) { |
| match p.node { |
| hir::PatLit(ref lit) => { |
| self.global_expr(Mode::Const, &**lit); |
| } |
| hir::PatRange(ref start, ref end) => { |
| self.global_expr(Mode::Const, &**start); |
| self.global_expr(Mode::Const, &**end); |
| |
| match const_eval::compare_lit_exprs(self.tcx, start, end) { |
| Some(Ordering::Less) | |
| Some(Ordering::Equal) => {} |
| Some(Ordering::Greater) => { |
| span_err!(self.tcx.sess, start.span, E0030, |
| "lower range bound must be less than or equal to upper"); |
| } |
| None => { |
| self.tcx.sess.delay_span_bug(start.span, |
| "non-constant path in constant expr"); |
| } |
| } |
| } |
| _ => intravisit::walk_pat(self, p) |
| } |
| } |
| |
| fn visit_block(&mut self, block: &hir::Block) { |
| // Check all statements in the block |
| for stmt in &block.stmts { |
| match stmt.node { |
| hir::StmtDecl(ref decl, _) => { |
| match decl.node { |
| hir::DeclLocal(_) => {}, |
| // Item statements are allowed |
| hir::DeclItem(_) => continue |
| } |
| } |
| hir::StmtExpr(_, _) => {}, |
| hir::StmtSemi(_, _) => {}, |
| } |
| self.add_qualif(ConstQualif::NOT_CONST); |
| // anything else should have been caught by check_const_fn |
| assert_eq!(self.mode, Mode::Var); |
| } |
| intravisit::walk_block(self, block); |
| } |
| |
| fn visit_expr(&mut self, ex: &hir::Expr) { |
| let mut outer = self.qualif; |
| self.qualif = ConstQualif::empty(); |
| |
| let node_ty = self.tcx.node_id_to_type(ex.id); |
| check_expr(self, ex, node_ty); |
| check_adjustments(self, ex); |
| |
| // Special-case some expressions to avoid certain flags bubbling up. |
| match ex.node { |
| hir::ExprCall(ref callee, ref args) => { |
| for arg in args { |
| self.visit_expr(&**arg) |
| } |
| |
| let inner = self.qualif; |
| self.visit_expr(&**callee); |
| // The callee's size doesn't count in the call. |
| let added = self.qualif - inner; |
| self.qualif = inner | (added - ConstQualif::NON_ZERO_SIZED); |
| } |
| hir::ExprRepeat(ref element, _) => { |
| self.visit_expr(&**element); |
| // The count is checked elsewhere (typeck). |
| let count = match node_ty.sty { |
| ty::TyArray(_, n) => n, |
| _ => unreachable!() |
| }; |
| // [element; 0] is always zero-sized. |
| if count == 0 { |
| self.qualif.remove(ConstQualif::NON_ZERO_SIZED | ConstQualif::PREFER_IN_PLACE); |
| } |
| } |
| hir::ExprMatch(ref discr, ref arms, _) => { |
| // Compute the most demanding borrow from all the arms' |
| // patterns and set that on the discriminator. |
| let mut borrow = None; |
| for pat in arms.iter().flat_map(|arm| &arm.pats) { |
| let pat_borrow = self.rvalue_borrows.remove(&pat.id); |
| match (borrow, pat_borrow) { |
| (None, _) | (_, Some(hir::MutMutable)) => { |
| borrow = pat_borrow; |
| } |
| _ => {} |
| } |
| } |
| if let Some(mutbl) = borrow { |
| self.record_borrow(discr.id, mutbl); |
| } |
| intravisit::walk_expr(self, ex); |
| } |
| // Division by zero and overflow checking. |
| hir::ExprBinary(op, _, _) => { |
| intravisit::walk_expr(self, ex); |
| let div_or_rem = op.node == hir::BiDiv || op.node == hir::BiRem; |
| match node_ty.sty { |
| ty::TyUint(_) | ty::TyInt(_) if div_or_rem => { |
| if !self.qualif.intersects(ConstQualif::NOT_CONST) { |
| match const_eval::eval_const_expr_partial( |
| self.tcx, ex, ExprTypeChecked, None) { |
| Ok(_) => {} |
| Err(ConstEvalErr { kind: IndexOpFeatureGated, ..}) => {}, |
| Err(msg) => { |
| self.tcx.sess.add_lint(::lint::builtin::CONST_ERR, ex.id, |
| msg.span, |
| msg.description().into_owned()) |
| } |
| } |
| } |
| } |
| _ => {} |
| } |
| } |
| _ => intravisit::walk_expr(self, ex) |
| } |
| |
| // Handle borrows on (or inside the autorefs of) this expression. |
| match self.rvalue_borrows.remove(&ex.id) { |
| Some(hir::MutImmutable) => { |
| // 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 the type doesn't have interior mutability, then `ConstQualif::MUTABLE_MEM` has |
| // propagated from another error, so erroring again would be just noise. |
| let tc = node_ty.type_contents(self.tcx); |
| if self.qualif.intersects(ConstQualif::MUTABLE_MEM) && tc.interior_unsafe() { |
| outer = outer | ConstQualif::NOT_CONST; |
| if self.mode != Mode::Var { |
| span_err!(self.tcx.sess, ex.span, E0492, |
| "cannot borrow a constant which contains \ |
| interior mutability, create a static instead"); |
| } |
| } |
| // If the reference has to be 'static, avoid in-place initialization |
| // as that will end up pointing to the stack instead. |
| if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) { |
| self.qualif = self.qualif - ConstQualif::PREFER_IN_PLACE; |
| self.add_qualif(ConstQualif::HAS_STATIC_BORROWS); |
| } |
| } |
| Some(hir::MutMutable) => { |
| // `&mut expr` means expr could be mutated, unless it's zero-sized. |
| if self.qualif.intersects(ConstQualif::NON_ZERO_SIZED) { |
| if self.mode == Mode::Var { |
| outer = outer | ConstQualif::NOT_CONST; |
| self.add_qualif(ConstQualif::MUTABLE_MEM); |
| } else { |
| span_err!(self.tcx.sess, ex.span, E0017, |
| "references in {}s may only refer \ |
| to immutable values", self.msg()) |
| } |
| } |
| if !self.qualif.intersects(ConstQualif::NON_STATIC_BORROWS) { |
| self.add_qualif(ConstQualif::HAS_STATIC_BORROWS); |
| } |
| } |
| None => {} |
| } |
| self.tcx.const_qualif_map.borrow_mut().insert(ex.id, self.qualif); |
| // Don't propagate certain flags. |
| self.qualif = outer | (self.qualif - ConstQualif::HAS_STATIC_BORROWS); |
| } |
| } |
| |
| /// This function is used to enforce the constraints on |
| /// const/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 const/static item, it is qualified for promotion |
| /// instead of producing errors. |
| fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, |
| e: &hir::Expr, node_ty: Ty<'tcx>) { |
| match node_ty.sty { |
| ty::TyStruct(def, _) | |
| ty::TyEnum(def, _) if def.has_dtor() => { |
| v.add_qualif(ConstQualif::NEEDS_DROP); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0493, |
| "{}s are not allowed to have destructors", |
| v.msg()); |
| } |
| } |
| _ => {} |
| } |
| |
| let method_call = ty::MethodCall::expr(e.id); |
| match e.node { |
| hir::ExprUnary(..) | |
| hir::ExprBinary(..) | |
| hir::ExprIndex(..) if v.tcx.tables.borrow().method_map.contains_key(&method_call) => { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0011, |
| "user-defined operators are not allowed in {}s", v.msg()); |
| } |
| } |
| hir::ExprBox(_) => { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0010, |
| "allocations are not allowed in {}s", v.msg()); |
| } |
| } |
| hir::ExprUnary(op, ref inner) => { |
| match v.tcx.node_id_to_type(inner.id).sty { |
| ty::TyRawPtr(_) => { |
| assert!(op == hir::UnDeref); |
| |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0396, |
| "raw pointers cannot be dereferenced in {}s", v.msg()); |
| } |
| } |
| _ => {} |
| } |
| } |
| hir::ExprBinary(op, ref lhs, _) => { |
| match v.tcx.node_id_to_type(lhs.id).sty { |
| ty::TyRawPtr(_) => { |
| assert!(op.node == hir::BiEq || op.node == hir::BiNe || |
| op.node == hir::BiLe || op.node == hir::BiLt || |
| op.node == hir::BiGe || op.node == hir::BiGt); |
| |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0395, |
| "raw pointers cannot be compared in {}s", v.msg()); |
| } |
| } |
| _ => {} |
| } |
| } |
| hir::ExprCast(ref from, _) => { |
| debug!("Checking const cast(id={})", from.id); |
| match v.tcx.cast_kinds.borrow().get(&from.id) { |
| None => v.tcx.sess.span_bug(e.span, "no kind for cast"), |
| Some(&CastKind::PtrAddrCast) | Some(&CastKind::FnPtrAddrCast) => { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0018, |
| "raw pointers cannot be cast to integers in {}s", v.msg()); |
| } |
| } |
| _ => {} |
| } |
| } |
| hir::ExprPath(..) => { |
| let def = v.tcx.def_map.borrow().get(&e.id).map(|d| d.full_def()); |
| match def { |
| Some(def::DefVariant(_, _, _)) => { |
| // Count the discriminator or function pointer. |
| v.add_qualif(ConstQualif::NON_ZERO_SIZED); |
| } |
| Some(def::DefStruct(_)) => { |
| if let ty::TyBareFn(..) = node_ty.sty { |
| // Count the function pointer. |
| v.add_qualif(ConstQualif::NON_ZERO_SIZED); |
| } |
| } |
| Some(def::DefFn(..)) | Some(def::DefMethod(..)) => { |
| // Count the function pointer. |
| v.add_qualif(ConstQualif::NON_ZERO_SIZED); |
| } |
| Some(def::DefStatic(..)) => { |
| match v.mode { |
| Mode::Static | Mode::StaticMut => {} |
| Mode::Const | Mode::ConstFn => { |
| span_err!(v.tcx.sess, e.span, E0013, |
| "{}s cannot refer to other statics, insert \ |
| an intermediate constant instead", v.msg()); |
| } |
| Mode::Var => v.add_qualif(ConstQualif::NOT_CONST) |
| } |
| } |
| Some(def::DefConst(did)) | |
| Some(def::DefAssociatedConst(did)) => { |
| if let Some(expr) = const_eval::lookup_const_by_id(v.tcx, did, |
| Some(e.id), |
| None) { |
| let inner = v.global_expr(Mode::Const, expr); |
| v.add_qualif(inner); |
| } |
| } |
| Some(def::DefLocal(..)) if v.mode == Mode::ConstFn => { |
| // Sadly, we can't determine whether the types are zero-sized. |
| v.add_qualif(ConstQualif::NOT_CONST | ConstQualif::NON_ZERO_SIZED); |
| } |
| def => { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| debug!("(checking const) found bad def: {:?}", def); |
| span_err!(v.tcx.sess, e.span, E0014, |
| "paths in {}s may only refer to constants \ |
| or functions", v.msg()); |
| } |
| } |
| } |
| } |
| hir::ExprCall(ref callee, _) => { |
| let mut callee = &**callee; |
| loop { |
| callee = match callee.node { |
| hir::ExprBlock(ref block) => match block.expr { |
| Some(ref tail) => &**tail, |
| None => break |
| }, |
| _ => break |
| }; |
| } |
| let def = v.tcx.def_map.borrow().get(&callee.id).map(|d| d.full_def()); |
| let is_const = match def { |
| Some(def::DefStruct(..)) => true, |
| Some(def::DefVariant(..)) => { |
| // Count the discriminator. |
| v.add_qualif(ConstQualif::NON_ZERO_SIZED); |
| true |
| } |
| Some(def::DefFn(did, _)) => { |
| v.handle_const_fn_call(e, did, node_ty) |
| } |
| Some(def::DefMethod(did)) => { |
| match v.tcx.impl_or_trait_item(did).container() { |
| ty::ImplContainer(_) => { |
| v.handle_const_fn_call(e, did, node_ty) |
| } |
| ty::TraitContainer(_) => false |
| } |
| } |
| _ => false |
| }; |
| if !is_const { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| // FIXME(#24111) Remove this check when const fn stabilizes |
| let (msg, note) = |
| if let UnstableFeatures::Disallow = v.tcx.sess.opts.unstable_features { |
| (format!("function calls in {}s are limited to \ |
| struct and enum constructors", |
| v.msg()), |
| Some("a limited form of compile-time function \ |
| evaluation is available on a nightly \ |
| compiler via `const fn`")) |
| } else { |
| (format!("function calls in {}s are limited \ |
| to constant functions, \ |
| struct and enum constructors", |
| v.msg()), |
| None) |
| }; |
| let mut err = struct_span_err!(v.tcx.sess, e.span, E0015, "{}", msg); |
| if let Some(note) = note { |
| err.span_note(e.span, note); |
| } |
| err.emit(); |
| } |
| } |
| } |
| hir::ExprMethodCall(..) => { |
| let method = v.tcx.tables.borrow().method_map[&method_call]; |
| let is_const = match v.tcx.impl_or_trait_item(method.def_id).container() { |
| ty::ImplContainer(_) => v.handle_const_fn_call(e, method.def_id, node_ty), |
| ty::TraitContainer(_) => false |
| }; |
| if !is_const { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0378, |
| "method calls in {}s are limited to \ |
| constant inherent methods", v.msg()); |
| } |
| } |
| } |
| hir::ExprStruct(..) => { |
| let did = v.tcx.def_map.borrow().get(&e.id).map(|def| def.def_id()); |
| if did == v.tcx.lang_items.unsafe_cell_type() { |
| v.add_qualif(ConstQualif::MUTABLE_MEM); |
| } |
| } |
| |
| hir::ExprLit(_) | |
| hir::ExprAddrOf(..) => { |
| v.add_qualif(ConstQualif::NON_ZERO_SIZED); |
| } |
| |
| hir::ExprRepeat(..) => { |
| v.add_qualif(ConstQualif::PREFER_IN_PLACE); |
| } |
| |
| hir::ExprClosure(..) => { |
| // Paths in constant contexts cannot refer to local variables, |
| // as there are none, and thus closures can't have upvars there. |
| if v.tcx.with_freevars(e.id, |fv| !fv.is_empty()) { |
| assert!(v.mode == Mode::Var, |
| "global closures can't capture anything"); |
| v.add_qualif(ConstQualif::NOT_CONST); |
| } |
| } |
| |
| hir::ExprBlock(_) | |
| hir::ExprIndex(..) | |
| hir::ExprField(..) | |
| hir::ExprTupField(..) | |
| hir::ExprVec(_) | |
| hir::ExprType(..) | |
| hir::ExprTup(..) => {} |
| |
| // Conditional control flow (possible to implement). |
| hir::ExprMatch(..) | |
| hir::ExprIf(..) | |
| |
| // Loops (not very meaningful in constants). |
| hir::ExprWhile(..) | |
| hir::ExprLoop(..) | |
| |
| // More control flow (also not very meaningful). |
| hir::ExprBreak(_) | |
| hir::ExprAgain(_) | |
| hir::ExprRet(_) | |
| |
| // Miscellaneous expressions that could be implemented. |
| hir::ExprRange(..) | |
| |
| // Expressions with side-effects. |
| hir::ExprAssign(..) | |
| hir::ExprAssignOp(..) | |
| hir::ExprInlineAsm(_) => { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0019, |
| "{} contains unimplemented expression type", v.msg()); |
| } |
| } |
| } |
| } |
| |
| /// Check the adjustments of an expression |
| fn check_adjustments<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr) { |
| match v.tcx.tables.borrow().adjustments.get(&e.id) { |
| None | |
| Some(&ty::adjustment::AdjustReifyFnPointer) | |
| Some(&ty::adjustment::AdjustUnsafeFnPointer) => {} |
| |
| Some(&ty::adjustment::AdjustDerefRef( |
| ty::adjustment::AutoDerefRef { autoderefs, .. } |
| )) => { |
| if (0..autoderefs as u32).any(|autoderef| { |
| v.tcx.is_overloaded_autoderef(e.id, autoderef) |
| }) { |
| v.add_qualif(ConstQualif::NOT_CONST); |
| if v.mode != Mode::Var { |
| span_err!(v.tcx.sess, e.span, E0400, |
| "user-defined dereference operators are not allowed in {}s", |
| v.msg()); |
| } |
| } |
| } |
| } |
| } |
| |
| pub fn check_crate(tcx: &ty::ctxt) { |
| tcx.visit_all_items_in_krate(DepNode::CheckConst, &mut CheckCrateVisitor { |
| tcx: tcx, |
| mode: Mode::Var, |
| qualif: ConstQualif::NOT_CONST, |
| rvalue_borrows: NodeMap() |
| }); |
| tcx.sess.abort_if_errors(); |
| } |
| |
| impl<'a, 'tcx> euv::Delegate<'tcx> for CheckCrateVisitor<'a, 'tcx> { |
| 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 { |
| Categorization::StaticItem => { |
| if self.mode != Mode::Var { |
| // 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. |
| span_err!(self.tcx.sess, consume_span, E0394, |
| "cannot refer to other statics by value, use the \ |
| address-of operator or a constant instead"); |
| } |
| break; |
| } |
| Categorization::Deref(ref cmt, _, _) | |
| Categorization::Downcast(ref cmt, _) | |
| Categorization::Interior(ref cmt, _) => cur = cmt, |
| |
| Categorization::Rvalue(..) | |
| Categorization::Upvar(..) | |
| Categorization::Local(..) => break |
| } |
| } |
| } |
| fn borrow(&mut self, |
| borrow_id: ast::NodeId, |
| borrow_span: Span, |
| cmt: mc::cmt<'tcx>, |
| _loan_region: ty::Region, |
| bk: ty::BorrowKind, |
| loan_cause: euv::LoanCause) |
| { |
| // Kind of hacky, but we allow Unsafe coercions in constants. |
| // These occur when we convert a &T or *T to a *U, as well as |
| // when making a thin pointer (e.g., `*T`) into a fat pointer |
| // (e.g., `*Trait`). |
| match loan_cause { |
| euv::LoanCause::AutoUnsafe => { |
| return; |
| } |
| _ => { } |
| } |
| |
| let mut cur = &cmt; |
| let mut is_interior = false; |
| loop { |
| match cur.cat { |
| Categorization::Rvalue(..) => { |
| if loan_cause == euv::MatchDiscriminant { |
| // Ignore the dummy immutable borrow created by EUV. |
| break; |
| } |
| let mutbl = bk.to_mutbl_lossy(); |
| if mutbl == hir::MutMutable && self.mode == Mode::StaticMut { |
| // Mutable slices are the only `&mut` allowed in |
| // globals, but only in `static mut`, nowhere else. |
| // FIXME: This exception is really weird... there isn't |
| // any fundamental reason to restrict this based on |
| // type of the expression. `&mut [1]` has exactly the |
| // same representation as &mut 1. |
| match cmt.ty.sty { |
| ty::TyArray(_, _) | ty::TySlice(_) => break, |
| _ => {} |
| } |
| } |
| self.record_borrow(borrow_id, mutbl); |
| break; |
| } |
| Categorization::StaticItem => { |
| if is_interior && self.mode != Mode::Var { |
| // 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. |
| span_err!(self.tcx.sess, borrow_span, E0494, |
| "cannot refer to the interior of another \ |
| static, use a constant instead"); |
| } |
| break; |
| } |
| Categorization::Deref(ref cmt, _, _) | |
| Categorization::Downcast(ref cmt, _) | |
| Categorization::Interior(ref cmt, _) => { |
| is_interior = true; |
| cur = cmt; |
| } |
| |
| Categorization::Upvar(..) | |
| Categorization::Local(..) => 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, |
| _: &hir::Pat, |
| _: mc::cmt, |
| _: euv::MatchMode) {} |
| |
| fn consume_pat(&mut self, |
| _consume_pat: &hir::Pat, |
| _cmt: mc::cmt, |
| _mode: euv::ConsumeMode) {} |
| } |