270 lines
10 KiB
Rust
270 lines
10 KiB
Rust
// 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.
|
|
|
|
use dep_graph::DepNode;
|
|
use middle::def::Def;
|
|
use middle::def_id::DefId;
|
|
use ty::subst::{Subst, Substs, EnumeratedItems};
|
|
use ty::{TransmuteRestriction, TyCtxt};
|
|
use ty::{self, Ty, TypeFoldable};
|
|
|
|
use std::fmt;
|
|
|
|
use syntax::abi::Abi::RustIntrinsic;
|
|
use syntax::ast;
|
|
use syntax::codemap::Span;
|
|
use hir::intravisit::{self, Visitor, FnKind};
|
|
use hir;
|
|
|
|
pub fn check_crate(tcx: &TyCtxt) {
|
|
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),
|
|
};
|
|
tcx.visit_all_items_in_krate(DepNode::IntrinsicCheck, &mut visitor);
|
|
}
|
|
|
|
struct IntrinsicCheckingVisitor<'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>>,
|
|
|
|
// 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>,
|
|
}
|
|
|
|
impl<'a, 'tcx> IntrinsicCheckingVisitor<'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::TyFnDef(_, _, ref bfty) => bfty.abi == RustIntrinsic,
|
|
_ => return false
|
|
};
|
|
intrinsic && self.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 param_env = match self.param_envs.last() {
|
|
Some(p) => p,
|
|
None => {
|
|
span_bug!(
|
|
span,
|
|
"transmute encountered outside of any fn");
|
|
}
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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");
|
|
|
|
substs.types.get_mut_slice(space)[index] = self.dummy_unsized_ty;
|
|
self.with_each_combination(span, param_env, types_in_scope.clone(),
|
|
substs, callback);
|
|
}
|
|
|
|
substs.types.get_mut_slice(space)[index] = self.dummy_sized_ty;
|
|
self.with_each_combination(span, param_env, types_in_scope,
|
|
substs, callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn push_transmute_restriction(&self, restriction: TransmuteRestriction<'tcx>) {
|
|
debug!("Pushing transmute restriction: {:?}", restriction);
|
|
self.tcx.transmute_restrictions.borrow_mut().push(restriction);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
FnKind::Closure(..) => {
|
|
intravisit::walk_fn(self, fk, fd, b, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn visit_expr(&mut self, expr: &hir::Expr) {
|
|
if let hir::ExprPath(..) = expr.node {
|
|
match self.tcx.resolve_expr(expr) {
|
|
Def::Fn(did) if self.def_id_is_transmute(did) => {
|
|
let typ = self.tcx.node_id_to_type(expr.id);
|
|
match typ.sty {
|
|
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);
|
|
}
|
|
}
|
|
_ => {
|
|
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)
|
|
}
|
|
}
|