Skip to content

Commit

Permalink
Check transmutes between types without statically known sizes.
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Apr 19, 2016
1 parent 24ca1ec commit c7d564d
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 351 deletions.
2 changes: 0 additions & 2 deletions src/librustc/dep_graph/dep_node.rs
Expand Up @@ -71,7 +71,6 @@ pub enum DepNode<D: Clone + Debug> {
DeadCheck,
StabilityCheck,
LateLintCheck,
IntrinsicUseCheck,
TransCrate,
TransCrateItem(D),
TransInlinedItem(D),
Expand Down Expand Up @@ -169,7 +168,6 @@ impl<D: Clone + Debug> DepNode<D> {
DeadCheck => Some(DeadCheck),
StabilityCheck => Some(StabilityCheck),
LateLintCheck => Some(LateLintCheck),
IntrinsicUseCheck => Some(IntrinsicUseCheck),
TransCrate => Some(TransCrate),
TransWriteMetadata => Some(TransWriteMetadata),
Hir(ref d) => op(d).map(Hir),
Expand Down
26 changes: 26 additions & 0 deletions src/librustc/diagnostics.rs
Expand Up @@ -1410,6 +1410,32 @@ It is not possible to use stability attributes outside of the standard library.
Also, for now, it is not possible to write deprecation messages either.
"##,

E0512: r##"
Transmute with two differently sized types was attempted. Erroneous code
example:
```compile_fail
fn takes_u8(_: u8) {}
fn main() {
unsafe { takes_u8(::std::mem::transmute(0u16)); }
// error: transmute called with differently sized types
}
```
Please use types with same size or use the expected type directly. Example:
```
fn takes_u8(_: u8) {}
fn main() {
unsafe { takes_u8(::std::mem::transmute(0i8)); } // ok!
// or:
unsafe { takes_u8(0u8); } // ok!
}
```
"##,

E0517: r##"
This error indicates that a `#[repr(..)]` attribute was placed on an
unsupported item.
Expand Down
289 changes: 103 additions & 186 deletions src/librustc/middle/intrinsicck.rs
Expand Up @@ -11,11 +11,10 @@
use dep_graph::DepNode;
use hir::def::Def;
use hir::def_id::DefId;
use ty::subst::{Subst, Substs, EnumeratedItems};
use ty::{TransmuteRestriction, TyCtxt};
use ty::{self, Ty, TypeFoldable};

use std::fmt;
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;
Expand All @@ -24,219 +23,148 @@ 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),
let mut visitor = ItemVisitor {
tcx: tcx
};
tcx.visit_all_items_in_krate(DepNode::IntrinsicCheck, &mut visitor);
}

struct IntrinsicCheckingVisitor<'a, 'tcx: 'a> {
tcx: &'a TyCtxt<'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 {
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 => {
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, &param_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);
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 {
ty::TyFnDef(_, _, ref bare_fn_ty) if bare_fn_ty.abi == RustIntrinsic => {
if let ty::FnConverging(to) = bare_fn_ty.sig.0.output {
Expand All @@ -256,14 +184,3 @@ impl<'a, 'tcx, 'v> Visitor<'v> for IntrinsicCheckingVisitor<'a, 'tcx> {
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)
}
}

0 comments on commit c7d564d

Please sign in to comment.