1
Fork 0

librustc: Forbid transmute from being called on types whose size is

only known post-monomorphization, and report `transmute` errors before
the code is generated for that `transmute`.

This can break code that looked like:

    unsafe fn f<T>(x: T) {
        let y: int = transmute(x);
    }

Change such code to take a type parameter that has the same size as the
type being transmuted to.

Closes #12898.

[breaking-change]
This commit is contained in:
Patrick Walton 2014-06-12 14:08:44 -07:00 committed by Alex Crichton
parent 8c4a10a159
commit c9f3f47702
13 changed files with 323 additions and 31 deletions

View file

@ -307,6 +307,20 @@ extern "rust-intrinsic" {
/// `forget` is unsafe because the caller is responsible for /// `forget` is unsafe because the caller is responsible for
/// ensuring the argument is deallocated already. /// ensuring the argument is deallocated already.
pub fn forget<T>(_: T) -> (); pub fn forget<T>(_: T) -> ();
/// Unsafely transforms a value of one type into a value of another type.
///
/// Both types must have the same size and alignment, and this guarantee
/// is enforced at compile-time.
///
/// # Example
///
/// ```rust
/// use std::mem;
///
/// let v: &[u8] = unsafe { mem::transmute("L") };
/// assert!(v == [76u8]);
/// ```
pub fn transmute<T,U>(e: T) -> U; pub fn transmute<T,U>(e: T) -> U;
/// Returns `true` if a type requires drop glue. /// Returns `true` if a type requires drop glue.

View file

@ -17,6 +17,8 @@ use ptr;
use intrinsics; use intrinsics;
use intrinsics::{bswap16, bswap32, bswap64}; use intrinsics::{bswap16, bswap32, bswap64};
pub use intrinsics::transmute;
/// Returns the size of a type in bytes. /// Returns the size of a type in bytes.
#[inline] #[inline]
#[stable] #[stable]
@ -412,29 +414,6 @@ pub fn drop<T>(_x: T) { }
#[stable] #[stable]
pub unsafe fn forget<T>(thing: T) { intrinsics::forget(thing) } pub unsafe fn forget<T>(thing: T) { intrinsics::forget(thing) }
/// Unsafely transforms a value of one type into a value of another type.
///
/// Both types must have the same size and alignment, and this guarantee is
/// enforced at compile-time.
///
/// # Example
///
/// ```rust
/// use std::mem;
///
/// let v: &[u8] = unsafe { mem::transmute("L") };
/// assert!(v == [76u8]);
/// ```
#[inline]
#[unstable = "this function will be modified to reject invocations of it which \
cannot statically prove that T and U are the same size. For \
example, this function, as written today, will be rejected in \
the future because the size of T and U cannot be statically \
known to be the same"]
pub unsafe fn transmute<T, U>(thing: T) -> U {
intrinsics::transmute(thing)
}
/// Interprets `src` as `&U`, and then reads `src` without moving the contained /// Interprets `src` as `&U`, and then reads `src` without moving the contained
/// value. /// value.
/// ///

View file

@ -333,6 +333,9 @@ pub fn phase_3_run_analysis_passes(sess: Session,
time(time_passes, "privacy checking", maps, |(a, b)| time(time_passes, "privacy checking", maps, |(a, b)|
middle::privacy::check_crate(&ty_cx, &exp_map2, a, b, krate)); middle::privacy::check_crate(&ty_cx, &exp_map2, a, b, krate));
time(time_passes, "intrinsic checking", (), |_|
middle::intrinsicck::check_crate(&ty_cx, krate));
time(time_passes, "effect checking", (), |_| time(time_passes, "effect checking", (), |_|
middle::effect::check_crate(&ty_cx, krate)); middle::effect::check_crate(&ty_cx, krate));

View file

@ -85,6 +85,7 @@ pub mod middle {
pub mod dependency_format; pub mod dependency_format;
pub mod weak_lang_items; pub mod weak_lang_items;
pub mod save; pub mod save;
pub mod intrinsicck;
} }
pub mod front { pub mod front {

View file

@ -0,0 +1,155 @@
// 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 metadata::csearch;
use middle::def::DefFn;
use middle::subst::Subst;
use middle::ty::{TransmuteRestriction, ctxt, ty_bare_fn};
use middle::ty;
use syntax::abi::RustIntrinsic;
use syntax::ast::DefId;
use syntax::ast;
use syntax::ast_map::NodeForeignItem;
use syntax::codemap::Span;
use syntax::parse::token;
use syntax::visit::Visitor;
use syntax::visit;
fn type_size_is_affected_by_type_parameters(tcx: &ty::ctxt, typ: ty::t)
-> bool {
let mut result = false;
ty::maybe_walk_ty(typ, |typ| {
match ty::get(typ).sty {
ty::ty_box(_) | ty::ty_uniq(_) | ty::ty_ptr(_) |
ty::ty_rptr(..) | ty::ty_bare_fn(..) | ty::ty_closure(..) => {
false
}
ty::ty_param(_) => {
result = true;
// No need to continue; we now know the result.
false
}
ty::ty_enum(did, ref substs) => {
for enum_variant in (*ty::enum_variants(tcx, did)).iter() {
for argument_type in enum_variant.args.iter() {
let argument_type = argument_type.subst(tcx, substs);
result = result ||
type_size_is_affected_by_type_parameters(
tcx,
argument_type);
}
}
// Don't traverse substitutions.
false
}
ty::ty_struct(did, ref substs) => {
for field in ty::struct_fields(tcx, did, substs).iter() {
result = result ||
type_size_is_affected_by_type_parameters(tcx,
field.mt.ty);
}
// Don't traverse substitutions.
false
}
_ => true,
}
});
result
}
struct IntrinsicCheckingVisitor<'a> {
tcx: &'a ctxt,
}
impl<'a> IntrinsicCheckingVisitor<'a> {
fn def_id_is_transmute(&self, def_id: DefId) -> bool {
if def_id.krate == ast::LOCAL_CRATE {
match self.tcx.map.get(def_id.node) {
NodeForeignItem(ref item) => {
token::get_ident(item.ident) ==
token::intern_and_get_ident("transmute")
}
_ => false,
}
} else {
match csearch::get_item_path(self.tcx, def_id).last() {
None => false,
Some(ref last) => {
token::get_name(last.name()) ==
token::intern_and_get_ident("transmute")
}
}
}
}
fn check_transmute(&self, span: Span, from: ty::t, to: ty::t) {
if type_size_is_affected_by_type_parameters(self.tcx, from) {
self.tcx.sess.span_err(span,
"cannot transmute from a type that \
contains type parameters");
}
if type_size_is_affected_by_type_parameters(self.tcx, to) {
self.tcx.sess.span_err(span,
"cannot transmute to a type that contains \
type parameters");
}
let restriction = TransmuteRestriction {
span: span,
from: from,
to: to,
};
self.tcx.transmute_restrictions.borrow_mut().push(restriction);
}
}
impl<'a> Visitor<()> for IntrinsicCheckingVisitor<'a> {
fn visit_expr(&mut self, expr: &ast::Expr, (): ()) {
match expr.node {
ast::ExprPath(..) => {
match ty::resolve_expr(self.tcx, expr) {
DefFn(did, _) if self.def_id_is_transmute(did) => {
let typ = ty::node_id_to_type(self.tcx, expr.id);
match ty::get(typ).sty {
ty_bare_fn(ref bare_fn_ty)
if bare_fn_ty.abi == RustIntrinsic => {
let from = *bare_fn_ty.sig.inputs.get(0);
let to = bare_fn_ty.sig.output;
self.check_transmute(expr.span, from, to);
}
_ => {
self.tcx
.sess
.span_bug(expr.span,
"transmute wasn't a bare fn?!");
}
}
}
_ => {}
}
}
_ => {}
}
visit::walk_expr(self, expr, ());
}
}
pub fn check_crate(tcx: &ctxt, krate: &ast::Crate) {
let mut visitor = IntrinsicCheckingVisitor {
tcx: tcx,
};
visit::walk_crate(&mut visitor, krate, ());
}

View file

@ -59,6 +59,7 @@ use middle::trans::expr;
use middle::trans::foreign; use middle::trans::foreign;
use middle::trans::glue; use middle::trans::glue;
use middle::trans::inline; use middle::trans::inline;
use middle::trans::intrinsic;
use middle::trans::machine; use middle::trans::machine;
use middle::trans::machine::{llalign_of_min, llsize_of, llsize_of_real}; use middle::trans::machine::{llalign_of_min, llsize_of, llsize_of_real};
use middle::trans::meth; use middle::trans::meth;
@ -2329,6 +2330,11 @@ pub fn trans_crate(krate: ast::Crate,
let ccx = CrateContext::new(llmod_id.as_slice(), tcx, exp_map2, let ccx = CrateContext::new(llmod_id.as_slice(), tcx, exp_map2,
Sha256::new(), link_meta, reachable); Sha256::new(), link_meta, reachable);
// First, verify intrinsics.
intrinsic::check_intrinsics(&ccx);
// Next, translate the module.
{ {
let _icx = push_ctxt("text"); let _icx = push_ctxt("text");
trans_mod(&ccx, &krate.module); trans_mod(&ccx, &krate.module);

View file

@ -390,7 +390,7 @@ pub fn trans_intrinsic(ccx: &CrateContext,
ast_map::NodeExpr(e) => e.span, ast_map::NodeExpr(e) => e.span,
_ => fail!("transmute has non-expr arg"), _ => fail!("transmute has non-expr arg"),
}; };
ccx.sess().span_fatal(sp, ccx.sess().span_bug(sp,
format!("transmute called on types with different sizes: \ format!("transmute called on types with different sizes: \
{} ({} bit{}) to \ {} ({} bit{}) to \
{} ({} bit{})", {} ({} bit{})",
@ -564,3 +564,41 @@ pub fn trans_intrinsic(ccx: &CrateContext,
} }
fcx.cleanup(); fcx.cleanup();
} }
/// Performs late verification that intrinsics are used correctly. At present,
/// the only intrinsic that needs such verification is `transmute`.
pub fn check_intrinsics(ccx: &CrateContext) {
for transmute_restriction in ccx.tcx
.transmute_restrictions
.borrow()
.iter() {
let llfromtype = type_of::sizing_type_of(ccx,
transmute_restriction.from);
let lltotype = type_of::sizing_type_of(ccx,
transmute_restriction.to);
let from_type_size = machine::llbitsize_of_real(ccx, llfromtype);
let to_type_size = machine::llbitsize_of_real(ccx, lltotype);
if from_type_size != to_type_size {
ccx.sess()
.span_err(transmute_restriction.span,
format!("transmute called on types with different sizes: \
{} ({} bit{}) to {} ({} bit{})",
ty_to_str(ccx.tcx(), transmute_restriction.from),
from_type_size as uint,
if from_type_size == 1 {
""
} else {
"s"
},
ty_to_str(ccx.tcx(), transmute_restriction.to),
to_type_size as uint,
if to_type_size == 1 {
""
} else {
"s"
}).as_slice());
}
}
ccx.sess().abort_if_errors();
}

View file

@ -235,6 +235,17 @@ pub enum AutoRef {
AutoBorrowObj(Region, ast::Mutability), AutoBorrowObj(Region, ast::Mutability),
} }
/// A restriction that certain types must be the same size. The use of
/// `transmute` gives rise to these restrictions.
pub struct TransmuteRestriction {
/// The span from whence the restriction comes.
pub span: Span,
/// The type being transmuted from.
pub from: t,
/// The type being transmuted to.
pub to: t,
}
/// The data structure to keep track of all the information that typechecker /// The data structure to keep track of all the information that typechecker
/// generates so that so that it can be reused and doesn't have to be redone /// generates so that so that it can be reused and doesn't have to be redone
/// later on. /// later on.
@ -357,6 +368,11 @@ pub struct ctxt {
pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::Lint), pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::Lint),
(lint::Level, lint::LintSource)>>, (lint::Level, lint::LintSource)>>,
/// The types that must be asserted to be the same size for `transmute`
/// to be valid. We gather up these restrictions in the intrinsicck pass
/// and check them in trans.
pub transmute_restrictions: RefCell<Vec<TransmuteRestriction>>,
} }
pub enum tbox_flag { pub enum tbox_flag {
@ -1118,6 +1134,7 @@ pub fn mk_ctxt(s: Session,
vtable_map: RefCell::new(FnvHashMap::new()), vtable_map: RefCell::new(FnvHashMap::new()),
dependency_formats: RefCell::new(HashMap::new()), dependency_formats: RefCell::new(HashMap::new()),
node_lint_levels: RefCell::new(HashMap::new()), node_lint_levels: RefCell::new(HashMap::new()),
transmute_restrictions: RefCell::new(Vec::new()),
} }
} }
@ -2711,8 +2728,7 @@ pub fn pat_ty(cx: &ctxt, pat: &ast::Pat) -> t {
// //
// NB (2): This type doesn't provide type parameter substitutions; e.g. if you // NB (2): This type doesn't provide type parameter substitutions; e.g. if you
// ask for the type of "id" in "id(3)", it will return "fn(&int) -> int" // ask for the type of "id" in "id(3)", it will return "fn(&int) -> int"
// instead of "fn(t) -> T with T = int". If this isn't what you want, see // instead of "fn(t) -> T with T = int".
// expr_ty_params_and_ty() below.
pub fn expr_ty(cx: &ctxt, expr: &ast::Expr) -> t { pub fn expr_ty(cx: &ctxt, expr: &ast::Expr) -> t {
return node_id_to_type(cx, expr.id); return node_id_to_type(cx, expr.id);
} }

View file

@ -126,9 +126,11 @@ impl<'a> PluginLoader<'a> {
}; };
unsafe { unsafe {
let registrar: PluginRegistrarFun = let registrar =
match lib.symbol(symbol.as_slice()) { match lib.symbol(symbol.as_slice()) {
Ok(registrar) => registrar, Ok(registrar) => {
mem::transmute::<*u8,PluginRegistrarFun>(registrar)
}
// again fatal if we can't register macros // again fatal if we can't register macros
Err(err) => self.sess.span_fatal(vi.span, err.as_slice()) Err(err) => self.sess.span_fatal(vi.span, err.as_slice())
}; };

View file

@ -12,6 +12,7 @@ use clean;
use dl = std::dynamic_lib; use dl = std::dynamic_lib;
use serialize::json; use serialize::json;
use std::mem;
use std::string::String; use std::string::String;
pub type PluginJson = Option<(String, json::Json)>; pub type PluginJson = Option<(String, json::Json)>;
@ -45,9 +46,11 @@ impl PluginManager {
let x = self.prefix.join(libname(name)); let x = self.prefix.join(libname(name));
let lib_result = dl::DynamicLibrary::open(Some(&x)); let lib_result = dl::DynamicLibrary::open(Some(&x));
let lib = lib_result.unwrap(); let lib = lib_result.unwrap();
let plugin = unsafe { lib.symbol("rustdoc_plugin_entrypoint") }.unwrap(); unsafe {
let plugin = lib.symbol("rustdoc_plugin_entrypoint").unwrap();
self.callbacks.push(mem::transmute::<*u8,PluginCallback>(plugin));
}
self.dylibs.push(lib); self.dylibs.push(lib);
self.callbacks.push(plugin);
} }
/// Load a normal Rust function as a plugin. /// Load a normal Rust function as a plugin.

View file

@ -134,7 +134,7 @@ impl DynamicLibrary {
} }
/// Access the value at the symbol of the dynamic library /// Access the value at the symbol of the dynamic library
pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<T, String> { pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*T, String> {
// This function should have a lifetime constraint of 'a on // This function should have a lifetime constraint of 'a on
// T but that feature is still unimplemented // T but that feature is still unimplemented

View file

@ -0,0 +1,27 @@
// Copyright 2012 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.
// Tests that `transmute` cannot be called on types of different size.
use std::mem::transmute;
unsafe fn f() {
let _: i8 = transmute(16i16);
//~^ ERROR transmute called on types with different sizes
}
unsafe fn g<T>(x: &T) {
let _: i8 = transmute(x);
//~^ ERROR transmute called on types with different sizes
}
fn main() {}

View file

@ -0,0 +1,48 @@
// Copyright 2012 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.
// Tests that `transmute` cannot be called on type parameters.
use std::mem::transmute;
unsafe fn f<T>(x: T) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
unsafe fn g<T>(x: (T, int)) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
unsafe fn h<T>(x: [T, ..10]) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
struct Bad<T> {
f: T,
}
unsafe fn i<T>(x: Bad<T>) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
enum Worse<T> {
A(T),
B,
}
unsafe fn j<T>(x: Worse<T>) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
unsafe fn k<T>(x: Option<T>) {
let _: int = transmute(x); //~ ERROR cannot transmute
}
fn main() {}