From c124deca7b3d93e72b5d849b392a5bea83eacfb5 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 21 Jan 2016 10:52:37 +0100 Subject: move more checks out of librustc --- src/librustc_passes/consts.rs | 916 ++++++++++++++++++++++++++++++++ src/librustc_passes/diagnostics.rs | 542 +++++++++++++++++++ src/librustc_passes/lib.rs | 7 + src/librustc_passes/loops.rs | 80 +++ src/librustc_passes/rvalues.rs | 105 ++++ src/librustc_passes/static_recursion.rs | 290 ++++++++++ 6 files changed, 1940 insertions(+) create mode 100644 src/librustc_passes/consts.rs create mode 100644 src/librustc_passes/loops.rs create mode 100644 src/librustc_passes/rvalues.rs create mode 100644 src/librustc_passes/static_recursion.rs (limited to 'src/librustc_passes') diff --git a/src/librustc_passes/consts.rs b/src/librustc_passes/consts.rs new file mode 100644 index 00000000000..60cc658eeca --- /dev/null +++ b/src/librustc_passes/consts.rs @@ -0,0 +1,916 @@ +// 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 or the MIT license +// , 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 rustc::dep_graph::DepNode; +use rustc::middle::ty::cast::{CastKind}; +use rustc::middle::const_eval::{self, ConstEvalErr}; +use rustc::middle::const_eval::ErrKind::IndexOpFeatureGated; +use rustc::middle::const_eval::EvalHint::ExprTypeChecked; +use rustc::middle::def::Def; +use rustc::middle::def_id::DefId; +use rustc::middle::expr_use_visitor as euv; +use rustc::middle::infer; +use rustc::middle::mem_categorization as mc; +use rustc::middle::mem_categorization::Categorization; +use rustc::middle::traits; +use rustc::middle::ty::{self, Ty}; +use rustc::util::nodemap::NodeMap; +use rustc::middle::const_qualif::ConstQualif; +use rustc::lint::builtin::CONST_ERR; + +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; + +#[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 +} + +impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> { + fn with_mode(&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, 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)); + + 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); + 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(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::Variant(..)) => { + // Count the discriminator or function pointer. + v.add_qualif(ConstQualif::NON_ZERO_SIZED); + } + Some(Def::Struct(..)) => { + if let ty::TyBareFn(..) = node_ty.sty { + // Count the function pointer. + v.add_qualif(ConstQualif::NON_ZERO_SIZED); + } + } + Some(Def::Fn(..)) | Some(Def::Method(..)) => { + // Count the function pointer. + v.add_qualif(ConstQualif::NON_ZERO_SIZED); + } + Some(Def::Static(..)) => { + 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::Const(did)) | + Some(Def::AssociatedConst(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::Local(..)) 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::Struct(..)) => true, + Some(Def::Variant(..)) => { + // Count the discriminator. + v.add_qualif(ConstQualif::NON_ZERO_SIZED); + true + } + Some(Def::Fn(did)) => { + v.handle_const_fn_call(e, did, node_ty) + } + Some(Def::Method(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) {} +} diff --git a/src/librustc_passes/diagnostics.rs b/src/librustc_passes/diagnostics.rs index 380eada18a1..2c08cbd3233 100644 --- a/src/librustc_passes/diagnostics.rs +++ b/src/librustc_passes/diagnostics.rs @@ -11,6 +11,108 @@ #![allow(non_snake_case)] register_long_diagnostics! { + +E0010: r##" +The value of statics and constants must be known at compile time, and they live +for the entire lifetime of a program. Creating a boxed value allocates memory on +the heap at runtime, and therefore cannot be done at compile time. Erroneous +code example: + +``` +#![feature(box_syntax)] + +const CON : Box = box 0; +``` +"##, + +E0011: r##" +Initializers for constants and statics are evaluated at compile time. +User-defined operators rely on user-defined functions, which cannot be evaluated +at compile time. + +Bad example: + +``` +use std::ops::Index; + +struct Foo { a: u8 } + +impl Index for Foo { + type Output = u8; + + fn index<'a>(&'a self, idx: u8) -> &'a u8 { &self.a } +} + +const a: Foo = Foo { a: 0u8 }; +const b: u8 = a[0]; // Index trait is defined by the user, bad! +``` + +Only operators on builtin types are allowed. + +Example: + +``` +const a: &'static [i32] = &[1, 2, 3]; +const b: i32 = a[0]; // Good! +``` +"##, + +E0013: r##" +Static and const variables can refer to other const variables. But a const +variable cannot refer to a static variable. For example, `Y` cannot refer to `X` +here: + +``` +static X: i32 = 42; +const Y: i32 = X; +``` + +To fix this, the value can be extracted as a const and then used: + +``` +const A: i32 = 42; +static X: i32 = A; +const Y: i32 = A; +``` +"##, + +E0014: r##" +Constants can only be initialized by a constant value or, in a future +version of Rust, a call to a const function. This error indicates the use +of a path (like a::b, or x) denoting something other than one of these +allowed items. Example: + +``` +const FOO: i32 = { let x = 0; x }; // 'x' isn't a constant nor a function! +``` + +To avoid it, you have to replace the non-constant value: + +``` +const FOO: i32 = { const X : i32 = 0; X }; +// or even: +const FOO: i32 = { 0 }; // but brackets are useless here +``` +"##, + +// FIXME(#24111) Change the language here when const fn stabilizes +E0015: r##" +The only functions that can be called in static or constant expressions are +`const` functions, and struct/enum constructors. `const` functions are only +available on a nightly compiler. Rust currently does not support more general +compile-time function execution. + +``` +const FOO: Option = Some(1); // enum constructor +struct Bar {x: u8} +const BAR: Bar = Bar {x: 1}; // struct constructor +``` + +See [RFC 911] for more details on the design of `const fn`s. + +[RFC 911]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md +"##, + E0016: r##" Blocks in constants may only contain items (such as constant, function definition, etc...) and a tail expression. Example: @@ -26,6 +128,86 @@ const FOO: i32 = { const X : i32 = 0; X }; ``` "##, +E0017: r##" +References in statics and constants may only refer to immutable values. Example: + +``` +static X: i32 = 1; +const C: i32 = 2; + +// these three are not allowed: +const CR: &'static mut i32 = &mut C; +static STATIC_REF: &'static mut i32 = &mut X; +static CONST_REF: &'static mut i32 = &mut C; +``` + +Statics are shared everywhere, and if they refer to mutable data one might +violate memory safety since holding multiple mutable references to shared data +is not allowed. + +If you really want global mutable state, try using `static mut` or a global +`UnsafeCell`. +"##, + +E0018: r##" +The value of static and const variables must be known at compile time. You +can't cast a pointer as an integer because we can't know what value the +address will take. + +However, pointers to other constants' addresses are allowed in constants, +example: + +``` +const X: u32 = 50; +const Y: *const u32 = &X; +``` + +Therefore, casting one of these non-constant pointers to an integer results +in a non-constant integer which lead to this error. Example: + +``` +const X: u32 = 1; +const Y: usize = &X as *const u32 as usize; +println!("{}", Y); +``` +"##, + +E0019: r##" +A function call isn't allowed in the const's initialization expression +because the expression's value must be known at compile-time. Example of +erroneous code: + +``` +enum Test { + V1 +} + +impl Test { + fn test(&self) -> i32 { + 12 + } +} + +fn main() { + const FOO: Test = Test::V1; + + const A: i32 = FOO.test(); // You can't call Test::func() here ! +} +``` + +Remember: you can't use a function call inside a const's initialization +expression! However, you can totally use it anywhere else: + +``` +fn main() { + const FOO: Test = Test::V1; + + FOO.func(); // here is good + let x = FOO.func(); // or even here! +} +``` +"##, + E0022: r##" Constant functions are not allowed to mutate anything. Thus, binding to an argument with a mutable pattern is not allowed. For example, @@ -43,6 +225,366 @@ you need to mutate the argument, try lazily initializing a global variable instead of using a `const fn`, or refactoring the code to a functional style to avoid mutation if possible. "##, + +E0030: r##" +When matching against a range, the compiler verifies that the range is +non-empty. Range patterns include both end-points, so this is equivalent to +requiring the start of the range to be less than or equal to the end of the +range. + +For example: + +``` +match 5u32 { + // This range is ok, albeit pointless. + 1 ... 1 => ... + // This range is empty, and the compiler can tell. + 1000 ... 5 => ... +} +``` +"##, + +E0161: r##" +In Rust, you can only move a value when its size is known at compile time. + +To work around this restriction, consider "hiding" the value behind a reference: +either `&x` or `&mut x`. Since a reference has a fixed size, this lets you move +it around as usual. +"##, + +E0265: r##" +This error indicates that a static or constant references itself. +All statics and constants need to resolve to a value in an acyclic manner. + +For example, neither of the following can be sensibly compiled: + +``` +const X: u32 = X; +``` + +``` +const X: u32 = Y; +const Y: u32 = X; +``` +"##, + +E0267: r##" +This error indicates the use of a loop keyword (`break` or `continue`) inside a +closure but outside of any loop. Erroneous code example: + +``` +let w = || { break; }; // error: `break` inside of a closure +``` + +`break` and `continue` keywords can be used as normal inside closures as long as +they are also contained within a loop. To halt the execution of a closure you +should instead use a return statement. Example: + +``` +let w = || { + for _ in 0..10 { + break; + } +}; + +w(); +``` +"##, + +E0268: r##" +This error indicates the use of a loop keyword (`break` or `continue`) outside +of a loop. Without a loop to break out of or continue in, no sensible action can +be taken. Erroneous code example: + +``` +fn some_func() { + break; // error: `break` outside of loop +} +``` + +Please verify that you are using `break` and `continue` only in loops. Example: + +``` +fn some_func() { + for _ in 0..10 { + break; // ok! + } +} +``` +"##, + +E0378: r##" +Method calls that aren't calls to inherent `const` methods are disallowed +in statics, constants, and constant functions. + +For example: + +``` +const BAZ: i32 = Foo(25).bar(); // error, `bar` isn't `const` + +struct Foo(i32); + +impl Foo { + const fn foo(&self) -> i32 { + self.bar() // error, `bar` isn't `const` + } + + fn bar(&self) -> i32 { self.0 } +} +``` + +For more information about `const fn`'s, see [RFC 911]. + +[RFC 911]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md +"##, + +E0394: r##" +From [RFC 246]: + + > It is invalid for a static to reference another static by value. It is + > required that all references be borrowed. + +[RFC 246]: https://github.com/rust-lang/rfcs/pull/246 +"##, + +E0395: r##" +The value assigned to a constant expression must be known at compile time, +which is not the case when comparing raw pointers. Erroneous code example: + +``` +static foo: i32 = 42; +static bar: i32 = 43; + +static baz: bool = { (&foo as *const i32) == (&bar as *const i32) }; +// error: raw pointers cannot be compared in statics! +``` + +Please check that the result of the comparison can be determined at compile time +or isn't assigned to a constant expression. Example: + +``` +static foo: i32 = 42; +static bar: i32 = 43; + +let baz: bool = { (&foo as *const i32) == (&bar as *const i32) }; +// baz isn't a constant expression so it's ok +``` +"##, + +E0396: r##" +The value assigned to a constant expression must be known at compile time, +which is not the case when dereferencing raw pointers. Erroneous code +example: + +``` +const foo: i32 = 42; +const baz: *const i32 = (&foo as *const i32); + +const deref: i32 = *baz; +// error: raw pointers cannot be dereferenced in constants +``` + +To fix this error, please do not assign this value to a constant expression. +Example: + +``` +const foo: i32 = 42; +const baz: *const i32 = (&foo as *const i32); + +unsafe { let deref: i32 = *baz; } +// baz isn't a constant expression so it's ok +``` + +You'll also note that this assignment must be done in an unsafe block! +"##, + +E0397: r##" +It is not allowed for a mutable static to allocate or have destructors. For +example: + +``` +// error: mutable statics are not allowed to have boxes +static mut FOO: Option> = None; + +// error: mutable statics are not allowed to have destructors +static mut BAR: Option> = None; +``` +"##, + +E0400: r##" +A user-defined dereference was attempted in an invalid context. Erroneous +code example: + +``` +use std::ops::Deref; + +struct A; + +impl Deref for A { + type Target = str; + + fn deref(&self)-> &str { "foo" } +} + +const S: &'static str = &A; +// error: user-defined dereference operators are not allowed in constants + +fn main() { + let foo = S; +} +``` + +You cannot directly use a dereference operation whilst initializing a constant +or a static. To fix this error, restructure your code to avoid this dereference, +perhaps moving it inline: + +``` +use std::ops::Deref; + +struct A; + +impl Deref for A { + type Target = str; + + fn deref(&self)-> &str { "foo" } +} + +fn main() { + let foo : &str = &A; +} +``` +"##, + +E0492: r##" +A borrow of a constant containing interior mutability was attempted. Erroneous +code example: + +``` +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; + +const A: AtomicUsize = ATOMIC_USIZE_INIT; +static B: &'static AtomicUsize = &A; +// error: cannot borrow a constant which contains interior mutability, create a +// static instead +``` + +A `const` represents a constant value that should never change. If one takes +a `&` reference to the constant, then one is taking a pointer to some memory +location containing the value. Normally this is perfectly fine: most values +can't be changed via a shared `&` pointer, but interior mutability would allow +it. That is, a constant value could be mutated. On the other hand, a `static` is +explicitly a single memory location, which can be mutated at will. + +So, in order to solve this error, either use statics which are `Sync`: + +``` +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; + +static A: AtomicUsize = ATOMIC_USIZE_INIT; +static B: &'static AtomicUsize = &A; // ok! +``` + +You can also have this error while using a cell type: + +``` +#![feature(const_fn)] + +use std::cell::Cell; + +const A: Cell = Cell::new(1); +const B: &'static Cell = &A; +// error: cannot borrow a constant which contains interior mutability, create +// a static instead + +// or: +struct C { a: Cell } + +const D: C = C { a: Cell::new(1) }; +const E: &'static Cell = &D.a; // error + +// or: +const F: &'static C = &D; // error +``` + +This is because cell types do operations that are not thread-safe. Due to this, +they don't implement Sync and thus can't be placed in statics. In this +case, `StaticMutex` would work just fine, but it isn't stable yet: +https://doc.rust-lang.org/nightly/std/sync/struct.StaticMutex.html + +However, if you still wish to use these types, you can achieve this by an unsafe +wrapper: + +``` +#![feature(const_fn)] + +use std::cell::Cell; +use std::marker::Sync; + +struct NotThreadSafe { + value: Cell, +} + +unsafe impl Sync for NotThreadSafe {} + +static A: NotThreadSafe = NotThreadSafe { value : Cell::new(1) }; +static B: &'static NotThreadSafe = &A; // ok! +``` + +Remember this solution is unsafe! You will have to ensure that accesses to the +cell are synchronized. +"##, + +E0493: r##" +A type with a destructor was assigned to an invalid type of variable. Erroneous +code example: + +``` +struct Foo { + a: u32 +} + +impl Drop for Foo { + fn drop(&mut self) {} +} + +const F : Foo = Foo { a : 0 }; +// error: constants are not allowed to have destructors +static S : Foo = Foo { a : 0 }; +// error: statics are not allowed to have destructors +``` + +To solve this issue, please use a type which does allow the usage of type with +destructors. +"##, + +E0494: r##" +A reference of an interior static was assigned to another const/static. +Erroneous code example: + +``` +struct Foo { + a: u32 +} + +static S : Foo = Foo { a : 0 }; +static A : &'static u32 = &S.a; +// error: cannot refer to the interior of another static, use a +// constant instead +``` + +The "base" variable has to be a const if you want another static/const variable +to refer to one of its fields. Example: + +``` +struct Foo { + a: u32 +} + +const S : Foo = Foo { a : 0 }; +static A : &'static u32 = &S.a; // ok! +``` +"##, + } register_diagnostics! { diff --git a/src/librustc_passes/lib.rs b/src/librustc_passes/lib.rs index 4adaa0cab7a..fcdbd6384d5 100644 --- a/src/librustc_passes/lib.rs +++ b/src/librustc_passes/lib.rs @@ -28,9 +28,16 @@ extern crate core; extern crate rustc; +extern crate rustc_front; +#[macro_use] extern crate log; #[macro_use] extern crate syntax; pub mod diagnostics; + pub mod const_fn; +pub mod consts; +pub mod loops; pub mod no_asm; +pub mod rvalues; +pub mod static_recursion; diff --git a/src/librustc_passes/loops.rs b/src/librustc_passes/loops.rs new file mode 100644 index 00000000000..eb2e445f9b0 --- /dev/null +++ b/src/librustc_passes/loops.rs @@ -0,0 +1,80 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use self::Context::*; + +use rustc::session::Session; + +use syntax::codemap::Span; +use rustc_front::intravisit::{self, Visitor}; +use rustc_front::hir; + +#[derive(Clone, Copy, PartialEq)] +enum Context { + Normal, Loop, Closure +} + +#[derive(Copy, Clone)] +struct CheckLoopVisitor<'a> { + sess: &'a Session, + cx: Context +} + +pub fn check_crate(sess: &Session, krate: &hir::Crate) { + krate.visit_all_items(&mut CheckLoopVisitor { sess: sess, cx: Normal }); +} + +impl<'a, 'v> Visitor<'v> for CheckLoopVisitor<'a> { + fn visit_item(&mut self, i: &hir::Item) { + self.with_context(Normal, |v| intravisit::walk_item(v, i)); + } + + fn visit_expr(&mut self, e: &hir::Expr) { + match e.node { + hir::ExprWhile(ref e, ref b, _) => { + self.visit_expr(&**e); + self.with_context(Loop, |v| v.visit_block(&**b)); + } + hir::ExprLoop(ref b, _) => { + self.with_context(Loop, |v| v.visit_block(&**b)); + } + hir::ExprClosure(_, _, ref b) => { + self.with_context(Closure, |v| v.visit_block(&**b)); + } + hir::ExprBreak(_) => self.require_loop("break", e.span), + hir::ExprAgain(_) => self.require_loop("continue", e.span), + _ => intravisit::walk_expr(self, e) + } + } +} + +impl<'a> CheckLoopVisitor<'a> { + fn with_context(&mut self, cx: Context, f: F) where + F: FnOnce(&mut CheckLoopVisitor<'a>), + { + let old_cx = self.cx; + self.cx = cx; + f(self); + self.cx = old_cx; + } + + fn require_loop(&self, name: &str, span: Span) { + match self.cx { + Loop => {} + Closure => { + span_err!(self.sess, span, E0267, + "`{}` inside of a closure", name); + } + Normal => { + span_err!(self.sess, span, E0268, + "`{}` outside of loop", name); + } + } + } +} diff --git a/src/librustc_passes/rvalues.rs b/src/librustc_passes/rvalues.rs new file mode 100644 index 00000000000..f5cc020932b --- /dev/null +++ b/src/librustc_passes/rvalues.rs @@ -0,0 +1,105 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Checks that all rvalues in a crate have statically known size. check_crate +// is the public starting point. + +use rustc::dep_graph::DepNode; +use rustc::middle::expr_use_visitor as euv; +use rustc::middle::infer; +use rustc::middle::mem_categorization as mc; +use rustc::middle::ty::{self, ParameterEnvironment}; + +use rustc_front::hir; +use rustc_front::intravisit; +use syntax::ast; +use syntax::codemap::Span; + +pub fn check_crate(tcx: &ty::ctxt) { + let mut rvcx = RvalueContext { tcx: tcx }; + tcx.visit_all_items_in_krate(DepNode::RvalueCheck, &mut rvcx); +} + +struct RvalueContext<'a, 'tcx: 'a> { + tcx: &'a ty::ctxt<'tcx>, +} + +impl<'a, 'tcx, 'v> intravisit::Visitor<'v> for RvalueContext<'a, 'tcx> { + fn visit_fn(&mut self, + fk: intravisit::FnKind<'v>, + fd: &'v hir::FnDecl, + b: &'v hir::Block, + s: Span, + fn_id: ast::NodeId) { + { + // FIXME (@jroesch) change this to be an inference context + let param_env = ParameterEnvironment::for_item(self.tcx, fn_id); + let infcx = infer::new_infer_ctxt(self.tcx, + &self.tcx.tables, + Some(param_env.clone())); + let mut delegate = RvalueContextDelegate { tcx: self.tcx, param_env: ¶m_env }; + let mut euv = euv::ExprUseVisitor::new(&mut delegate, &infcx); + euv.walk_fn(fd, b); + } + intravisit::walk_fn(self, fk, fd, b, s) + } +} + +struct RvalueContextDelegate<'a, 'tcx: 'a> { + tcx: &'a ty::ctxt<'tcx>, + param_env: &'a ty::ParameterEnvironment<'a,'tcx>, +} + +impl<'a, 'tcx> euv::Delegate<'tcx> for RvalueContextDelegate<'a, 'tcx> { + fn consume(&mut self, + _: ast::NodeId, + span: Span, + cmt: mc::cmt<'tcx>, + _: euv::ConsumeMode) { + debug!("consume; cmt: {:?}; type: {:?}", *cmt, cmt.ty); + if !cmt.ty.is_sized(self.param_env, span) { + span_err!(self.tcx.sess, span, E0161, + "cannot move a value of type {0}: the size of {0} cannot be statically determined", + cmt.ty); + } + } + + fn matched_pat(&mut self, + _matched_pat: &hir::Pat, + _cmt: mc::cmt, + _mode: euv::MatchMode) {} + + fn consume_pat(&mut self, + _consume_pat: &hir::Pat, + _cmt: mc::cmt, + _mode: euv::ConsumeMode) { + } + + 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) { + } + + 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) { + } +} diff --git a/src/librustc_passes/static_recursion.rs b/src/librustc_passes/static_recursion.rs new file mode 100644 index 00000000000..b49db16b4ce --- /dev/null +++ b/src/librustc_passes/static_recursion.rs @@ -0,0 +1,290 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This compiler pass detects constants that refer to themselves +// recursively. + +use rustc::front::map as ast_map; +use rustc::session::Session; +use rustc::middle::def::{Def, DefMap}; +use rustc::util::nodemap::NodeMap; + +use syntax::{ast}; +use syntax::codemap::Span; +use syntax::feature_gate::{GateIssue, emit_feature_err}; +use rustc_front::intravisit::{self, Visitor}; +use rustc_front::hir; + +use std::cell::RefCell; + +struct CheckCrateVisitor<'a, 'ast: 'a> { + sess: &'a Session, + def_map: &'a DefMap, + ast_map: &'a ast_map::Map<'ast>, + // `discriminant_map` is a cache that associates the `NodeId`s of local + // variant definitions with the discriminant expression that applies to + // each one. If the variant uses the default values (starting from `0`), + // then `None` is stored. + discriminant_map: RefCell>>, +} + +impl<'a, 'ast: 'a> Visitor<'ast> for CheckCrateVisitor<'a, 'ast> { + fn visit_item(&mut self, it: &'ast hir::Item) { + match it.node { + hir::ItemStatic(..) | + hir::ItemConst(..) => { + let mut recursion_visitor = + CheckItemRecursionVisitor::new(self, &it.span); + recursion_visitor.visit_item(it); + }, + hir::ItemEnum(ref enum_def, ref generics) => { + // We could process the whole enum, but handling the variants + // with discriminant expressions one by one gives more specific, + // less redundant output. + for variant in &enum_def.variants { + if let Some(_) = variant.node.disr_expr { + let mut recursion_visitor = + CheckItemRecursionVisitor::new(self, &variant.span); + recursion_visitor.populate_enum_discriminants(enum_def); + recursion_visitor.visit_variant(variant, generics, it.id); + } + } + } + _ => {} + } + intravisit::walk_item(self, it) + } + + fn visit_trait_item(&mut self, ti: &'ast hir::TraitItem) { + match ti.node { + hir::ConstTraitItem(_, ref default) => { + if let Some(_) = *default { + let mut recursion_visitor = + CheckItemRecursionVisitor::new(self, &ti.span); + recursion_visitor.visit_trait_item(ti); + } + } + _ => {} + } + intravisit::walk_trait_item(self, ti) + } + + fn visit_impl_item(&mut self, ii: &'ast hir::ImplItem) { + match ii.node { + hir::ImplItemKind::Const(..) => { + let mut recursion_visitor = + CheckItemRecursionVisitor::new(self, &ii.span); + recursion_visitor.visit_impl_item(ii); + } + _ => {} + } + intravisit::walk_impl_item(self, ii) + } +} + +pub fn check_crate<'ast>(sess: &Session, + krate: &'ast hir::Crate, + def_map: &DefMap, + ast_map: &ast_map::Map<'ast>) { + let mut visitor = CheckCrateVisitor { + sess: sess, + def_map: def_map, + ast_map: ast_map, + discriminant_map: RefCell::new(NodeMap()), + }; + sess.abort_if_new_errors(|| { + krate.visit_all_items(&mut visitor); + }); +} + +struct CheckItemRecursionVisitor<'a, 'ast: 'a> { + root_span: &'a Span, + sess: &'a Session, + ast_map: &'a ast_map::Map<'ast>, + def_map: &'a DefMap, + discriminant_map: &'a RefCell>>, + idstack: Vec, +} + +impl<'a, 'ast: 'a> CheckItemRecursionVisitor<'a, 'ast> { + fn new(v: &'a CheckCrateVisitor<'a, 'ast>, span: &'a Span) + -> CheckItemRecursionVisitor<'a, 'ast> { + CheckItemRecursionVisitor { + root_span: span, + sess: v.sess, + ast_map: v.ast_map, + def_map: v.def_map, + discriminant_map: &v.discriminant_map, + idstack: Vec::new(), + } + } + fn with_item_id_pushed(&mut self, id: ast::NodeId, f: F) + where F: Fn(&mut Self) { + if self.idstack.iter().any(|&x| x == id) { + let any_static = self.idstack.iter().any(|&x| { + if let ast_map::NodeItem(item) = self.ast_map.get(x) { + if let hir::ItemStatic(..) = item.node { + true + } else { + false + } + } else { + false + } + }); + if any_static { + if !self.sess.features.borrow().static_recursion { + emit_feature_err(&self.sess.parse_sess.span_diagnostic, + "static_recursion", + *self.root_span, GateIssue::Language, "recursive static"); + } + } else { + span_err!(self.sess, *self.root_span, E0265, "recursive constant"); + } + return; + } + self.idstack.push(id); + f(self); + self.idstack.pop(); + } + // If a variant has an expression specifying its discriminant, then it needs + // to be checked just like a static or constant. However, if there are more + // variants with no explicitly specified discriminant, those variants will + // increment the same expression to get their values. + // + // So for every variant, we need to track whether there is an expression + // somewhere in the enum definition that controls its discriminant. We do + // this by starting from the end and searching backward. + fn populate_enum_discriminants(&self, enum_definition: &'ast hir::EnumDef) { + // Get the map, and return if we already processed this enum or if it + // has no variants. + let mut discriminant_map = self.discriminant_map.borrow_mut(); + match enum_definition.variants.first() { + None => { return; } + Some(variant) if discriminant_map.contains_key(&variant.node.data.id()) => { + return; + } + _ => {} + } + + // Go through all the variants. + let mut variant_stack: Vec = Vec::new(); + for variant in enum_definition.variants.iter().rev() { + variant_stack.push(variant.node.data.id()); + // When we find an expression, every variant currently on the stack + // is affected by that expression. + if let Some(ref expr) = variant.node.disr_expr { + for id in &variant_stack { + discriminant_map.insert(*id, Some(expr)); + } + variant_stack.clear() + } + } + // If we are at the top, that always starts at 0, so any variant on the + // stack has a default value and does not need to be checked. + for id in &variant_stack { + discriminant_map.insert(*id, None); + } + } +} + +impl<'a, 'ast: 'a> Visitor<'ast> for CheckItemRecursionVisitor<'a, 'ast> { + fn visit_item(&mut self, it: &'ast hir::Item) { + self.with_item_id_pushed(it.id, |v| intravisit::walk_item(v, it)); + } + + fn visit_enum_def(&mut self, enum_definition: &'ast hir::EnumDef, + generics: &'ast hir::Generics, item_id: ast::NodeId, _: Span) { + self.populate_enum_discriminants(enum_definition); + intravisit::walk_enum_def(self, enum_definition, generics, item_id); + } + + fn visit_variant(&mut self, variant: &'ast hir::Variant, + _: &'ast hir::Generics, _: ast::NodeId) { + let variant_id = variant.node.data.id(); + let maybe_expr; + if let Some(get_expr) = self.discriminant_map.borrow().get(&variant_id) { + // This is necessary because we need to let the `discriminant_map` + // borrow fall out of scope, so that we can reborrow farther down. + maybe_expr = (*get_expr).clone(); + } else { + self.sess.span_bug(variant.span, + "`check_static_recursion` attempted to visit \ + variant with unknown discriminant") + } + // If `maybe_expr` is `None`, that's because no discriminant is + // specified that affects this variant. Thus, no risk of recursion. + if let Some(expr) = maybe_expr { + self.with_item_id_pushed(expr.id, |v| intravisit::walk_expr(v, expr)); + } + } + + fn visit_trait_item(&mut self, ti: &'ast hir::TraitItem) { + self.with_item_id_pushed(ti.id, |v| intravisit::walk_trait_item(v, ti)); + } + + fn visit_impl_item(&mut self, ii: &'ast hir::ImplItem) { + self.with_item_id_pushed(ii.id, |v| intravisit::walk_impl_item(v, ii)); + } + + fn visit_expr(&mut self, e: &'ast hir::Expr) { + match e.node { + hir::ExprPath(..) => { + match self.def_map.get(&e.id).map(|d| d.base_def) { + Some(Def::Static(def_id, _)) | + Some(Def::AssociatedConst(def_id)) | + Some(Def::Const(def_id)) => { + if let Some(node_id) = self.ast_map.as_local_node_id(def_id) { + match self.ast_map.get(node_id) { + ast_map::NodeItem(item) => + self.visit_item(item), + ast_map::NodeTraitItem(item) => + self.visit_trait_item(item), + ast_map::NodeImplItem(item) => + self.visit_impl_item(item), + ast_map::NodeForeignItem(_) => {}, + _ => { + self.sess.span_bug( + e.span, + &format!("expected item, found {}", + self.ast_map.node_to_string(node_id))); + } + } + } + } + // For variants, we only want to check expressions that + // affect the specific variant used, but we need to check + // the whole enum definition to see what expression that + // might be (if any). + Some(Def::Variant(enum_id, variant_id)) => { + if let Some(enum_node_id) = self.ast_map.as_local_node_id(enum_id) { + if let hir::ItemEnum(ref enum_def, ref generics) = + self.ast_map.expect_item(enum_node_id).node + { + self.populate_enum_discriminants(enum_def); + let enum_id = self.ast_map.as_local_node_id(enum_id).unwrap(); + let variant_id = self.ast_map.as_local_node_id(variant_id).unwrap(); + let variant = self.ast_map.expect_variant(variant_id); + self.visit_variant(variant, generics, enum_id); + } else { + self.sess.span_bug(e.span, + "`check_static_recursion` found \ + non-enum in Def::Variant"); + } + } + } + _ => () + } + }, + _ => () + } + intravisit::walk_expr(self, e); + } +} -- cgit 1.4.1-3-g733a5