Skip to content

Commit 0f91cb8

Browse files
committed
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 rust-lang#12898. [breaking-change]
1 parent 021bea1 commit 0f91cb8

File tree

14 files changed

+340
-39
lines changed

14 files changed

+340
-39
lines changed

src/libcore/intrinsics.rs

+14
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

+2-23
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

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

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

src/librustc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub mod middle {
8484
pub mod expr_use_visitor;
8585
pub mod dependency_format;
8686
pub mod weak_lang_items;
87+
pub mod intrinsicck;
8788
}
8889

8990
pub mod front {

src/librustc/middle/intrinsicck.rs

+155
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

+6
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

+54-7
Original file line numberDiff line numberDiff line change
@@ -389,14 +389,23 @@ pub fn trans_intrinsic(ccx: &CrateContext,
389389
ast_map::NodeExpr(e) => e.span,
390390
_ => fail!("transmute has non-expr arg"),
391391
};
392-
ccx.sess().span_fatal(sp,
392+
ccx.sess().span_bug(sp,
393393
format!("transmute called on types with different sizes: \
394-
{intype} ({insize, plural, =1{# bit} other{# bits}}) to \
395-
{outtype} ({outsize, plural, =1{# bit} other{# bits}})",
396-
intype = ty_to_str(ccx.tcx(), in_type),
397-
insize = in_type_size as uint,
398-
outtype = ty_to_str(ccx.tcx(), out_type),
399-
outsize = out_type_size as uint).as_slice());
394+
{} ({} bit{}) to {} ({} bit{})",
395+
ty_to_str(ccx.tcx(), in_type),
396+
in_type_size as uint,
397+
if in_type_size == 1 {
398+
""
399+
} else {
400+
"s"
401+
},
402+
ty_to_str(ccx.tcx(), out_type),
403+
out_type_size as uint,
404+
if out_type_size == 1 {
405+
""
406+
} else {
407+
"s"
408+
}).as_slice());
400409
}
401410

402411
if !return_type_is_void(ccx, out_type) {
@@ -554,3 +563,41 @@ pub fn trans_intrinsic(ccx: &CrateContext,
554563
}
555564
fcx.cleanup();
556565
}
566+
567+
/// Performs late verification that intrinsics are used correctly. At present,
568+
/// the only intrinsic that needs such verification is `transmute`.
569+
pub fn check_intrinsics(ccx: &CrateContext) {
570+
for transmute_restriction in ccx.tcx
571+
.transmute_restrictions
572+
.borrow()
573+
.iter() {
574+
let llfromtype = type_of::sizing_type_of(ccx,
575+
transmute_restriction.from);
576+
let lltotype = type_of::sizing_type_of(ccx,
577+
transmute_restriction.to);
578+
let from_type_size = machine::llbitsize_of_real(ccx, llfromtype);
579+
let to_type_size = machine::llbitsize_of_real(ccx, lltotype);
580+
if from_type_size != to_type_size {
581+
ccx.sess()
582+
.span_err(transmute_restriction.span,
583+
format!("transmute called on types with different sizes: \
584+
{} ({} bit{}) to {} ({} bit{})",
585+
ty_to_str(ccx.tcx(), transmute_restriction.from),
586+
from_type_size as uint,
587+
if from_type_size == 1 {
588+
""
589+
} else {
590+
"s"
591+
},
592+
ty_to_str(ccx.tcx(), transmute_restriction.to),
593+
to_type_size as uint,
594+
if to_type_size == 1 {
595+
""
596+
} else {
597+
"s"
598+
}).as_slice());
599+
}
600+
}
601+
ccx.sess().abort_if_errors();
602+
}
603+

src/librustc/middle/trans/type_of.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ pub fn sizing_type_of(cx: &CrateContext, t: ty::t) -> Type {
152152
}
153153
}
154154

155-
ty::ty_self(_) | ty::ty_infer(..) | ty::ty_param(..) |
156-
ty::ty_err(..) | ty::ty_vec(_, None) | ty::ty_str => {
155+
ty::ty_self(_) | ty::ty_infer(..) | ty::ty_err(..) |
156+
ty::ty_param(..) | ty::ty_vec(_, None) | ty::ty_str => {
157157
cx.sess().bug(format!("fictitious type {:?} in sizing_type_of()",
158158
ty::get(t).sty).as_slice())
159159
}

src/librustc/middle/ty.rs

+18-2
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,17 @@ pub enum AutoRef {
237237
AutoBorrowObj(Region, ast::Mutability),
238238
}
239239

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

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

364380
pub enum tbox_flag {
@@ -1108,6 +1124,7 @@ pub fn mk_ctxt(s: Session,
11081124
vtable_map: RefCell::new(FnvHashMap::new()),
11091125
dependency_formats: RefCell::new(HashMap::new()),
11101126
node_lint_levels: RefCell::new(HashMap::new()),
1127+
transmute_restrictions: RefCell::new(Vec::new()),
11111128
}
11121129
}
11131130

@@ -2689,8 +2706,7 @@ pub fn pat_ty(cx: &ctxt, pat: &ast::Pat) -> t {
26892706
//
26902707
// NB (2): This type doesn't provide type parameter substitutions; e.g. if you
26912708
// ask for the type of "id" in "id(3)", it will return "fn(&int) -> int"
2692-
// instead of "fn(t) -> T with T = int". If this isn't what you want, see
2693-
// expr_ty_params_and_ty() below.
2709+
// instead of "fn(t) -> T with T = int".
26942710
pub fn expr_ty(cx: &ctxt, expr: &ast::Expr) -> t {
26952711
return node_id_to_type(cx, expr.id);
26962712
}

src/librustc/plugin/load.rs

+4-2
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
};

0 commit comments

Comments
 (0)