Skip to content

Commit c9f3f47

Browse files
pcwaltonalexcrichton
authored andcommitted
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]
1 parent 8c4a10a commit c9f3f47

File tree

13 files changed

+323
-31
lines changed

13 files changed

+323
-31
lines changed

src/libcore/intrinsics.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,20 @@ extern "rust-intrinsic" {
307307
/// `forget` is unsafe because the caller is responsible for
308308
/// ensuring the argument is deallocated already.
309309
pub fn forget<T>(_: T) -> ();
310+
311+
/// Unsafely transforms a value of one type into a value of another type.
312+
///
313+
/// Both types must have the same size and alignment, and this guarantee
314+
/// is enforced at compile-time.
315+
///
316+
/// # Example
317+
///
318+
/// ```rust
319+
/// use std::mem;
320+
///
321+
/// let v: &[u8] = unsafe { mem::transmute("L") };
322+
/// assert!(v == [76u8]);
323+
/// ```
310324
pub fn transmute<T,U>(e: T) -> U;
311325

312326
/// Returns `true` if a type requires drop glue.

src/libcore/mem.rs

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use ptr;
1717
use intrinsics;
1818
use intrinsics::{bswap16, bswap32, bswap64};
1919

20+
pub use intrinsics::transmute;
21+
2022
/// Returns the size of a type in bytes.
2123
#[inline]
2224
#[stable]
@@ -412,29 +414,6 @@ pub fn drop<T>(_x: T) { }
412414
#[stable]
413415
pub unsafe fn forget<T>(thing: T) { intrinsics::forget(thing) }
414416

415-
/// Unsafely transforms a value of one type into a value of another type.
416-
///
417-
/// Both types must have the same size and alignment, and this guarantee is
418-
/// enforced at compile-time.
419-
///
420-
/// # Example
421-
///
422-
/// ```rust
423-
/// use std::mem;
424-
///
425-
/// let v: &[u8] = unsafe { mem::transmute("L") };
426-
/// assert!(v == [76u8]);
427-
/// ```
428-
#[inline]
429-
#[unstable = "this function will be modified to reject invocations of it which \
430-
cannot statically prove that T and U are the same size. For \
431-
example, this function, as written today, will be rejected in \
432-
the future because the size of T and U cannot be statically \
433-
known to be the same"]
434-
pub unsafe fn transmute<T, U>(thing: T) -> U {
435-
intrinsics::transmute(thing)
436-
}
437-
438417
/// Interprets `src` as `&U`, and then reads `src` without moving the contained
439418
/// value.
440419
///

src/librustc/driver/driver.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ pub fn phase_3_run_analysis_passes(sess: Session,
333333
time(time_passes, "privacy checking", maps, |(a, b)|
334334
middle::privacy::check_crate(&ty_cx, &exp_map2, a, b, krate));
335335

336+
time(time_passes, "intrinsic checking", (), |_|
337+
middle::intrinsicck::check_crate(&ty_cx, krate));
338+
336339
time(time_passes, "effect checking", (), |_|
337340
middle::effect::check_crate(&ty_cx, krate));
338341

src/librustc/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub mod middle {
8585
pub mod dependency_format;
8686
pub mod weak_lang_items;
8787
pub mod save;
88+
pub mod intrinsicck;
8889
}
8990

9091
pub mod front {

src/librustc/middle/intrinsicck.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use metadata::csearch;
12+
use middle::def::DefFn;
13+
use middle::subst::Subst;
14+
use middle::ty::{TransmuteRestriction, ctxt, ty_bare_fn};
15+
use middle::ty;
16+
17+
use syntax::abi::RustIntrinsic;
18+
use syntax::ast::DefId;
19+
use syntax::ast;
20+
use syntax::ast_map::NodeForeignItem;
21+
use syntax::codemap::Span;
22+
use syntax::parse::token;
23+
use syntax::visit::Visitor;
24+
use syntax::visit;
25+
26+
fn type_size_is_affected_by_type_parameters(tcx: &ty::ctxt, typ: ty::t)
27+
-> bool {
28+
let mut result = false;
29+
ty::maybe_walk_ty(typ, |typ| {
30+
match ty::get(typ).sty {
31+
ty::ty_box(_) | ty::ty_uniq(_) | ty::ty_ptr(_) |
32+
ty::ty_rptr(..) | ty::ty_bare_fn(..) | ty::ty_closure(..) => {
33+
false
34+
}
35+
ty::ty_param(_) => {
36+
result = true;
37+
// No need to continue; we now know the result.
38+
false
39+
}
40+
ty::ty_enum(did, ref substs) => {
41+
for enum_variant in (*ty::enum_variants(tcx, did)).iter() {
42+
for argument_type in enum_variant.args.iter() {
43+
let argument_type = argument_type.subst(tcx, substs);
44+
result = result ||
45+
type_size_is_affected_by_type_parameters(
46+
tcx,
47+
argument_type);
48+
}
49+
}
50+
51+
// Don't traverse substitutions.
52+
false
53+
}
54+
ty::ty_struct(did, ref substs) => {
55+
for field in ty::struct_fields(tcx, did, substs).iter() {
56+
result = result ||
57+
type_size_is_affected_by_type_parameters(tcx,
58+
field.mt.ty);
59+
}
60+
61+
// Don't traverse substitutions.
62+
false
63+
}
64+
_ => true,
65+
}
66+
});
67+
result
68+
}
69+
70+
struct IntrinsicCheckingVisitor<'a> {
71+
tcx: &'a ctxt,
72+
}
73+
74+
impl<'a> IntrinsicCheckingVisitor<'a> {
75+
fn def_id_is_transmute(&self, def_id: DefId) -> bool {
76+
if def_id.krate == ast::LOCAL_CRATE {
77+
match self.tcx.map.get(def_id.node) {
78+
NodeForeignItem(ref item) => {
79+
token::get_ident(item.ident) ==
80+
token::intern_and_get_ident("transmute")
81+
}
82+
_ => false,
83+
}
84+
} else {
85+
match csearch::get_item_path(self.tcx, def_id).last() {
86+
None => false,
87+
Some(ref last) => {
88+
token::get_name(last.name()) ==
89+
token::intern_and_get_ident("transmute")
90+
}
91+
}
92+
}
93+
}
94+
95+
fn check_transmute(&self, span: Span, from: ty::t, to: ty::t) {
96+
if type_size_is_affected_by_type_parameters(self.tcx, from) {
97+
self.tcx.sess.span_err(span,
98+
"cannot transmute from a type that \
99+
contains type parameters");
100+
}
101+
if type_size_is_affected_by_type_parameters(self.tcx, to) {
102+
self.tcx.sess.span_err(span,
103+
"cannot transmute to a type that contains \
104+
type parameters");
105+
}
106+
107+
let restriction = TransmuteRestriction {
108+
span: span,
109+
from: from,
110+
to: to,
111+
};
112+
self.tcx.transmute_restrictions.borrow_mut().push(restriction);
113+
}
114+
}
115+
116+
impl<'a> Visitor<()> for IntrinsicCheckingVisitor<'a> {
117+
fn visit_expr(&mut self, expr: &ast::Expr, (): ()) {
118+
match expr.node {
119+
ast::ExprPath(..) => {
120+
match ty::resolve_expr(self.tcx, expr) {
121+
DefFn(did, _) if self.def_id_is_transmute(did) => {
122+
let typ = ty::node_id_to_type(self.tcx, expr.id);
123+
match ty::get(typ).sty {
124+
ty_bare_fn(ref bare_fn_ty)
125+
if bare_fn_ty.abi == RustIntrinsic => {
126+
let from = *bare_fn_ty.sig.inputs.get(0);
127+
let to = bare_fn_ty.sig.output;
128+
self.check_transmute(expr.span, from, to);
129+
}
130+
_ => {
131+
self.tcx
132+
.sess
133+
.span_bug(expr.span,
134+
"transmute wasn't a bare fn?!");
135+
}
136+
}
137+
}
138+
_ => {}
139+
}
140+
}
141+
_ => {}
142+
}
143+
144+
visit::walk_expr(self, expr, ());
145+
}
146+
}
147+
148+
pub fn check_crate(tcx: &ctxt, krate: &ast::Crate) {
149+
let mut visitor = IntrinsicCheckingVisitor {
150+
tcx: tcx,
151+
};
152+
153+
visit::walk_crate(&mut visitor, krate, ());
154+
}
155+

src/librustc/middle/trans/base.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use middle::trans::expr;
5959
use middle::trans::foreign;
6060
use middle::trans::glue;
6161
use middle::trans::inline;
62+
use middle::trans::intrinsic;
6263
use middle::trans::machine;
6364
use middle::trans::machine::{llalign_of_min, llsize_of, llsize_of_real};
6465
use middle::trans::meth;
@@ -2329,6 +2330,11 @@ pub fn trans_crate(krate: ast::Crate,
23292330

23302331
let ccx = CrateContext::new(llmod_id.as_slice(), tcx, exp_map2,
23312332
Sha256::new(), link_meta, reachable);
2333+
2334+
// First, verify intrinsics.
2335+
intrinsic::check_intrinsics(&ccx);
2336+
2337+
// Next, translate the module.
23322338
{
23332339
let _icx = push_ctxt("text");
23342340
trans_mod(&ccx, &krate.module);

src/librustc/middle/trans/intrinsic.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ pub fn trans_intrinsic(ccx: &CrateContext,
390390
ast_map::NodeExpr(e) => e.span,
391391
_ => fail!("transmute has non-expr arg"),
392392
};
393-
ccx.sess().span_fatal(sp,
393+
ccx.sess().span_bug(sp,
394394
format!("transmute called on types with different sizes: \
395395
{} ({} bit{}) to \
396396
{} ({} bit{})",
@@ -564,3 +564,41 @@ pub fn trans_intrinsic(ccx: &CrateContext,
564564
}
565565
fcx.cleanup();
566566
}
567+
568+
/// Performs late verification that intrinsics are used correctly. At present,
569+
/// the only intrinsic that needs such verification is `transmute`.
570+
pub fn check_intrinsics(ccx: &CrateContext) {
571+
for transmute_restriction in ccx.tcx
572+
.transmute_restrictions
573+
.borrow()
574+
.iter() {
575+
let llfromtype = type_of::sizing_type_of(ccx,
576+
transmute_restriction.from);
577+
let lltotype = type_of::sizing_type_of(ccx,
578+
transmute_restriction.to);
579+
let from_type_size = machine::llbitsize_of_real(ccx, llfromtype);
580+
let to_type_size = machine::llbitsize_of_real(ccx, lltotype);
581+
if from_type_size != to_type_size {
582+
ccx.sess()
583+
.span_err(transmute_restriction.span,
584+
format!("transmute called on types with different sizes: \
585+
{} ({} bit{}) to {} ({} bit{})",
586+
ty_to_str(ccx.tcx(), transmute_restriction.from),
587+
from_type_size as uint,
588+
if from_type_size == 1 {
589+
""
590+
} else {
591+
"s"
592+
},
593+
ty_to_str(ccx.tcx(), transmute_restriction.to),
594+
to_type_size as uint,
595+
if to_type_size == 1 {
596+
""
597+
} else {
598+
"s"
599+
}).as_slice());
600+
}
601+
}
602+
ccx.sess().abort_if_errors();
603+
}
604+

src/librustc/middle/ty.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ pub enum AutoRef {
235235
AutoBorrowObj(Region, ast::Mutability),
236236
}
237237

238+
/// A restriction that certain types must be the same size. The use of
239+
/// `transmute` gives rise to these restrictions.
240+
pub struct TransmuteRestriction {
241+
/// The span from whence the restriction comes.
242+
pub span: Span,
243+
/// The type being transmuted from.
244+
pub from: t,
245+
/// The type being transmuted to.
246+
pub to: t,
247+
}
248+
238249
/// The data structure to keep track of all the information that typechecker
239250
/// generates so that so that it can be reused and doesn't have to be redone
240251
/// later on.
@@ -357,6 +368,11 @@ pub struct ctxt {
357368

358369
pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::Lint),
359370
(lint::Level, lint::LintSource)>>,
371+
372+
/// The types that must be asserted to be the same size for `transmute`
373+
/// to be valid. We gather up these restrictions in the intrinsicck pass
374+
/// and check them in trans.
375+
pub transmute_restrictions: RefCell<Vec<TransmuteRestriction>>,
360376
}
361377

362378
pub enum tbox_flag {
@@ -1118,6 +1134,7 @@ pub fn mk_ctxt(s: Session,
11181134
vtable_map: RefCell::new(FnvHashMap::new()),
11191135
dependency_formats: RefCell::new(HashMap::new()),
11201136
node_lint_levels: RefCell::new(HashMap::new()),
1137+
transmute_restrictions: RefCell::new(Vec::new()),
11211138
}
11221139
}
11231140

@@ -2711,8 +2728,7 @@ pub fn pat_ty(cx: &ctxt, pat: &ast::Pat) -> t {
27112728
//
27122729
// NB (2): This type doesn't provide type parameter substitutions; e.g. if you
27132730
// ask for the type of "id" in "id(3)", it will return "fn(&int) -> int"
2714-
// instead of "fn(t) -> T with T = int". If this isn't what you want, see
2715-
// expr_ty_params_and_ty() below.
2731+
// instead of "fn(t) -> T with T = int".
27162732
pub fn expr_ty(cx: &ctxt, expr: &ast::Expr) -> t {
27172733
return node_id_to_type(cx, expr.id);
27182734
}

src/librustc/plugin/load.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ impl<'a> PluginLoader<'a> {
126126
};
127127

128128
unsafe {
129-
let registrar: PluginRegistrarFun =
129+
let registrar =
130130
match lib.symbol(symbol.as_slice()) {
131-
Ok(registrar) => registrar,
131+
Ok(registrar) => {
132+
mem::transmute::<*u8,PluginRegistrarFun>(registrar)
133+
}
132134
// again fatal if we can't register macros
133135
Err(err) => self.sess.span_fatal(vi.span, err.as_slice())
134136
};

src/librustdoc/plugins.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use clean;
1212

1313
use dl = std::dynamic_lib;
1414
use serialize::json;
15+
use std::mem;
1516
use std::string::String;
1617

1718
pub type PluginJson = Option<(String, json::Json)>;
@@ -45,9 +46,11 @@ impl PluginManager {
4546
let x = self.prefix.join(libname(name));
4647
let lib_result = dl::DynamicLibrary::open(Some(&x));
4748
let lib = lib_result.unwrap();
48-
let plugin = unsafe { lib.symbol("rustdoc_plugin_entrypoint") }.unwrap();
49+
unsafe {
50+
let plugin = lib.symbol("rustdoc_plugin_entrypoint").unwrap();
51+
self.callbacks.push(mem::transmute::<*u8,PluginCallback>(plugin));
52+
}
4953
self.dylibs.push(lib);
50-
self.callbacks.push(plugin);
5154
}
5255

5356
/// Load a normal Rust function as a plugin.

src/libstd/dynamic_lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl DynamicLibrary {
134134
}
135135

136136
/// Access the value at the symbol of the dynamic library
137-
pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<T, String> {
137+
pub unsafe fn symbol<T>(&self, symbol: &str) -> Result<*T, String> {
138138
// This function should have a lifetime constraint of 'a on
139139
// T but that feature is still unimplemented
140140

0 commit comments

Comments
 (0)