Skip to content

Commit 330a66e

Browse files
committed
safe transmute: forbid reference lifetime extension
Modifies `BikeshedIntrinsicFrom` to forbid lifetime extensions on references. This static check can be opted out of with the `Assume::lifetimes` flag. Fixes #129097
1 parent 0f442e2 commit 330a66e

File tree

9 files changed

+533
-132
lines changed

9 files changed

+533
-132
lines changed

compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+103-73
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ use rustc_infer::infer::{DefineOpaqueTypes, HigherRankedType, InferOk};
1717
use rustc_infer::traits::ObligationCauseCode;
1818
use rustc_middle::traits::{BuiltinImplSource, SignatureMismatchData};
1919
use rustc_middle::ty::{
20-
self, GenericArgs, GenericArgsRef, GenericParamDefKind, ToPolyTraitRef, TraitPredicate, Ty,
21-
TyCtxt, Upcast,
20+
self, GenericArgs, GenericArgsRef, GenericParamDefKind, ToPolyTraitRef, Ty, TyCtxt, Upcast,
2221
};
2322
use rustc_middle::{bug, span_bug};
2423
use rustc_span::def_id::DefId;
@@ -292,90 +291,120 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
292291
&mut self,
293292
obligation: &PolyTraitObligation<'tcx>,
294293
) -> Result<Vec<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
295-
use rustc_transmute::{Answer, Condition};
296-
#[instrument(level = "debug", skip(tcx, obligation, predicate))]
294+
use rustc_transmute::{Answer, Assume, Condition};
295+
296+
/// Generate sub-obligations for reference-to-reference transmutations.
297+
fn reference_obligations<'tcx>(
298+
tcx: TyCtxt<'tcx>,
299+
obligation: &PolyTraitObligation<'tcx>,
300+
(src_lifetime, src_ty, src_mut): (ty::Region<'tcx>, Ty<'tcx>, Mutability),
301+
(dst_lifetime, dst_ty, dst_mut): (ty::Region<'tcx>, Ty<'tcx>, Mutability),
302+
assume: Assume,
303+
) -> Vec<PredicateObligation<'tcx>> {
304+
let make_transmute_obl = |src, dst| {
305+
let transmute_trait = obligation.predicate.def_id();
306+
let assume = obligation.predicate.skip_binder().trait_ref.args.const_at(2);
307+
let trait_ref = ty::TraitRef::new(
308+
tcx,
309+
transmute_trait,
310+
[
311+
ty::GenericArg::from(dst),
312+
ty::GenericArg::from(src),
313+
ty::GenericArg::from(assume),
314+
],
315+
);
316+
Obligation::with_depth(
317+
tcx,
318+
obligation.cause.clone(),
319+
obligation.recursion_depth + 1,
320+
obligation.param_env,
321+
obligation.predicate.rebind(trait_ref),
322+
)
323+
};
324+
325+
let make_freeze_obl = |ty| {
326+
let trait_ref = ty::TraitRef::new(
327+
tcx,
328+
tcx.require_lang_item(LangItem::Freeze, None),
329+
[ty::GenericArg::from(ty)],
330+
);
331+
Obligation::with_depth(
332+
tcx,
333+
obligation.cause.clone(),
334+
obligation.recursion_depth + 1,
335+
obligation.param_env,
336+
trait_ref,
337+
)
338+
};
339+
340+
let make_outlives_obl = |target, region| {
341+
let outlives = ty::OutlivesPredicate(target, region);
342+
Obligation::with_depth(
343+
tcx,
344+
obligation.cause.clone(),
345+
obligation.recursion_depth + 1,
346+
obligation.param_env,
347+
obligation.predicate.rebind(outlives),
348+
)
349+
};
350+
351+
// Given a transmutation from `&'a (mut) Src` and `&'dst (mut) Dst`,
352+
// it is always the case that `Src` must be transmutable into `Dst`,
353+
// and that that `'src` must outlive `'dst`.
354+
let mut obls = vec![make_transmute_obl(src_ty, dst_ty)];
355+
if !assume.lifetimes {
356+
obls.push(make_outlives_obl(src_lifetime, dst_lifetime));
357+
}
358+
359+
// Given a transmutation from `&Src`, both `Src` and `Dst` must be
360+
// `Freeze`, otherwise, using the transmuted value could lead to
361+
// data races.
362+
if src_mut == Mutability::Not {
363+
obls.extend([make_freeze_obl(src_ty), make_freeze_obl(dst_ty)])
364+
}
365+
366+
// Given a transmutation into `&'dst mut Dst`, it also must be the
367+
// case that `Dst` is transmutable into `Src`. For example,
368+
// transmuting bool -> u8 is OK as long as you can't update that u8
369+
// to be > 1, because you could later transmute the u8 back to a
370+
// bool and get undefined behavior. It also must be the case that
371+
// `'dst` lives exactly as long as `'src`.
372+
if dst_mut == Mutability::Mut {
373+
obls.push(make_transmute_obl(dst_ty, src_ty));
374+
if !assume.lifetimes {
375+
obls.push(make_outlives_obl(dst_lifetime, src_lifetime));
376+
}
377+
}
378+
379+
obls
380+
}
381+
382+
/// Flatten the `Condition` tree into a conjunction of obligations.
383+
#[instrument(level = "debug", skip(tcx, obligation))]
297384
fn flatten_answer_tree<'tcx>(
298385
tcx: TyCtxt<'tcx>,
299386
obligation: &PolyTraitObligation<'tcx>,
300-
predicate: TraitPredicate<'tcx>,
301387
cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
388+
assume: Assume,
302389
) -> Vec<PredicateObligation<'tcx>> {
303390
match cond {
304391
// FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll`
305392
// Not possible until the trait solver supports disjunctions of obligations
306393
Condition::IfAll(conds) | Condition::IfAny(conds) => conds
307394
.into_iter()
308-
.flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond))
395+
.flat_map(|cond| flatten_answer_tree(tcx, obligation, cond, assume))
309396
.collect(),
310-
Condition::IfTransmutable { src, dst } => {
311-
let transmute_trait = obligation.predicate.def_id();
312-
let assume_const = predicate.trait_ref.args.const_at(2);
313-
let make_transmute_obl = |from_ty, to_ty| {
314-
let trait_ref = ty::TraitRef::new(
315-
tcx,
316-
transmute_trait,
317-
[
318-
ty::GenericArg::from(to_ty),
319-
ty::GenericArg::from(from_ty),
320-
ty::GenericArg::from(assume_const),
321-
],
322-
);
323-
Obligation::with_depth(
324-
tcx,
325-
obligation.cause.clone(),
326-
obligation.recursion_depth + 1,
327-
obligation.param_env,
328-
trait_ref,
329-
)
330-
};
331-
332-
let make_freeze_obl = |ty| {
333-
let trait_ref = ty::TraitRef::new(
334-
tcx,
335-
tcx.require_lang_item(LangItem::Freeze, None),
336-
[ty::GenericArg::from(ty)],
337-
);
338-
Obligation::with_depth(
339-
tcx,
340-
obligation.cause.clone(),
341-
obligation.recursion_depth + 1,
342-
obligation.param_env,
343-
trait_ref,
344-
)
345-
};
346-
347-
let mut obls = vec![];
348-
349-
// If the source is a shared reference, it must be `Freeze`;
350-
// otherwise, transmuting could lead to data races.
351-
if src.mutability == Mutability::Not {
352-
obls.extend([make_freeze_obl(src.ty), make_freeze_obl(dst.ty)])
353-
}
354-
355-
// If Dst is mutable, check bidirectionally.
356-
// For example, transmuting bool -> u8 is OK as long as you can't update that u8
357-
// to be > 1, because you could later transmute the u8 back to a bool and get UB.
358-
match dst.mutability {
359-
Mutability::Not => obls.push(make_transmute_obl(src.ty, dst.ty)),
360-
Mutability::Mut => obls.extend([
361-
make_transmute_obl(src.ty, dst.ty),
362-
make_transmute_obl(dst.ty, src.ty),
363-
]),
364-
}
365-
366-
obls
367-
}
397+
Condition::IfTransmutable { src, dst } => reference_obligations(
398+
tcx,
399+
obligation,
400+
(src.lifetime, src.ty, src.mutability),
401+
(dst.lifetime, dst.ty, dst.mutability),
402+
assume,
403+
),
368404
}
369405
}
370406

371-
// We erase regions here because transmutability calls layout queries,
372-
// which does not handle inference regions and doesn't particularly
373-
// care about other regions. Erasing late-bound regions is equivalent
374-
// to instantiating the binder with placeholders then erasing those
375-
// placeholder regions.
376-
let predicate = self
377-
.tcx()
378-
.erase_regions(self.tcx().instantiate_bound_regions_with_erased(obligation.predicate));
407+
let predicate = obligation.predicate.skip_binder();
379408

380409
let Some(assume) = rustc_transmute::Assume::from_const(
381410
self.infcx.tcx,
@@ -387,6 +416,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
387416

388417
let dst = predicate.trait_ref.args.type_at(0);
389418
let src = predicate.trait_ref.args.type_at(1);
419+
390420
debug!(?src, ?dst);
391421
let mut transmute_env = rustc_transmute::TransmuteTypeEnv::new(self.infcx);
392422
let maybe_transmutable = transmute_env.is_transmutable(
@@ -397,7 +427,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
397427

398428
let fully_flattened = match maybe_transmutable {
399429
Answer::No(_) => Err(Unimplemented)?,
400-
Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond),
430+
Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, cond, assume),
401431
Answer::Yes => vec![],
402432
};
403433

compiler/rustc_transmute/src/layout/mod.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ pub mod rustc {
6363
use std::fmt::{self, Write};
6464

6565
use rustc_middle::mir::Mutability;
66-
use rustc_middle::ty::{self, Ty};
66+
use rustc_middle::ty::layout::{LayoutCx, LayoutError};
67+
use rustc_middle::ty::{self, Ty, TyCtxt};
68+
use rustc_target::abi::Layout;
6769

6870
/// A reference in the layout.
6971
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
@@ -120,4 +122,12 @@ pub mod rustc {
120122
self != &Self::Primitive
121123
}
122124
}
125+
126+
pub(crate) fn layout_of<'tcx>(
127+
cx: LayoutCx<'tcx, TyCtxt<'tcx>>,
128+
ty: Ty<'tcx>,
129+
) -> Result<Layout<'tcx>, &'tcx LayoutError<'tcx>> {
130+
use rustc_middle::ty::layout::LayoutOf;
131+
cx.layout_of(ty).map(|tl| tl.layout)
132+
}
123133
}

0 commit comments

Comments
 (0)