Skip to content

Commit b4e96a4

Browse files
committed
Implement a more strict transmute check
This checks whether types being transmuted from and to have a specified layout.
1 parent b58b721 commit b4e96a4

File tree

3 files changed

+156
-41
lines changed

3 files changed

+156
-41
lines changed

src/librustc/diagnostics.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2133,8 +2133,8 @@ register_diagnostics! {
21332133
E0688, // in-band lifetimes cannot be mixed with explicit lifetime binders
21342134

21352135
E0697, // closures cannot be static
2136-
21372136
E0707, // multiple elided lifetimes used in arguments of `async fn`
21382137
E0708, // `async` non-`move` closures with arguments are not currently supported
21392138
E0709, // multiple different lifetimes used in arguments of `async fn`
2139+
E0912, // transmutation between types of unspecified layout
21402140
}

src/librustc/middle/intrinsicck.rs

+153-4
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use hir;
1112
use hir::def::Def;
1213
use hir::def_id::DefId;
13-
use ty::{self, Ty, TyCtxt};
14+
use hir::intravisit::{self, Visitor, NestedVisitorMap};
1415
use ty::layout::{LayoutError, Pointer, SizeSkeleton};
16+
use ty::{self, Ty, AdtKind, TyCtxt, TypeFoldable};
17+
use ty::subst::Substs;
1518

1619
use rustc_target::spec::abi::Abi::RustIntrinsic;
20+
use syntax_pos::DUMMY_SP;
1721
use syntax_pos::Span;
18-
use hir::intravisit::{self, Visitor, NestedVisitorMap};
19-
use hir;
2022

2123
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
2224
let mut visitor = ItemVisitor {
@@ -64,17 +66,164 @@ fn unpack_option_like<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
6466
ty
6567
}
6668

69+
/// Check if this enum can be safely exported based on the
70+
/// "nullable pointer optimization". Currently restricted
71+
/// to function pointers and references, but could be
72+
/// expanded to cover NonZero raw pointers and newtypes.
73+
/// FIXME: This duplicates code in codegen.
74+
pub fn is_repr_nullable_ptr<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
75+
def: &'tcx ty::AdtDef,
76+
substs: &Substs<'tcx>)
77+
-> bool {
78+
if def.variants.len() == 2 {
79+
let data_idx;
80+
81+
if def.variants[0].fields.is_empty() {
82+
data_idx = 1;
83+
} else if def.variants[1].fields.is_empty() {
84+
data_idx = 0;
85+
} else {
86+
return false;
87+
}
88+
89+
if def.variants[data_idx].fields.len() == 1 {
90+
match def.variants[data_idx].fields[0].ty(tcx, substs).sty {
91+
ty::TyFnPtr(_) => {
92+
return true;
93+
}
94+
ty::TyRef(..) => {
95+
return true;
96+
}
97+
_ => {}
98+
}
99+
}
100+
}
101+
false
102+
}
103+
67104
impl<'a, 'tcx> ExprVisitor<'a, 'tcx> {
68105
fn def_id_is_transmute(&self, def_id: DefId) -> bool {
69106
self.tcx.fn_sig(def_id).abi() == RustIntrinsic &&
70107
self.tcx.item_name(def_id) == "transmute"
71108
}
72109

110+
/// Calculate whether a type has a specified layout.
111+
///
112+
/// The function returns `None` in indeterminate cases (such as `TyError`).
113+
fn is_layout_specified(&self, ty: Ty<'tcx>) -> Option<bool> {
114+
match ty.sty {
115+
// These types have a specified layout
116+
// Reference: Primitive type layout
117+
ty::TyBool |
118+
ty::TyChar |
119+
ty::TyInt(_) |
120+
ty::TyUint(_) |
121+
ty::TyFloat(_) |
122+
// Reference: Pointers and references layout
123+
ty::TyFnPtr(_) => Some(true),
124+
// Reference: Array layout (depends on the contained type)
125+
ty::TyArray(ty, _) => self.is_layout_specified(ty),
126+
// Reference: Tuple layout (only specified if empty)
127+
ty::TyTuple(ref tys) => Some(tys.is_empty()),
128+
129+
// Cases with definitely unspecified layouts
130+
ty::TyClosure(_, _) |
131+
ty::TyGenerator(_, _, _) |
132+
ty::TyGeneratorWitness(_) => Some(false),
133+
// Currently ZST, but does not seem to be guaranteed
134+
ty::TyFnDef(_, _) => Some(false),
135+
// Unsized types
136+
ty::TyForeign(_) |
137+
ty::TyNever |
138+
ty::TyStr |
139+
ty::TySlice(_) |
140+
ty::TyDynamic(_, _) => Some(false),
141+
142+
// Indeterminate cases
143+
ty::TyInfer(_) |
144+
// should we report `Some(false)` for TyParam(_)? It this possible to reach this branch?
145+
ty::TyParam(_) |
146+
ty::TyError => None,
147+
148+
// The “it’s complicated™” cases
149+
// Reference: Pointers and references layout
150+
ty::TyRawPtr(ty::TypeAndMut { ty: pointee, .. }) |
151+
ty::TyRef(_, pointee, _) => {
152+
let pointee = self.tcx.normalize_erasing_regions(self.param_env, pointee);
153+
// Pointers to unsized types have no specified layout.
154+
Some(pointee.is_sized(self.tcx.at(DUMMY_SP), self.param_env))
155+
}
156+
ty::TyProjection(_) | ty::TyAnon(_, _) => {
157+
let normalized = self.tcx.normalize_erasing_regions(self.param_env, ty);
158+
if ty == normalized {
159+
None
160+
} else {
161+
self.is_layout_specified(normalized)
162+
}
163+
}
164+
ty::TyAdt(def, substs) => {
165+
// Documentation guarantees 0-size.
166+
if def.is_phantom_data() {
167+
return Some(true);
168+
}
169+
match def.adt_kind() {
170+
AdtKind::Struct | AdtKind::Union => {
171+
if !def.repr.c() && !def.repr.transparent() && !def.repr.simd() {
172+
return Some(false);
173+
}
174+
// FIXME: do we guarantee 0-sizedness for structs with 0 fields?
175+
// If not, they should cause Some(false) here.
176+
let mut seen_none = false;
177+
for field in &def.non_enum_variant().fields {
178+
let field_ty = field.ty(self.tcx, substs);
179+
match self.is_layout_specified(field_ty) {
180+
Some(true) => continue,
181+
None => {
182+
seen_none = true;
183+
continue;
184+
}
185+
x => return x,
186+
}
187+
}
188+
return if seen_none { None } else { Some(true) };
189+
}
190+
AdtKind::Enum => {
191+
if !def.repr.c() && def.repr.int.is_none() {
192+
if !is_repr_nullable_ptr(self.tcx, def, substs) {
193+
return Some(false);
194+
}
195+
}
196+
return Some(true);
197+
}
198+
}
199+
}
200+
}
201+
}
202+
73203
fn check_transmute(&self, span: Span, from: Ty<'tcx>, to: Ty<'tcx>) {
204+
// Check for unspecified types before checking for same size.
205+
assert!(!from.has_infer_types());
206+
assert!(!to.has_infer_types());
207+
208+
let unspecified_layout = |msg, ty| {
209+
struct_span_err!(self.tcx.sess, span, E0912, "{}", msg)
210+
.note(&format!("{} has an unspecified layout", ty))
211+
.note("this will become a hard error in the future")
212+
.emit();
213+
};
214+
215+
if self.is_layout_specified(from) == Some(false) {
216+
unspecified_layout("transmutation from a type with an unspecified layout", from);
217+
}
218+
219+
if self.is_layout_specified(to) == Some(false) {
220+
unspecified_layout("transmutation to a type with an unspecified layout", to);
221+
}
222+
223+
// Check for same size using the skeletons.
74224
let sk_from = SizeSkeleton::compute(from, self.tcx, self.param_env);
75225
let sk_to = SizeSkeleton::compute(to, self.tcx, self.param_env);
76226

77-
// Check for same size using the skeletons.
78227
if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
79228
if sk_from.same_size(sk_to) {
80229
return;

src/librustc_lint/types.rs

+2-36
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
#![allow(non_snake_case)]
1212

1313
use rustc::hir::map as hir_map;
14-
use rustc::ty::subst::Substs;
15-
use rustc::ty::{self, AdtKind, ParamEnv, Ty, TyCtxt};
14+
use rustc::ty::{self, AdtKind, ParamEnv, Ty};
1615
use rustc::ty::layout::{self, IntegerExt, LayoutOf};
16+
use rustc::middle::intrinsicck::is_repr_nullable_ptr;
1717
use util::nodemap::FxHashSet;
1818
use lint::{LateContext, LintContext, LintArray};
1919
use lint::{LintPass, LateLintPass};
@@ -428,40 +428,6 @@ enum FfiResult<'tcx> {
428428
},
429429
}
430430

431-
/// Check if this enum can be safely exported based on the
432-
/// "nullable pointer optimization". Currently restricted
433-
/// to function pointers and references, but could be
434-
/// expanded to cover NonZero raw pointers and newtypes.
435-
/// FIXME: This duplicates code in codegen.
436-
fn is_repr_nullable_ptr<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
437-
def: &'tcx ty::AdtDef,
438-
substs: &Substs<'tcx>)
439-
-> bool {
440-
if def.variants.len() == 2 {
441-
let data_idx;
442-
443-
if def.variants[0].fields.is_empty() {
444-
data_idx = 1;
445-
} else if def.variants[1].fields.is_empty() {
446-
data_idx = 0;
447-
} else {
448-
return false;
449-
}
450-
451-
if def.variants[data_idx].fields.len() == 1 {
452-
match def.variants[data_idx].fields[0].ty(tcx, substs).sty {
453-
ty::TyFnPtr(_) => {
454-
return true;
455-
}
456-
ty::TyRef(..) => {
457-
return true;
458-
}
459-
_ => {}
460-
}
461-
}
462-
}
463-
false
464-
}
465431

466432
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
467433
/// Check if the given type is "ffi-safe" (has a stable, well-defined

0 commit comments

Comments
 (0)