// except according to those terms.
use dep_graph::DepNode;
-use middle::def::Def;
-use middle::def_id::DefId;
-use middle::subst::{Subst, Substs, EnumeratedItems};
-use middle::ty::{TransmuteRestriction, ctxt, TyBareFn};
-use middle::ty::{self, Ty, TypeFoldable};
-
-use std::fmt;
+use hir::def::Def;
+use hir::def_id::DefId;
+use infer::{InferCtxt, new_infer_ctxt};
+use traits::ProjectionMode;
+use ty::{self, Ty, TyCtxt};
+use ty::layout::{LayoutError, Pointer, SizeSkeleton};
use syntax::abi::Abi::RustIntrinsic;
use syntax::ast;
use syntax::codemap::Span;
-use rustc_front::intravisit::{self, Visitor, FnKind};
-use rustc_front::hir;
+use hir::intravisit::{self, Visitor, FnKind};
+use hir;
-pub fn check_crate(tcx: &ctxt) {
- let mut visitor = IntrinsicCheckingVisitor {
- tcx: tcx,
- param_envs: Vec::new(),
- dummy_sized_ty: tcx.types.isize,
- dummy_unsized_ty: tcx.mk_slice(tcx.types.isize),
+pub fn check_crate(tcx: &TyCtxt) {
+ let mut visitor = ItemVisitor {
+ tcx: tcx
};
tcx.visit_all_items_in_krate(DepNode::IntrinsicCheck, &mut visitor);
}
-struct IntrinsicCheckingVisitor<'a, 'tcx: 'a> {
- tcx: &'a ctxt<'tcx>,
+struct ItemVisitor<'a, 'tcx: 'a> {
+ tcx: &'a TyCtxt<'tcx>
+}
- // As we traverse the AST, we keep a stack of the parameter
- // environments for each function we encounter. When we find a
- // call to `transmute`, we can check it in the context of the top
- // of the stack (which ought not to be empty).
- param_envs: Vec<ty::ParameterEnvironment<'a,'tcx>>,
+impl<'a, 'tcx> ItemVisitor<'a, 'tcx> {
+ fn visit_const(&mut self, item_id: ast::NodeId, expr: &hir::Expr) {
+ let param_env = ty::ParameterEnvironment::for_item(self.tcx, item_id);
+ let infcx = new_infer_ctxt(self.tcx, &self.tcx.tables,
+ Some(param_env),
+ ProjectionMode::Any);
+ let mut visitor = ExprVisitor {
+ infcx: &infcx
+ };
+ visitor.visit_expr(expr);
+ }
+}
- // Dummy sized/unsized types that use to substitute for type
- // parameters in order to estimate how big a type will be for any
- // possible instantiation of the type parameters in scope. See
- // `check_transmute` for more details.
- dummy_sized_ty: Ty<'tcx>,
- dummy_unsized_ty: Ty<'tcx>,
+struct ExprVisitor<'a, 'tcx: 'a> {
+ infcx: &'a InferCtxt<'a, 'tcx>
}
-impl<'a, 'tcx> IntrinsicCheckingVisitor<'a, 'tcx> {
+impl<'a, 'tcx> ExprVisitor<'a, 'tcx> {
fn def_id_is_transmute(&self, def_id: DefId) -> bool {
- let intrinsic = match self.tcx.lookup_item_type(def_id).ty.sty {
- ty::TyBareFn(_, ref bfty) => bfty.abi == RustIntrinsic,
+ let intrinsic = match self.infcx.tcx.lookup_item_type(def_id).ty.sty {
+ ty::TyFnDef(_, _, ref bfty) => bfty.abi == RustIntrinsic,
_ => return false
};
- intrinsic && self.tcx.item_name(def_id).as_str() == "transmute"
+ intrinsic && self.infcx.tcx.item_name(def_id).as_str() == "transmute"
}
fn check_transmute(&self, span: Span, from: Ty<'tcx>, to: Ty<'tcx>, id: ast::NodeId) {
- // Find the parameter environment for the most recent function that
- // we entered.
+ let sk_from = SizeSkeleton::compute(from, self.infcx);
+ let sk_to = SizeSkeleton::compute(to, self.infcx);
- let param_env = match self.param_envs.last() {
- Some(p) => p,
- None => {
- self.tcx.sess.span_bug(
- span,
- "transmute encountered outside of any fn");
+ // Check for same size using the skeletons.
+ if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
+ if sk_from.same_size(sk_to) {
+ return;
}
- };
-
- // Simple case: no type parameters involved.
- if
- !from.has_param_types() && !from.has_self_ty() &&
- !to.has_param_types() && !to.has_self_ty()
- {
- let restriction = TransmuteRestriction {
- span: span,
- original_from: from,
- original_to: to,
- substituted_from: from,
- substituted_to: to,
- id: id,
- };
- self.push_transmute_restriction(restriction);
- return;
- }
- // The rules around type parameters are a bit subtle. We are
- // checking these rules before monomorphization, so there may
- // be unsubstituted type parameters present in the
- // types. Obviously we cannot create LLVM types for those.
- // However, if a type parameter appears only indirectly (i.e.,
- // through a pointer), it does not necessarily affect the
- // size, so that should be allowed. The only catch is that we
- // DO want to be careful around unsized type parameters, since
- // fat pointers have a different size than a thin pointer, and
- // hence `&T` and `&U` have different sizes if `T : Sized` but
- // `U : Sized` does not hold.
- //
- // However, it's not as simple as checking whether `T :
- // Sized`, because even if `T : Sized` does not hold, that
- // just means that `T` *may* not be sized. After all, even a
- // type parameter `T: ?Sized` could be bound to a sized
- // type. (Issue #20116)
- //
- // To handle this, we first check for "interior" type
- // parameters, which are always illegal. If there are none of
- // those, then we know that the only way that all type
- // parameters `T` are referenced indirectly, e.g. via a
- // pointer type like `&T`. In that case, we only care whether
- // `T` is sized or not, because that influences whether `&T`
- // is a thin or fat pointer.
- //
- // One could imagine establishing a sophisticated constraint
- // system to ensure that the transmute is legal, but instead
- // we do something brutally dumb. We just substitute dummy
- // sized or unsized types for every type parameter in scope,
- // exhaustively checking all possible combinations. Here are some examples:
- //
- // ```
- // fn foo<T, U>() {
- // // T=int, U=int
- // }
- //
- // fn bar<T: ?Sized, U>() {
- // // T=int, U=int
- // // T=[int], U=int
- // }
- //
- // fn baz<T: ?Sized, U: ?Sized>() {
- // // T=int, U=int
- // // T=[int], U=int
- // // T=int, U=[int]
- // // T=[int], U=[int]
- // }
- // ```
- //
- // In all cases, we keep the original unsubstituted types
- // around for error reporting.
-
- let from_tc = from.type_contents(self.tcx);
- let to_tc = to.type_contents(self.tcx);
- if from_tc.interior_param() || to_tc.interior_param() {
- span_err!(self.tcx.sess, span, E0139,
- "cannot transmute to or from a type that contains \
- unsubstituted type parameters");
- return;
+ match (&from.sty, sk_to) {
+ (&ty::TyFnDef(..), SizeSkeleton::Known(size_to))
+ if size_to == Pointer.size(&self.infcx.tcx.data_layout) => {
+ // FIXME #19925 Remove this warning after a release cycle.
+ let msg = format!("`{}` is now zero-sized and has to be cast \
+ to a pointer before transmuting to `{}`",
+ from, to);
+ self.infcx.tcx.sess.add_lint(
+ ::lint::builtin::TRANSMUTE_FROM_FN_ITEM_TYPES, id, span, msg);
+ return;
+ }
+ _ => {}
+ }
}
- let mut substs = param_env.free_substs.clone();
- self.with_each_combination(
- span,
- param_env,
- param_env.free_substs.types.iter_enumerated(),
- &mut substs,
- &mut |substs| {
- let restriction = TransmuteRestriction {
- span: span,
- original_from: from,
- original_to: to,
- substituted_from: from.subst(self.tcx, substs),
- substituted_to: to.subst(self.tcx, substs),
- id: id,
- };
- self.push_transmute_restriction(restriction);
- });
- }
-
- fn with_each_combination(&self,
- span: Span,
- param_env: &ty::ParameterEnvironment<'a,'tcx>,
- mut types_in_scope: EnumeratedItems<Ty<'tcx>>,
- substs: &mut Substs<'tcx>,
- callback: &mut FnMut(&Substs<'tcx>))
- {
- // This parameter invokes `callback` many times with different
- // substitutions that replace all the parameters in scope with
- // either `int` or `[int]`, depending on whether the type
- // parameter is known to be sized. See big comment above for
- // an explanation of why this is a reasonable thing to do.
-
- match types_in_scope.next() {
- None => {
- debug!("with_each_combination(substs={:?})",
- substs);
-
- callback(substs);
+ // Try to display a sensible error with as much information as possible.
+ let skeleton_string = |ty: Ty<'tcx>, sk| {
+ match sk {
+ Ok(SizeSkeleton::Known(size)) => {
+ format!("{} bits", size.bits())
+ }
+ Ok(SizeSkeleton::Pointer { tail, .. }) => {
+ format!("pointer to {}", tail)
+ }
+ Err(LayoutError::Unknown(bad)) => {
+ if bad == ty {
+ format!("size can vary")
+ } else {
+ format!("size can vary because of {}", bad)
+ }
+ }
+ Err(err) => err.to_string()
}
+ };
- Some((space, index, ¶m_ty)) => {
- debug!("with_each_combination: space={:?}, index={}, param_ty={:?}",
- space, index, param_ty);
-
- if !param_ty.is_sized(param_env, span) {
- debug!("with_each_combination: param_ty is not known to be sized");
+ span_err!(self.infcx.tcx.sess, span, E0512,
+ "transmute called with differently sized types: \
+ {} ({}) to {} ({})",
+ from, skeleton_string(from, sk_from),
+ to, skeleton_string(to, sk_to));
+ }
+}
- substs.types.get_mut_slice(space)[index] = self.dummy_unsized_ty;
- self.with_each_combination(span, param_env, types_in_scope.clone(),
- substs, callback);
- }
+impl<'a, 'tcx, 'v> Visitor<'v> for ItemVisitor<'a, 'tcx> {
+ // const, static and N in [T; N].
+ fn visit_expr(&mut self, expr: &hir::Expr) {
+ let infcx = new_infer_ctxt(self.tcx, &self.tcx.tables,
+ None, ProjectionMode::Any);
+ let mut visitor = ExprVisitor {
+ infcx: &infcx
+ };
+ visitor.visit_expr(expr);
+ }
- substs.types.get_mut_slice(space)[index] = self.dummy_sized_ty;
- self.with_each_combination(span, param_env, types_in_scope,
- substs, callback);
- }
+ fn visit_trait_item(&mut self, item: &hir::TraitItem) {
+ if let hir::ConstTraitItem(_, Some(ref expr)) = item.node {
+ self.visit_const(item.id, expr);
+ } else {
+ intravisit::walk_trait_item(self, item);
}
}
- fn push_transmute_restriction(&self, restriction: TransmuteRestriction<'tcx>) {
- debug!("Pushing transmute restriction: {:?}", restriction);
- self.tcx.transmute_restrictions.borrow_mut().push(restriction);
+ fn visit_impl_item(&mut self, item: &hir::ImplItem) {
+ if let hir::ImplItemKind::Const(_, ref expr) = item.node {
+ self.visit_const(item.id, expr);
+ } else {
+ intravisit::walk_impl_item(self, item);
+ }
}
-}
-impl<'a, 'tcx, 'v> Visitor<'v> for IntrinsicCheckingVisitor<'a, 'tcx> {
fn visit_fn(&mut self, fk: FnKind<'v>, fd: &'v hir::FnDecl,
b: &'v hir::Block, s: Span, id: ast::NodeId) {
match fk {
FnKind::ItemFn(..) | FnKind::Method(..) => {
let param_env = ty::ParameterEnvironment::for_item(self.tcx, id);
- self.param_envs.push(param_env);
- intravisit::walk_fn(self, fk, fd, b, s);
- self.param_envs.pop();
+ let infcx = new_infer_ctxt(self.tcx, &self.tcx.tables,
+ Some(param_env),
+ ProjectionMode::Any);
+ let mut visitor = ExprVisitor {
+ infcx: &infcx
+ };
+ visitor.visit_fn(fk, fd, b, s, id);
}
- FnKind::Closure => {
- intravisit::walk_fn(self, fk, fd, b, s);
+ FnKind::Closure(..) => {
+ span_bug!(s, "intrinsicck: closure outside of function")
}
}
}
+}
+impl<'a, 'tcx, 'v> Visitor<'v> for ExprVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &hir::Expr) {
if let hir::ExprPath(..) = expr.node {
- match self.tcx.resolve_expr(expr) {
+ match self.infcx.tcx.resolve_expr(expr) {
Def::Fn(did) if self.def_id_is_transmute(did) => {
- let typ = self.tcx.node_id_to_type(expr.id);
+ let typ = self.infcx.tcx.node_id_to_type(expr.id);
match typ.sty {
- TyBareFn(_, ref bare_fn_ty) if bare_fn_ty.abi == RustIntrinsic => {
+ ty::TyFnDef(_, _, ref bare_fn_ty) if bare_fn_ty.abi == RustIntrinsic => {
if let ty::FnConverging(to) = bare_fn_ty.sig.0.output {
let from = bare_fn_ty.sig.0.inputs[0];
self.check_transmute(expr.span, from, to, expr.id);
}
}
_ => {
- self.tcx
- .sess
- .span_bug(expr.span, "transmute wasn't a bare fn?!");
+ span_bug!(expr.span, "transmute wasn't a bare fn?!");
}
}
}
intravisit::walk_expr(self, expr);
}
}
-
-impl<'tcx> fmt::Debug for TransmuteRestriction<'tcx> {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "TransmuteRestriction(id={}, original=({:?},{:?}), substituted=({:?},{:?}))",
- self.id,
- self.original_from,
- self.original_to,
- self.substituted_from,
- self.substituted_to)
- }
-}