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:
parent
8c4a10a159
commit
c9f3f47702
13 changed files with 323 additions and 31 deletions
|
@ -307,6 +307,20 @@ extern "rust-intrinsic" {
|
|||
/// `forget` is unsafe because the caller is responsible for
|
||||
/// ensuring the argument is deallocated already.
|
||||
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;
|
||||
|
||||
/// Returns `true` if a type requires drop glue.
|
||||
|
|
|
@ -17,6 +17,8 @@ use ptr;
|
|||
use intrinsics;
|
||||
use intrinsics::{bswap16, bswap32, bswap64};
|
||||
|
||||
pub use intrinsics::transmute;
|
||||
|
||||
/// Returns the size of a type in bytes.
|
||||
#[inline]
|
||||
#[stable]
|
||||
|
@ -412,29 +414,6 @@ pub fn drop<T>(_x: T) { }
|
|||
#[stable]
|
||||
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
|
||||
/// value.
|
||||
///
|
||||
|
|
|
@ -333,6 +333,9 @@ pub fn phase_3_run_analysis_passes(sess: Session,
|
|||
time(time_passes, "privacy checking", maps, |(a, b)|
|
||||
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", (), |_|
|
||||
middle::effect::check_crate(&ty_cx, krate));
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ pub mod middle {
|
|||
pub mod dependency_format;
|
||||
pub mod weak_lang_items;
|
||||
pub mod save;
|
||||
pub mod intrinsicck;
|
||||
}
|
||||
|
||||
pub mod front {
|
||||
|
|
155
src/librustc/middle/intrinsicck.rs
Normal file
155
src/librustc/middle/intrinsicck.rs
Normal 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, ());
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ use middle::trans::expr;
|
|||
use middle::trans::foreign;
|
||||
use middle::trans::glue;
|
||||
use middle::trans::inline;
|
||||
use middle::trans::intrinsic;
|
||||
use middle::trans::machine;
|
||||
use middle::trans::machine::{llalign_of_min, llsize_of, llsize_of_real};
|
||||
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,
|
||||
Sha256::new(), link_meta, reachable);
|
||||
|
||||
// First, verify intrinsics.
|
||||
intrinsic::check_intrinsics(&ccx);
|
||||
|
||||
// Next, translate the module.
|
||||
{
|
||||
let _icx = push_ctxt("text");
|
||||
trans_mod(&ccx, &krate.module);
|
||||
|
|
|
@ -390,7 +390,7 @@ pub fn trans_intrinsic(ccx: &CrateContext,
|
|||
ast_map::NodeExpr(e) => e.span,
|
||||
_ => fail!("transmute has non-expr arg"),
|
||||
};
|
||||
ccx.sess().span_fatal(sp,
|
||||
ccx.sess().span_bug(sp,
|
||||
format!("transmute called on types with different sizes: \
|
||||
{} ({} bit{}) to \
|
||||
{} ({} bit{})",
|
||||
|
@ -564,3 +564,41 @@ pub fn trans_intrinsic(ccx: &CrateContext,
|
|||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -235,6 +235,17 @@ pub enum AutoRef {
|
|||
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
|
||||
/// generates so that so that it can be reused and doesn't have to be redone
|
||||
/// later on.
|
||||
|
@ -357,6 +368,11 @@ pub struct ctxt {
|
|||
|
||||
pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::Lint),
|
||||
(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 {
|
||||
|
@ -1118,6 +1134,7 @@ pub fn mk_ctxt(s: Session,
|
|||
vtable_map: RefCell::new(FnvHashMap::new()),
|
||||
dependency_formats: 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
|
||||
// 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
|
||||
// expr_ty_params_and_ty() below.
|
||||
// instead of "fn(t) -> T with T = int".
|
||||
pub fn expr_ty(cx: &ctxt, expr: &ast::Expr) -> t {
|
||||
return node_id_to_type(cx, expr.id);
|
||||
}
|
||||
|
|
|
@ -126,9 +126,11 @@ impl<'a> PluginLoader<'a> {
|
|||
};
|
||||
|
||||
unsafe {
|
||||
let registrar: PluginRegistrarFun =
|
||||
let registrar =
|
||||
match lib.symbol(symbol.as_slice()) {
|
||||
Ok(registrar) => registrar,
|
||||
Ok(registrar) => {
|
||||
mem::transmute::<*u8,PluginRegistrarFun>(registrar)
|
||||
}
|
||||
// again fatal if we can't register macros
|
||||
Err(err) => self.sess.span_fatal(vi.span, err.as_slice())
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ use clean;
|
|||
|
||||
use dl = std::dynamic_lib;
|
||||
use serialize::json;
|
||||
use std::mem;
|
||||
use std::string::String;
|
||||
|
||||
pub type PluginJson = Option<(String, json::Json)>;
|
||||
|
@ -45,9 +46,11 @@ impl PluginManager {
|
|||
let x = self.prefix.join(libname(name));
|
||||
let lib_result = dl::DynamicLibrary::open(Some(&x));
|
||||
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.callbacks.push(plugin);
|
||||
}
|
||||
|
||||
/// Load a normal Rust function as a plugin.
|
||||
|
|
|
@ -134,7 +134,7 @@ impl DynamicLibrary {
|
|||
}
|
||||
|
||||
/// 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
|
||||
// T but that feature is still unimplemented
|
||||
|
||||
|
|
27
src/test/compile-fail/transmute-different-sizes.rs
Normal file
27
src/test/compile-fail/transmute-different-sizes.rs
Normal 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() {}
|
||||
|
||||
|
48
src/test/compile-fail/transmute-type-parameters.rs
Normal file
48
src/test/compile-fail/transmute-type-parameters.rs
Normal 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() {}
|
Loading…
Add table
Add a link
Reference in a new issue