Skip to content

Commit 3ed2a10

Browse files
committed
Auto merge of #110662 - bryangarza:safe-transmute-reference-types, r=compiler-errors
Safe Transmute: Enable handling references This patch enables support for references in Safe Transmute, by generating nested obligations during trait selection. Specifically, when we call `confirm_transmutability_candidate(...)`, we now recursively traverse the `rustc_transmute::Answer` tree and create obligations for all the `Answer` variants, some of which include multiple nested `Answer`s.
2 parents 57c215b + f4cf8f6 commit 3ed2a10

32 files changed

+789
-206
lines changed

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -709,18 +709,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
709709
scope: Ty<'tcx>,
710710
assume: rustc_transmute::Assume,
711711
) -> Result<Certainty, NoSolution> {
712+
use rustc_transmute::Answer;
712713
// FIXME(transmutability): This really should be returning nested goals for `Answer::If*`
713714
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
714715
ObligationCause::dummy(),
715716
src_and_dst,
716717
scope,
717718
assume,
718719
) {
719-
rustc_transmute::Answer::Yes => Ok(Certainty::Yes),
720-
rustc_transmute::Answer::No(_)
721-
| rustc_transmute::Answer::IfTransmutable { .. }
722-
| rustc_transmute::Answer::IfAll(_)
723-
| rustc_transmute::Answer::IfAny(_) => Err(NoSolution),
720+
Answer::Yes => Ok(Certainty::Yes),
721+
Answer::No(_) | Answer::If(_) => Err(NoSolution),
724722
}
725723
}
726724

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+45-11
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ pub struct ImplCandidate<'tcx> {
6666
pub similarity: CandidateSimilarity,
6767
}
6868

69+
enum GetSafeTransmuteErrorAndReason {
70+
Silent,
71+
Error { err_msg: String, safe_transmute_explanation: String },
72+
}
73+
6974
pub trait InferCtxtExt<'tcx> {
7075
/// Given some node representing a fn-like thing in the HIR map,
7176
/// returns a span and `ArgKind` information that describes the
@@ -739,11 +744,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
739744
== self.tcx.lang_items().transmute_trait()
740745
{
741746
// Recompute the safe transmute reason and use that for the error reporting
742-
self.get_safe_transmute_error_and_reason(
747+
match self.get_safe_transmute_error_and_reason(
743748
obligation.clone(),
744749
trait_ref,
745750
span,
746-
)
751+
) {
752+
GetSafeTransmuteErrorAndReason::Silent => return,
753+
GetSafeTransmuteErrorAndReason::Error {
754+
err_msg,
755+
safe_transmute_explanation,
756+
} => (err_msg, Some(safe_transmute_explanation)),
757+
}
747758
} else {
748759
(err_msg, None)
749760
};
@@ -1403,7 +1414,7 @@ trait InferCtxtPrivExt<'tcx> {
14031414
obligation: PredicateObligation<'tcx>,
14041415
trait_ref: ty::PolyTraitRef<'tcx>,
14051416
span: Span,
1406-
) -> (String, Option<String>);
1417+
) -> GetSafeTransmuteErrorAndReason;
14071418

14081419
fn add_tuple_trait_message(
14091420
&self,
@@ -2850,7 +2861,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
28502861
obligation: PredicateObligation<'tcx>,
28512862
trait_ref: ty::PolyTraitRef<'tcx>,
28522863
span: Span,
2853-
) -> (String, Option<String>) {
2864+
) -> GetSafeTransmuteErrorAndReason {
2865+
use rustc_transmute::Answer;
2866+
28542867
// Erase regions because layout code doesn't particularly care about regions.
28552868
let trait_ref = self.tcx.erase_regions(self.tcx.erase_late_bound_regions(trait_ref));
28562869

@@ -2863,19 +2876,20 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
28632876
rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, trait_ref.substs.const_at(3)) else {
28642877
span_bug!(span, "Unable to construct rustc_transmute::Assume where it was previously possible");
28652878
};
2879+
28662880
match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable(
28672881
obligation.cause,
28682882
src_and_dst,
28692883
scope,
28702884
assume,
28712885
) {
2872-
rustc_transmute::Answer::No(reason) => {
2886+
Answer::No(reason) => {
28732887
let dst = trait_ref.substs.type_at(0);
28742888
let src = trait_ref.substs.type_at(1);
2875-
let custom_err_msg = format!(
2889+
let err_msg = format!(
28762890
"`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`"
28772891
);
2878-
let reason_msg = match reason {
2892+
let safe_transmute_explanation = match reason {
28792893
rustc_transmute::Reason::SrcIsUnspecified => {
28802894
format!("`{src}` does not have a well-specified layout")
28812895
}
@@ -2891,19 +2905,39 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
28912905
rustc_transmute::Reason::DstIsPrivate => format!(
28922906
"`{dst}` is or contains a type or field that is not visible in that scope"
28932907
),
2894-
// FIXME(bryangarza): Include the number of bytes of src and dst
28952908
rustc_transmute::Reason::DstIsTooBig => {
28962909
format!("The size of `{src}` is smaller than the size of `{dst}`")
28972910
}
2911+
rustc_transmute::Reason::DstHasStricterAlignment {
2912+
src_min_align,
2913+
dst_min_align,
2914+
} => {
2915+
format!(
2916+
"The minimum alignment of `{src}` ({src_min_align}) should be greater than that of `{dst}` ({dst_min_align})"
2917+
)
2918+
}
2919+
rustc_transmute::Reason::DstIsMoreUnique => {
2920+
format!("`{src}` is a shared reference, but `{dst}` is a unique reference")
2921+
}
2922+
// Already reported by rustc
2923+
rustc_transmute::Reason::TypeError => {
2924+
return GetSafeTransmuteErrorAndReason::Silent;
2925+
}
2926+
rustc_transmute::Reason::SrcLayoutUnknown => {
2927+
format!("`{src}` has an unknown layout")
2928+
}
2929+
rustc_transmute::Reason::DstLayoutUnknown => {
2930+
format!("`{dst}` has an unknown layout")
2931+
}
28982932
};
2899-
(custom_err_msg, Some(reason_msg))
2933+
GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation }
29002934
}
29012935
// Should never get a Yes at this point! We already ran it before, and did not get a Yes.
2902-
rustc_transmute::Answer::Yes => span_bug!(
2936+
Answer::Yes => span_bug!(
29032937
span,
29042938
"Inconsistent rustc_transmute::is_transmutable(...) result, got Yes",
29052939
),
2906-
_ => span_bug!(span, "Unsupported rustc_transmute::Reason variant"),
2940+
other => span_bug!(span, "Unsupported rustc_transmute::Answer variant: `{other:?}`"),
29072941
}
29082942
}
29092943

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

+64-10
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
//!
77
//! [rustc dev guide]:
88
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
9+
use rustc_ast::Mutability;
910
use rustc_data_structures::stack::ensure_sufficient_stack;
1011
use rustc_hir::lang_items::LangItem;
1112
use rustc_infer::infer::LateBoundRegionConversionTime::HigherRankedType;
1213
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
1314
use rustc_middle::traits::SelectionOutputTypeParameterMismatch;
1415
use rustc_middle::ty::{
1516
self, Binder, GenericParamDefKind, InternalSubsts, SubstsRef, ToPolyTraitRef, ToPredicate,
16-
TraitRef, Ty, TyCtxt, TypeVisitableExt,
17+
TraitPredicate, TraitRef, Ty, TyCtxt, TypeVisitableExt,
1718
};
1819
use rustc_session::config::TraitSolver;
1920
use rustc_span::def_id::DefId;
@@ -279,11 +280,60 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
279280
ImplSourceBuiltinData { nested: obligations }
280281
}
281282

283+
#[instrument(level = "debug", skip(self))]
282284
fn confirm_transmutability_candidate(
283285
&mut self,
284286
obligation: &TraitObligation<'tcx>,
285287
) -> Result<ImplSourceBuiltinData<PredicateObligation<'tcx>>, SelectionError<'tcx>> {
286-
debug!(?obligation, "confirm_transmutability_candidate");
288+
use rustc_transmute::{Answer, Condition};
289+
#[instrument(level = "debug", skip(tcx, obligation, predicate))]
290+
fn flatten_answer_tree<'tcx>(
291+
tcx: TyCtxt<'tcx>,
292+
obligation: &TraitObligation<'tcx>,
293+
predicate: TraitPredicate<'tcx>,
294+
cond: Condition<rustc_transmute::layout::rustc::Ref<'tcx>>,
295+
) -> Vec<PredicateObligation<'tcx>> {
296+
match cond {
297+
// FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll`
298+
// Not possible until the trait solver supports disjunctions of obligations
299+
Condition::IfAll(conds) | Condition::IfAny(conds) => conds
300+
.into_iter()
301+
.flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond))
302+
.collect(),
303+
Condition::IfTransmutable { src, dst } => {
304+
let trait_def_id = obligation.predicate.def_id();
305+
let scope = predicate.trait_ref.substs.type_at(2);
306+
let assume_const = predicate.trait_ref.substs.const_at(3);
307+
let make_obl = |from_ty, to_ty| {
308+
let trait_ref1 = ty::TraitRef::new(
309+
tcx,
310+
trait_def_id,
311+
[
312+
ty::GenericArg::from(to_ty),
313+
ty::GenericArg::from(from_ty),
314+
ty::GenericArg::from(scope),
315+
ty::GenericArg::from(assume_const),
316+
],
317+
);
318+
Obligation::with_depth(
319+
tcx,
320+
obligation.cause.clone(),
321+
obligation.recursion_depth + 1,
322+
obligation.param_env,
323+
trait_ref1,
324+
)
325+
};
326+
327+
// If Dst is mutable, check bidirectionally.
328+
// For example, transmuting bool -> u8 is OK as long as you can't update that u8
329+
// to be > 1, because you could later transmute the u8 back to a bool and get UB.
330+
match dst.mutability {
331+
Mutability::Not => vec![make_obl(src.ty, dst.ty)],
332+
Mutability::Mut => vec![make_obl(src.ty, dst.ty), make_obl(dst.ty, src.ty)],
333+
}
334+
}
335+
}
336+
}
287337

288338
// We erase regions here because transmutability calls layout queries,
289339
// which does not handle inference regions and doesn't particularly
@@ -301,21 +351,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
301351
return Err(Unimplemented);
302352
};
303353

354+
let dst = predicate.trait_ref.substs.type_at(0);
355+
let src = predicate.trait_ref.substs.type_at(1);
356+
debug!(?src, ?dst);
304357
let mut transmute_env = rustc_transmute::TransmuteTypeEnv::new(self.infcx);
305358
let maybe_transmutable = transmute_env.is_transmutable(
306359
obligation.cause.clone(),
307-
rustc_transmute::Types {
308-
dst: predicate.trait_ref.substs.type_at(0),
309-
src: predicate.trait_ref.substs.type_at(1),
310-
},
360+
rustc_transmute::Types { dst, src },
311361
predicate.trait_ref.substs.type_at(2),
312362
assume,
313363
);
314364

315-
match maybe_transmutable {
316-
rustc_transmute::Answer::Yes => Ok(ImplSourceBuiltinData { nested: vec![] }),
317-
_ => Err(Unimplemented),
318-
}
365+
let fully_flattened = match maybe_transmutable {
366+
Answer::No(_) => Err(Unimplemented)?,
367+
Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond),
368+
Answer::Yes => vec![],
369+
};
370+
371+
debug!(?fully_flattened);
372+
Ok(ImplSourceBuiltinData { nested: fully_flattened })
319373
}
320374

321375
/// This handles the case where an `auto trait Foo` impl is being used.

compiler/rustc_transmute/src/layout/mod.rs

+29-13
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,49 @@ impl fmt::Debug for Byte {
3030
}
3131

3232
pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {}
33-
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {}
33+
pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {
34+
fn min_align(&self) -> usize;
35+
36+
fn is_mutable(&self) -> bool;
37+
}
3438

3539
impl Def for ! {}
36-
impl Ref for ! {}
40+
impl Ref for ! {
41+
fn min_align(&self) -> usize {
42+
unreachable!()
43+
}
44+
fn is_mutable(&self) -> bool {
45+
unreachable!()
46+
}
47+
}
3748

3849
#[cfg(feature = "rustc")]
39-
pub(crate) mod rustc {
50+
pub mod rustc {
4051
use rustc_middle::mir::Mutability;
41-
use rustc_middle::ty;
42-
use rustc_middle::ty::Region;
43-
use rustc_middle::ty::Ty;
52+
use rustc_middle::ty::{self, Ty};
4453

4554
/// A reference in the layout.
4655
#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
4756
pub struct Ref<'tcx> {
48-
lifetime: Region<'tcx>,
49-
ty: Ty<'tcx>,
50-
mutability: Mutability,
57+
pub lifetime: ty::Region<'tcx>,
58+
pub ty: Ty<'tcx>,
59+
pub mutability: Mutability,
60+
pub align: usize,
5161
}
5262

53-
impl<'tcx> super::Ref for Ref<'tcx> {}
63+
impl<'tcx> super::Ref for Ref<'tcx> {
64+
fn min_align(&self) -> usize {
65+
self.align
66+
}
5467

55-
impl<'tcx> Ref<'tcx> {
56-
pub fn min_align(&self) -> usize {
57-
todo!()
68+
fn is_mutable(&self) -> bool {
69+
match self.mutability {
70+
Mutability::Mut => true,
71+
Mutability::Not => false,
72+
}
5873
}
5974
}
75+
impl<'tcx> Ref<'tcx> {}
6076

6177
/// A visibility node in the layout.
6278
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]

compiler/rustc_transmute/src/layout/tree.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,14 @@ pub(crate) mod rustc {
188188
/// The layout of the type is unspecified.
189189
Unspecified,
190190
/// This error will be surfaced elsewhere by rustc, so don't surface it.
191-
Unknown,
191+
UnknownLayout,
192192
TypeError(ErrorGuaranteed),
193193
}
194194

195195
impl<'tcx> From<LayoutError<'tcx>> for Err {
196196
fn from(err: LayoutError<'tcx>) -> Self {
197197
match err {
198-
LayoutError::Unknown(..) => Self::Unknown,
198+
LayoutError::Unknown(..) => Self::UnknownLayout,
199199
err => unimplemented!("{:?}", err),
200200
}
201201
}
@@ -365,6 +365,17 @@ pub(crate) mod rustc {
365365
}
366366
}))
367367
}
368+
369+
ty::Ref(lifetime, ty, mutability) => {
370+
let align = layout_of(tcx, *ty)?.align();
371+
Ok(Tree::Ref(Ref {
372+
lifetime: *lifetime,
373+
ty: *ty,
374+
mutability: *mutability,
375+
align,
376+
}))
377+
}
378+
368379
_ => Err(Err::Unspecified),
369380
}
370381
}

0 commit comments

Comments
 (0)