diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 9df0c50868cb1..fe39d91ee9392 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -119,6 +119,7 @@ lint_builtin_missing_doc = missing documentation for {$article} {$desc} lint_builtin_mutable_transmutes = transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + .note = transmute from `{$from}` to `{$to}` lint_builtin_no_mangle_fn = declaration of a `no_mangle` function lint_builtin_no_mangle_generic = functions generic over types or consts must be mangled @@ -888,6 +889,11 @@ lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe .label = usage of unsafe attribute lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)` +lint_unsafe_cell_conversions = + converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + .label = conversion happend here + .note = conversion from `{$from}` to `{$to}` + lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´ lint_untranslatable_diag = diagnostics should be created using translatable messages diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e130cfc1d736d..0d56dc4ae9e7e 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -54,12 +54,11 @@ use crate::lints::{ BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, - BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes, - BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, - BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, - BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, - BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, - BuiltinWhileTrue, InvalidAsmLabel, + BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinNoMangleGeneric, + BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, + BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, + BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, + BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel, }; use crate::nonstandard_style::{MethodLateContext, method_context}; use crate::{ @@ -1076,72 +1075,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { } } -declare_lint! { - /// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut - /// T` because it is [undefined behavior]. - /// - /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html - /// - /// ### Example - /// - /// ```rust,compile_fail - /// unsafe { - /// let y = std::mem::transmute::<&i32, &mut i32>(&5); - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Certain assumptions are made about aliasing of data, and this transmute - /// violates those assumptions. Consider using [`UnsafeCell`] instead. - /// - /// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html - MUTABLE_TRANSMUTES, - Deny, - "transmuting &T to &mut T is undefined behavior, even if the reference is unused" -} - -declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]); - -impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { - if let Some((&ty::Ref(_, _, from_mutbl), &ty::Ref(_, _, to_mutbl))) = - get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind())) - { - if from_mutbl < to_mutbl { - cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes); - } - } - - fn get_transmute_from_to<'tcx>( - cx: &LateContext<'tcx>, - expr: &hir::Expr<'_>, - ) -> Option<(Ty<'tcx>, Ty<'tcx>)> { - let def = if let hir::ExprKind::Path(ref qpath) = expr.kind { - cx.qpath_res(qpath, expr.hir_id) - } else { - return None; - }; - if let Res::Def(DefKind::Fn, did) = def { - if !def_id_is_transmute(cx, did) { - return None; - } - let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx); - let from = sig.inputs().skip_binder()[0]; - let to = sig.output().skip_binder(); - return Some((from, to)); - } - None - } - - fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { - cx.tcx.is_intrinsic(def_id, sym::transmute) - } - } -} - declare_lint! { /// The `unstable_features` lint detects uses of `#![feature]`. /// @@ -1595,7 +1528,6 @@ declare_lint_pass!( UNUSED_DOC_COMMENTS, NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS, - MUTABLE_TRANSMUTES, UNSTABLE_FEATURES, UNREACHABLE_PUB, TYPE_ALIAS_BOUNDS, diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 4cf5c7b4ff964..8d0280505eb5f 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -61,6 +61,7 @@ mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; mod multiple_supertrait_upcastable; +mod mutable_transmutes; mod non_ascii_idents; mod non_fmt_panic; mod non_local_def; @@ -98,6 +99,7 @@ use let_underscore::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; use multiple_supertrait_upcastable::*; +use mutable_transmutes::*; use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; use non_local_def::*; diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 3a854796f6735..de1bd36b3bf29 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -5,7 +5,8 @@ use std::num::NonZero; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString, ElidedLifetimeInPathSubdiag, - EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp, Subdiagnostic, SuggestionStyle, + EmissionGuarantee, IntoDiagArg, LintDiagnostic, MultiSpan, SubdiagMessageOp, Subdiagnostic, + SuggestionStyle, }; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; @@ -224,9 +225,41 @@ pub(crate) struct BuiltinConstNoMangle { pub suggestion: Span, } +// This would be more convenient as `from: String` and `to: String` instead of `from` and `to` +// being `ConversionBreadcrumbs`, but that'll mean we'll have to format the `Ty`s in the lint code, +// and then we'll ICE when the lint is allowed, because formatting a `Ty` is expensive and so the +// compiler panics if it is done when there aren't diagnostics. +pub(crate) struct ConversionBreadcrumbs<'a> { + pub before_ty: &'static str, + pub ty: Ty<'a>, + pub after_ty: String, +} + +impl IntoDiagArg for ConversionBreadcrumbs<'_> { + fn into_diag_arg(self) -> DiagArgValue { + format!("{}{}{}", self.before_ty, self.ty, self.after_ty).into_diag_arg() + } +} + +// mutable_transmutes.rs #[derive(LintDiagnostic)] #[diag(lint_builtin_mutable_transmutes)] -pub(crate) struct BuiltinMutablesTransmutes; +#[note] +pub(crate) struct BuiltinMutablesTransmutes<'a> { + pub from: ConversionBreadcrumbs<'a>, + pub to: ConversionBreadcrumbs<'a>, +} + +// mutable_transmutes.rs +#[derive(LintDiagnostic)] +#[diag(lint_unsafe_cell_conversions)] +#[note] +pub(crate) struct UnsafeCellConversions<'a> { + #[label] + pub orig_cast: Option, + pub from: ConversionBreadcrumbs<'a>, + pub to: ConversionBreadcrumbs<'a>, +} #[derive(LintDiagnostic)] #[diag(lint_builtin_unstable_features)] diff --git a/compiler/rustc_lint/src/mutable_transmutes.rs b/compiler/rustc_lint/src/mutable_transmutes.rs new file mode 100644 index 0000000000000..f2e836c082900 --- /dev/null +++ b/compiler/rustc_lint/src/mutable_transmutes.rs @@ -0,0 +1,721 @@ +//! This module check that we are not transmuting `&T` to `&mut T` or `&UnsafeCell`, or casting +//! `&T` to `&UnsafeCell` (checking casts from `&T` to `&mut T` is done in reference_casting.rs, +//! because it has some differences). +//! +//! The complexity comes from the fact that we want to lint against this conversion even when the `&T`, the `&mut T` +//! or the `&UnsafeCell` (either the `&` or the `UnsafeCell` part of it) are hidden in fields. +//! +//! The general idea is to first isolate potential candidates, then check if they are really problematic. +//! +//! That is, we first collect all instances of `&mut` references in the target type, then we check if any +//! of them overlap with a `&` reference in the source type. +//! +//! We do a similar thing to `UnsafeCell`. Here it is a little more complicated, since we operate two layers deep: +//! first, we collect all `&` references in the target type. Then, we check if any of them overlaps with a `&` reference +//! in the source type (not a `&mut` reference, since it is perfectly fine to convert `&mut T` to `&UnsafeCell`). +//! If we found such overlap, we collect all instances of `UnsafeCell` under the reference in the target type. +//! If we found any, we try to find if it overlaps with things that are not `UnsafeCell` under the reference +//! in the source type. + +use std::fmt::Write; + +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{BorrowKind, Expr, ExprKind, UnOp}; +use rustc_index::IndexSlice; +use rustc_middle::bug; +use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, List, Mutability, Ty}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::symbol::sym; +use rustc_target::abi::{FieldIdx, FieldsShape, HasDataLayout, Layout, Size}; +use tracing::{debug, debug_span}; + +use crate::lints::{BuiltinMutablesTransmutes, ConversionBreadcrumbs, UnsafeCellConversions}; +use crate::{LateContext, LateLintPass, LintContext}; + +fn struct_fields<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + layout: Layout<'tcx>, + adt_def: AdtDef<'tcx>, + substs: GenericArgsRef<'tcx>, +) -> impl Iterator)> + 'a { + let field_tys = + adt_def.non_enum_variant().fields.iter().map(|field| { + cx.tcx.normalize_erasing_regions(cx.typing_env(), field.ty(cx.tcx, substs)) + }); + // I thought we could panic if the fields shape is not `Arbitrary`, but apparently SIMD vectors are + // structs but also have primitive layout. So... + let field_offsets = match &layout.fields { + FieldsShape::Arbitrary { offsets, .. } => offsets.as_slice(), + _ => IndexSlice::empty(), + }; + std::iter::zip(field_offsets.iter_enumerated(), field_tys) + .map(|((idx, &offset), ty)| (idx, offset, ty)) +} + +fn tuple_fields<'tcx>( + layout: Layout<'tcx>, + field_tys: &'tcx List>, +) -> impl Iterator)> + 'tcx { + let field_offsets = match &layout.fields { + FieldsShape::Arbitrary { offsets, .. } => offsets, + _ => bug!("expected FieldsShape::Arbitrary for tuples"), + }; + std::iter::zip(field_offsets.iter_enumerated(), field_tys) + .map(|((idx, &offset), ty)| (idx, offset, ty)) +} + +#[derive(Debug, Default, Clone)] +struct FieldsBreadcrumbs(Vec); + +impl FieldsBreadcrumbs { + fn push_with(&mut self, field: FieldIdx, f: impl FnOnce(&mut FieldsBreadcrumbs) -> R) -> R { + self.0.push(field); + let result = f(self); + self.0.pop(); + result + } + + fn translate_for_diagnostics<'tcx>( + &self, + cx: &LateContext<'tcx>, + output: &mut String, + mut ty: Ty<'tcx>, + ) { + for ¤t_breadcrumb in &self.0 { + ty = match ty.kind() { + &ty::Adt(adt_def, substs) if adt_def.is_struct() => { + let field = &adt_def.non_enum_variant().fields[current_breadcrumb]; + let field_ty = + cx.tcx.normalize_erasing_regions(cx.typing_env(), field.ty(cx.tcx, substs)); + output.push_str("."); + output.push_str(field.name.as_str()); + + field_ty + } + + &ty::Tuple(tys) => { + let field_ty = tys[current_breadcrumb.as_usize()]; + write!(output, ".{}", current_breadcrumb.as_u32()).unwrap(); + + field_ty + } + + &ty::Array(element_ty, _) => { + output.push_str("[0]"); + + element_ty + } + + _ => bug!("field breadcrumb on an unknown type"), + } + } + } + + // Formats the breadcrumbs as `(*path.to.reference).path.to.unsafecell`; + fn format_unsafe_cell_for_diag<'tcx>( + cx: &LateContext<'tcx>, + reference: &FieldsBreadcrumbs, + top_ty: Ty<'tcx>, + referent: &FieldsBreadcrumbs, + reference_ty: Ty<'tcx>, + ) -> ConversionBreadcrumbs<'tcx> { + let mut after_ty = String::new(); + let before_ty; + if !referent.0.is_empty() { + before_ty = "(*"; + reference.translate_for_diagnostics(cx, &mut after_ty, top_ty); + after_ty.push_str(")"); + referent.translate_for_diagnostics(cx, &mut after_ty, reference_ty); + } else { + before_ty = "*"; + reference.translate_for_diagnostics(cx, &mut after_ty, top_ty); + } + ConversionBreadcrumbs { before_ty, ty: top_ty, after_ty } + } + + // Formats the breadcrumbs as `(*path.to.reference).path.to.unsafecell`; + fn format_for_diag<'tcx>( + &self, + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + ) -> ConversionBreadcrumbs<'tcx> { + let mut after_ty = String::new(); + if !self.0.is_empty() { + self.translate_for_diagnostics(cx, &mut after_ty, ty); + } + ConversionBreadcrumbs { before_ty: "", ty, after_ty } + } +} + +#[derive(PartialEq)] +enum CollectFields { + Yes, + No, +} + +fn collect_fields<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + offset: Size, + callback: &mut impl FnMut(Size, Ty<'tcx>, &FieldsBreadcrumbs) -> CollectFields, + fields_breadcrumbs: &mut FieldsBreadcrumbs, +) { + if callback(offset, ty, &fields_breadcrumbs) == CollectFields::No { + return; + } + + match ty.kind() { + &ty::Adt(adt_def, substs) if adt_def.is_struct() => { + let Ok(layout) = cx.layout_of(ty) else { + return; + }; + debug!(?offset, ?layout, "collect_fields struct"); + for (field_idx, field_offset, field_ty) in + struct_fields(cx, layout.layout, adt_def, substs) + { + fields_breadcrumbs.push_with(field_idx, |fields_breadcrumbs| { + collect_fields( + cx, + field_ty, + offset + field_offset, + callback, + fields_breadcrumbs, + ) + }); + } + } + + &ty::Tuple(tys) => { + let Ok(layout) = cx.layout_of(ty) else { + return; + }; + debug!(?offset, ?layout, "collect_fields tuple"); + for (field_idx, field_offset, field_ty) in tuple_fields(layout.layout, tys) { + fields_breadcrumbs.push_with(field_idx, |fields_breadcrumbs| { + collect_fields( + cx, + field_ty, + offset + field_offset, + callback, + fields_breadcrumbs, + ) + }); + } + } + + &ty::Array(ty, len) + if let Some(len) = len.try_to_target_usize(cx.tcx) + && len != 0 => + { + debug!(?offset, "collect_fields array"); + fields_breadcrumbs.push_with(FieldIdx::from_u32(0), |fields_breadcrumbs| { + collect_fields(cx, ty, offset, callback, fields_breadcrumbs) + }); + } + + _ => {} + } +} + +trait Field { + fn start_offset(&self) -> Size; + fn end_offset(&self) -> Size; +} + +#[derive(Debug)] +enum CheckOverlapping { + Yes, + No, +} + +/// `check_overlap_with` must be sorted by starting offset. +fn check_overlapping<'tcx>( + cx: &LateContext<'tcx>, + check_overlap_with: &[impl Field], + ty: Ty<'tcx>, + offset: Size, + on_overlapping: &mut impl FnMut(Ty<'tcx>, usize, &FieldsBreadcrumbs) -> CheckOverlapping, + fields_breadcrumbs: &mut FieldsBreadcrumbs, +) { + let Ok(layout) = cx.layout_of(ty) else { + return; + }; + debug!(?offset, ?layout, "check_overlapping"); + + // Then, for any entry that we overlap with (there can be many because our size can be bigger than one, + // or because both are only partially overlapping) call the closure. + let mut idx = 0; + while idx < check_overlap_with.len() + && check_overlap_with[idx].start_offset() < offset + layout.size + { + if check_overlap_with[idx].end_offset() <= offset { + idx += 1; + continue; + } + + let span = debug_span!("on_overlapping", ?ty, ?idx, ?fields_breadcrumbs); + let _guard = span.enter(); + let result = on_overlapping(ty, idx, &fields_breadcrumbs); + debug!(?result, "on_overlapping result"); + match result { + CheckOverlapping::Yes => {} + CheckOverlapping::No => return, + }; + idx += 1; + } + + // Finally, descend to every field and check there too. + match ty.kind() { + &ty::Adt(adt_def, substs) if adt_def.is_struct() => { + for (field_idx, field_offset, field_ty) in + struct_fields(cx, layout.layout, adt_def, substs) + { + fields_breadcrumbs.push_with(field_idx, |fields_breadcrumbs| { + check_overlapping( + cx, + check_overlap_with, + field_ty, + offset + field_offset, + on_overlapping, + fields_breadcrumbs, + ) + }); + } + } + + &ty::Tuple(tys) => { + for (field_idx, field_offset, field_ty) in tuple_fields(layout.layout, tys) { + fields_breadcrumbs.push_with(field_idx, |fields_breadcrumbs| { + check_overlapping( + cx, + check_overlap_with, + field_ty, + offset + field_offset, + on_overlapping, + fields_breadcrumbs, + ) + }); + } + } + + &ty::Array(ty, _) if layout.size != Size::ZERO => { + fields_breadcrumbs.push_with(FieldIdx::from_u32(0), |fields_breadcrumbs| { + check_overlapping( + cx, + check_overlap_with, + ty, + offset, + on_overlapping, + fields_breadcrumbs, + ) + }); + } + + _ => {} + } +} + +#[derive(Debug)] +struct Ref<'tcx> { + start_offset: Size, + end_offset: Size, + ty: Ty<'tcx>, + mutability: Mutability, + fields_breadcrumbs: FieldsBreadcrumbs, +} + +impl Field for Ref<'_> { + fn start_offset(&self) -> Size { + self.start_offset + } + + fn end_offset(&self) -> Size { + self.end_offset + } +} + +#[derive(Debug)] +struct UnsafeCellField { + start_offset: Size, + end_offset: Size, + fields_breadcrumbs: FieldsBreadcrumbs, +} + +impl Field for UnsafeCellField { + fn start_offset(&self) -> Size { + self.start_offset + } + + fn end_offset(&self) -> Size { + self.end_offset + } +} + +/// The parameters to `report_error` are: breadcrumbs to src's UnsafeCell, breadcrumbs to dst's UnsafeCell. +fn check_unsafe_cells<'tcx>( + cx: &LateContext<'tcx>, + dst_ref_ty: Ty<'tcx>, + src_ty: Ty<'tcx>, + mut report_error: impl FnMut(&FieldsBreadcrumbs, &FieldsBreadcrumbs), +) { + let mut dst_unsafe_cells = Vec::new(); + collect_fields( + cx, + dst_ref_ty, + Size::ZERO, + &mut |offset, ty, fields_breadcrumbs| match ty.kind() { + &ty::Adt(adt_def, _) if adt_def.is_unsafe_cell() => { + let Ok(layout) = cx.layout_of(ty) else { + return CollectFields::Yes; + }; + if layout.is_zst() { + return CollectFields::No; + } + + let Ok(layout) = cx.layout_of(ty) else { + return CollectFields::Yes; + }; + dst_unsafe_cells.push(UnsafeCellField { + start_offset: offset, + end_offset: offset + layout.size, + fields_breadcrumbs: fields_breadcrumbs.clone(), + }); + CollectFields::No + } + _ => CollectFields::Yes, + }, + &mut FieldsBreadcrumbs::default(), + ); + dst_unsafe_cells.sort_unstable_by_key(|unsafe_cell| unsafe_cell.start_offset); + debug!(?dst_unsafe_cells, "collected UnsafeCells"); + + check_overlapping( + cx, + &dst_unsafe_cells, + src_ty, + Size::ZERO, + &mut |ty, idx, src_pointee_fields_breadcrumbs| { + // First, check that there are actually some bytes there, not just padding or ZST. + match ty.kind() { + // Transmuting `&UnsafeCell` to `&UnsafeCell` is fine, don't check inside. + ty::Adt(adt_def, _) if adt_def.is_unsafe_cell() => { + return CheckOverlapping::No; + } + + ty::Bool + | ty::Char + | ty::Int(..) + | ty::Uint(..) + | ty::Float(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnPtr(..) => {} + // Enums have their discriminant. + ty::Adt(adt_def, _) if adt_def.is_enum() => {} + _ => return CheckOverlapping::Yes, + } + + report_error(src_pointee_fields_breadcrumbs, &dst_unsafe_cells[idx].fields_breadcrumbs); + + CheckOverlapping::No + }, + &mut FieldsBreadcrumbs::default(), + ); +} + +/// Checks for transmutes via `std::mem::transmute` and lints. +fn lint_transmutes(cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some((src, dst)) = get_transmute_from_to(cx, expr) else { return }; + let span = debug_span!("lint_transmutes"); + let _guard = span.enter(); + debug!(?src, ?dst); + + let mut dst_refs = Vec::new(); + // First, collect references in the target type, so we can see for mutable references if they are transmuted + // from a shared reference, and for shared references if `UnsafeCell` inside them is transmuted from non-`UnsafeCell`. + // Instead of doing this in two passes, one for shared references (`UnsafeCell`) and another for mutable, + // we do it in one pass and keep track of what kind of reference it is. + collect_fields( + cx, + dst, + Size::ZERO, + &mut |offset, ty, fields_breadcrumbs| match ty.kind() { + &ty::Ref(_, ty, mutability) => { + dst_refs.push(Ref { + start_offset: offset, + end_offset: offset + cx.data_layout().pointer_size, + ty, + mutability, + fields_breadcrumbs: fields_breadcrumbs.clone(), + }); + CollectFields::No + } + _ => CollectFields::Yes, + }, + &mut FieldsBreadcrumbs::default(), + ); + dst_refs.sort_unstable_by_key(|ref_| ref_.start_offset); + debug!(?dst_refs, "collected refs"); + check_overlapping( + cx, + &dst_refs, + src, + Size::ZERO, + &mut |ty, idx, src_fields_breadcrumbs| { + let dst_ref = &dst_refs[idx]; + match dst_ref.mutability { + // For mutable references in the target type, we need to see if they were converted from shared references. + Mutability::Mut => match ty.kind() { + ty::Ref(_, _, Mutability::Not) => { + let from = src_fields_breadcrumbs.format_for_diag(cx, src); + let to = dst_ref.fields_breadcrumbs.format_for_diag(cx, dst); + cx.emit_span_lint( + MUTABLE_TRANSMUTES, + expr.span, + BuiltinMutablesTransmutes { from, to }, + ); + CheckOverlapping::No + } + _ => CheckOverlapping::Yes, + }, + // For shared references, we need to see if they were transmuted from shared (not mutable!) references, + // and if there is an incorrectly transmuted `UnsafeCell` inside. + Mutability::Not => { + let &ty::Ref(_, src_ty, Mutability::Not) = ty.kind() else { + return CheckOverlapping::Yes; + }; + debug!(?src_ty, dst_ty = ?dst_ref.ty, "found shared ref"); + + check_unsafe_cells( + cx, + dst_ref.ty, + src_ty, + |src_pointee_fields_breadcrumbs, dst_pointee_fields_breadcrumbs| { + let from = FieldsBreadcrumbs::format_unsafe_cell_for_diag( + cx, + src_fields_breadcrumbs, + src, + src_pointee_fields_breadcrumbs, + src_ty, + ); + let to = FieldsBreadcrumbs::format_unsafe_cell_for_diag( + cx, + &dst_ref.fields_breadcrumbs, + dst, + dst_pointee_fields_breadcrumbs, + dst_ref.ty, + ); + + cx.emit_span_lint( + UNSAFE_CELL_CONVERSIONS, + expr.span, + UnsafeCellConversions { from, to, orig_cast: None }, + ); + }, + ); + CheckOverlapping::No + } + } + }, + &mut FieldsBreadcrumbs::default(), + ); +} + +fn get_transmute_from_to<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<(Ty<'tcx>, Ty<'tcx>)> { + let def = if let ExprKind::Path(ref qpath) = expr.kind { + cx.qpath_res(qpath, expr.hir_id) + } else { + return None; + }; + if let Res::Def(DefKind::Fn, did) = def { + if !def_id_is_transmute(cx, did) { + return None; + } + let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx); + let from = sig.inputs().skip_binder()[0]; + let to = sig.output().skip_binder(); + return Some((from, to)); + } + None +} + +fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { + cx.tcx.is_intrinsic(def_id, sym::transmute) +} + +/// Checks for casts via `&*(reference as *const _ as *const _)` and lints. +fn lint_reference_casting<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + let Some(e) = borrow_or_assign(expr) else { + return; + }; + + let init = cx.expr_or_init(e); + + let Some((dst, src)) = is_type_cast(cx, init) else { + return; + }; + let orig_cast = if init.span != e.span { Some(init.span) } else { None }; + + let span = debug_span!("lint_reference_casting"); + let _guard = span.enter(); + + check_unsafe_cells(cx, dst, src, |src_fields_breadcrumbs, dst_fields_breadcrumbs| { + let from = src_fields_breadcrumbs.format_for_diag(cx, src); + let to = dst_fields_breadcrumbs.format_for_diag(cx, dst); + cx.emit_span_lint(UNSAFE_CELL_CONVERSIONS, expr.span, UnsafeCellConversions { + from, + to, + orig_cast, + }); + }); +} + +fn borrow_or_assign<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // &(mut) + let inner = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = expr.kind { + expr + // = ... + } else if let ExprKind::Assign(expr, _, _) = expr.kind { + expr + // += ... + } else if let ExprKind::AssignOp(_, expr, _) = expr.kind { + expr + } else { + return None; + }; + + // * + let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else { + return None; + }; + Some(e) +} + +fn is_type_cast<'tcx>( + cx: &LateContext<'tcx>, + orig_expr: &'tcx Expr<'tcx>, +) -> Option<(Ty<'tcx>, Ty<'tcx>)> { + let mut e = orig_expr; + + let end_ty = cx.typeck_results().node_type(orig_expr.hir_id); + + let &ty::RawPtr(dst, _) = end_ty.kind() else { + return None; + }; + + loop { + e = e.peel_blocks(); + // as ... + e = if let ExprKind::Cast(expr, _) = e.kind { + expr + // .cast() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast), + ) + { + expr + // ptr::from_ref() or mem::transmute<_, _>() + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_from_ref | sym::transmute) + ) + { + arg + } else { + break; + }; + } + + let &ty::Ref(_, src, Mutability::Not) = cx.typeck_results().node_type(e.hir_id).kind() else { + return None; + }; + + Some((dst, src)) +} + +declare_lint! { + /// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut + /// T` because it is [undefined behavior]. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// unsafe { + /// let y = std::mem::transmute::<&i32, &mut i32>(&5); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Certain assumptions are made about aliasing of data, and this transmute + /// violates those assumptions. Consider using [`UnsafeCell`] instead. + /// + /// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html + pub MUTABLE_TRANSMUTES, + Deny, + "transmuting &T to &mut T is undefined behavior, even if the reference is unused" +} + +declare_lint! { + /// The `unsafe_cell_conversions` lint catches transmuting or casting from `&T` to [`&UnsafeCell`] + /// because it dangerous and might be [undefined behavior]. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// use std::cell::Cell; + /// + /// unsafe { + /// let x = 5_i32; + /// let y = std::mem::transmute::<&i32, &Cell>(&x); + /// y.set(6); + /// + /// let z = &*(&x as *const i32 as *const Cell); + /// z.set(7); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Conversion from `&T` to `&UnsafeCell` might be immediate undefined behavior, depending on + /// unspecified details of the aliasing model. + /// + /// Even if it is not, writing to it will be undefined behavior if there was no `UnsafeCell` in + /// the original `T`, and even if there was, it might be undefined behavior (again, depending + /// on unspecified details of the aliasing model). + /// + /// It is also highly dangerous and error-prone, and unlikely to be useful. + /// + /// [`&UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html + pub UNSAFE_CELL_CONVERSIONS, + Deny, + "transmuting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior" +} + +declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES, UNSAFE_CELL_CONVERSIONS]); + +impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + lint_reference_casting(cx, expr); + lint_transmutes(cx, expr); + } +} diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs index 88c73d14ef72b..37133d72b102f 100644 --- a/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_cmpxchg.rs @@ -1,5 +1,6 @@ // Should not rely on the aliasing model for its failure. //@compile-flags: -Zmiri-disable-stacked-borrows +#![allow(unsafe_cell_transmutes)] use std::sync::atomic::{AtomicI32, Ordering}; diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_acquire.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_acquire.rs index af0dc2d3fd64a..6933e492637c7 100644 --- a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_acquire.rs +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_acquire.rs @@ -1,5 +1,6 @@ // Should not rely on the aliasing model for its failure. //@compile-flags: -Zmiri-disable-stacked-borrows +#![allow(unsafe_cell_transmutes)] use std::sync::atomic::{AtomicI32, Ordering}; diff --git a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs index 42c3a9619d464..cce2b5e1f72c4 100644 --- a/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs +++ b/src/tools/miri/tests/fail/concurrency/read_only_atomic_load_large.rs @@ -2,6 +2,7 @@ //@compile-flags: -Zmiri-disable-stacked-borrows // Needs atomic accesses larger than the pointer size //@ignore-bitwidth: 64 +#![allow(unsafe_cell_transmutes)] use std::sync::atomic::{AtomicI64, Ordering}; diff --git a/src/tools/miri/tests/pass/atomic-readonly-load.rs b/src/tools/miri/tests/pass/atomic-readonly-load.rs index 8f8086b3538c6..a2e3c3a44fbc5 100644 --- a/src/tools/miri/tests/pass/atomic-readonly-load.rs +++ b/src/tools/miri/tests/pass/atomic-readonly-load.rs @@ -1,5 +1,6 @@ // Stacked Borrows doesn't like this. //@compile-flags: -Zmiri-tree-borrows +#![allow(unsafe_cell_transmutes)] use std::sync::atomic::*; diff --git a/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs b/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs index 1df0636e1e5d2..dab36024714f9 100644 --- a/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs +++ b/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs @@ -1,4 +1,5 @@ //@compile-flags: -Zmiri-tree-borrows +#![allow(unsafe_cell_transmutes)] //! Testing `mem::transmute` between types with and without interior mutability. //! All transmutations should work, as long as we don't do any actual accesses diff --git a/tests/ui/lint/force-warn/allowed-cli-deny-by-default-lint.stderr b/tests/ui/lint/force-warn/allowed-cli-deny-by-default-lint.stderr index 6a1fc76e18a18..768e57a968ee2 100644 --- a/tests/ui/lint/force-warn/allowed-cli-deny-by-default-lint.stderr +++ b/tests/ui/lint/force-warn/allowed-cli-deny-by-default-lint.stderr @@ -4,6 +4,7 @@ warning: transmuting &T to &mut T is undefined behavior, even if the reference i LL | let y = std::mem::transmute::<&i32, &mut i32>(&5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: transmute from `&i32` to `&mut i32` = note: requested on the command line with `--force-warn mutable-transmutes` warning: 1 warning emitted diff --git a/tests/ui/lint/force-warn/allowed-deny-by-default-lint.stderr b/tests/ui/lint/force-warn/allowed-deny-by-default-lint.stderr index 9ef53d47eb931..e5b10122218c5 100644 --- a/tests/ui/lint/force-warn/allowed-deny-by-default-lint.stderr +++ b/tests/ui/lint/force-warn/allowed-deny-by-default-lint.stderr @@ -4,6 +4,7 @@ warning: transmuting &T to &mut T is undefined behavior, even if the reference i LL | let y = std::mem::transmute::<&i32, &mut i32>(&5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: transmute from `&i32` to `&mut i32` = note: requested on the command line with `--force-warn mutable-transmutes` warning: 1 warning emitted diff --git a/tests/ui/lint/force-warn/deny-by-default-lint.stderr b/tests/ui/lint/force-warn/deny-by-default-lint.stderr index c644d0fe741ad..01508109939b1 100644 --- a/tests/ui/lint/force-warn/deny-by-default-lint.stderr +++ b/tests/ui/lint/force-warn/deny-by-default-lint.stderr @@ -4,6 +4,7 @@ warning: transmuting &T to &mut T is undefined behavior, even if the reference i LL | let y = std::mem::transmute::<&i32, &mut i32>(&5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: transmute from `&i32` to `&mut i32` = note: requested on the command line with `--force-warn mutable-transmutes` warning: 1 warning emitted diff --git a/tests/ui/lint/mutable_transmutes/allow.rs b/tests/ui/lint/mutable_transmutes/allow.rs new file mode 100644 index 0000000000000..b3603ea177cf1 --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/allow.rs @@ -0,0 +1,35 @@ +//@ check-pass + +use std::cell::UnsafeCell; +use std::mem::transmute; + +fn main() { + // Allow mutable reference to mutable reference. + let _a: &mut u8 = unsafe { transmute(&mut 0u8) }; + + // Allow mutable reference to `UnsafeCell`. + let _a: &mut UnsafeCell = unsafe { transmute(&mut 0u8) }; + let _a: &UnsafeCell = unsafe { transmute(&mut 0u8) }; + + // Allow `UnsafeCell` to `UnsafeCell`. + { + #[repr(C)] + struct A { + a: u32, + b: UnsafeCell, + } + + #[repr(C)] + struct B { + a: u32, + b: UnsafeCell, + } + + #[repr(transparent)] + struct AWrapper(A); + + let _a: &UnsafeCell = unsafe { transmute(&UnsafeCell::new(0u8)) }; + let _a: &B = unsafe { transmute(&A { a: 0, b: UnsafeCell::new(0) }) }; + let _a: &AWrapper = unsafe { transmute(&A { a: 0, b: UnsafeCell::new(0) }) }; + } +} diff --git a/tests/ui/lint/mutable_transmutes/lint.rs b/tests/ui/lint/mutable_transmutes/lint.rs new file mode 100644 index 0000000000000..4b3b32e17d776 --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/lint.rs @@ -0,0 +1,163 @@ +use std::cell::UnsafeCell; +use std::mem::transmute; + +fn main() { + // Array. + let _a: [&mut u8; 2] = unsafe { transmute([&1u8; 2]) }; + //~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + + // Assert diagnostics show field names. + { + #[repr(transparent)] + struct A { + a: B, + } + #[repr(transparent)] + struct B { + b: C, + } + #[repr(transparent)] + struct C { + c: &'static D, + } + #[repr(transparent)] + struct D { + d: UnsafeCell, + } + + #[repr(transparent)] + struct E { + e: F, + } + #[repr(transparent)] + struct F { + f: &'static G, + } + #[repr(transparent)] + struct G { + g: H, + } + #[repr(transparent)] + struct H { + h: u8, + } + + let _: A = unsafe { transmute(&1u8) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: A = unsafe { transmute(E { e: F { f: &G { g: H { h: 0 } } } }) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: &'static UnsafeCell = + unsafe { transmute(E { e: F { f: &G { g: H { h: 0 } } } }) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + } + + // Immutable to mutable reference. + let _a: &mut u8 = unsafe { transmute(&1u8) }; + //~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + + // Immutable reference to `UnsafeCell`. + let _a: &UnsafeCell = unsafe { transmute(&1u8) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + + // Check in nested field. + { + #[repr(C)] + struct Foo { + a: u32, + b: Bar, + } + #[repr(C)] + struct Bar(Baz); + #[repr(C)] + struct Baz(T); + + #[repr(C)] + struct Other(&'static u8, &'static u8); + + let _: Foo<&'static mut u8> = unsafe { transmute(Other(&1u8, &1u8)) }; + //~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + let _: Foo<&'static UnsafeCell> = unsafe { transmute(Other(&1u8, &1u8)) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + } + + // Check that transmuting only part of the type to `UnsafeCell` triggers the lint. + { + #[repr(C)] + struct A(u32); + + #[repr(C)] + struct B(u16, UnsafeCell); + + #[repr(C)] + struct C(u8, UnsafeCell); + + #[repr(C)] + struct D(UnsafeCell); + + #[repr(C, packed)] + struct E(u8, UnsafeCell, u8); + + #[repr(C, packed)] + struct F(UnsafeCell, u16); + + let _: &B = unsafe { transmute(&A(0)) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: &D = unsafe { transmute(&C(0, UnsafeCell::new(0))) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: &F = unsafe { transmute(&E(0, UnsafeCell::new(0), 0)) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + } + + // Check that we report all error, since once cast may be intentional but another not, + // especially considering that `&T` to `&UnsafeCell` may be valid but to `&mut T` never is. + { + #[repr(C)] + struct Foo(&'static u8, &'static u8); + #[repr(C)] + struct Bar(&'static UnsafeCell, &'static mut u8); + + let _a: Bar = unsafe { transmute(Foo(&0, &0)) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + //~| ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + } + + // `UnsafeCell` reference casting. + { + #[repr(C)] + struct A { + a: u64, + b: u32, + c: u32, + } + + #[repr(C)] + struct B { + a: u64, + b: UnsafeCell, + c: u32, + } + + let a = A { a: 0, b: 0, c: 0 }; + let _b = unsafe { &*(&a as *const A as *const B) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + } + + // Unsized. + { + #[repr(C)] + struct A { + a: u32, + b: T, + } + + #[repr(C)] + struct B { + a: UnsafeCell, + b: [u32], + } + + let a = &A { a: 0, b: [0_u32, 0] } as &A<[u32]>; + let _b = unsafe { &*(a as *const A<[u32]> as *const B) }; + //~^ ERROR converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + } +} diff --git a/tests/ui/lint/mutable_transmutes/lint.stderr b/tests/ui/lint/mutable_transmutes/lint.stderr new file mode 100644 index 0000000000000..b1c6c46c2e2cb --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/lint.stderr @@ -0,0 +1,124 @@ +error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + --> $DIR/lint.rs:6:37 + | +LL | let _a: [&mut u8; 2] = unsafe { transmute([&1u8; 2]) }; + | ^^^^^^^^^ + | + = note: transmute from `[&u8; 2][0]` to `[&mut u8; 2][0]` + = note: `#[deny(mutable_transmutes)]` on by default + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:45:29 + | +LL | let _: A = unsafe { transmute(&1u8) }; + | ^^^^^^^^^ + | + = note: conversion from `*&u8` to `(*main::A.a.b.c).d` + = note: `#[deny(unsafe_cell_conversions)]` on by default + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:47:29 + | +LL | let _: A = unsafe { transmute(E { e: F { f: &G { g: H { h: 0 } } } }) }; + | ^^^^^^^^^ + | + = note: conversion from `(*main::E.e.f).g.h` to `(*main::A.a.b.c).d` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:50:22 + | +LL | unsafe { transmute(E { e: F { f: &G { g: H { h: 0 } } } }) }; + | ^^^^^^^^^ + | + = note: conversion from `(*main::E.e.f).g.h` to `*&UnsafeCell` + +error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + --> $DIR/lint.rs:55:32 + | +LL | let _a: &mut u8 = unsafe { transmute(&1u8) }; + | ^^^^^^^^^ + | + = note: transmute from `&u8` to `&mut u8` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:59:40 + | +LL | let _a: &UnsafeCell = unsafe { transmute(&1u8) }; + | ^^^^^^^^^ + | + = note: conversion from `*&u8` to `*&UnsafeCell` + +error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + --> $DIR/lint.rs:77:48 + | +LL | let _: Foo<&'static mut u8> = unsafe { transmute(Other(&1u8, &1u8)) }; + | ^^^^^^^^^ + | + = note: transmute from `main::Other.1` to `main::Foo<&mut u8>.b.0.0` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:79:56 + | +LL | let _: Foo<&'static UnsafeCell> = unsafe { transmute(Other(&1u8, &1u8)) }; + | ^^^^^^^^^ + | + = note: conversion from `*main::Other.1` to `*main::Foo<&UnsafeCell>.b.0.0` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:103:30 + | +LL | let _: &B = unsafe { transmute(&A(0)) }; + | ^^^^^^^^^ + | + = note: conversion from `(*&main::A).0` to `(*&main::B).1` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:105:30 + | +LL | let _: &D = unsafe { transmute(&C(0, UnsafeCell::new(0))) }; + | ^^^^^^^^^ + | + = note: conversion from `(*&main::C).0` to `(*&main::D).0` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:107:30 + | +LL | let _: &F = unsafe { transmute(&E(0, UnsafeCell::new(0), 0)) }; + | ^^^^^^^^^ + | + = note: conversion from `(*&main::E).0` to `(*&main::F).0` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:119:32 + | +LL | let _a: Bar = unsafe { transmute(Foo(&0, &0)) }; + | ^^^^^^^^^ + | + = note: conversion from `*main::Foo.0` to `*main::Bar.0` + +error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell + --> $DIR/lint.rs:119:32 + | +LL | let _a: Bar = unsafe { transmute(Foo(&0, &0)) }; + | ^^^^^^^^^ + | + = note: transmute from `main::Foo.1` to `main::Bar.1` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:141:27 + | +LL | let _b = unsafe { &*(&a as *const A as *const B) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: conversion from `main::A.b` to `main::B.b` + +error: converting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + --> $DIR/lint.rs:160:27 + | +LL | let _b = unsafe { &*(a as *const A<[u32]> as *const B) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: conversion from `main::A<[u32]>.a` to `main::B.a` + +error: aborting due to 15 previous errors + diff --git a/tests/ui/lint/mutable_transmutes/no_ice_when_allowed.rs b/tests/ui/lint/mutable_transmutes/no_ice_when_allowed.rs new file mode 100644 index 0000000000000..9c4a9864943a1 --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/no_ice_when_allowed.rs @@ -0,0 +1,9 @@ +//@ check-pass + +#![allow(unsafe_cell_conversions)] + +use std::cell::UnsafeCell; + +fn main() { + let _: &UnsafeCell = unsafe { &*(&0u8 as *const u8 as *const UnsafeCell) }; +} diff --git a/tests/ui/lint/mutable_transmutes/non_c_layout.rs b/tests/ui/lint/mutable_transmutes/non_c_layout.rs new file mode 100644 index 0000000000000..8f83e010b9246 --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/non_c_layout.rs @@ -0,0 +1,23 @@ +//@ check-pass +//@ compile-flags: -Zrandomize-layout -Zlayout-seed=2464363 + +use std::cell::UnsafeCell; + +#[derive(Default)] +struct A { + a: u32, + b: u32, + c: u32, + d: u32, + e: UnsafeCell, + f: UnsafeCell, + g: UnsafeCell, + h: UnsafeCell, +} + +#[repr(transparent)] +struct B(A); + +fn main() { + let _b: &B = unsafe { std::mem::transmute(&A::default()) }; +} diff --git a/tests/ui/transmute/transmute-imut-to-mut.rs b/tests/ui/transmute/transmute-imut-to-mut.rs deleted file mode 100644 index 9f3f76c1ef3e0..0000000000000 --- a/tests/ui/transmute/transmute-imut-to-mut.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Tests that transmuting from &T to &mut T is Undefined Behavior. - -use std::mem::transmute; - -fn main() { - let _a: &mut u8 = unsafe { transmute(&1u8) }; - //~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell -} diff --git a/tests/ui/transmute/transmute-imut-to-mut.stderr b/tests/ui/transmute/transmute-imut-to-mut.stderr deleted file mode 100644 index d37050fa58af6..0000000000000 --- a/tests/ui/transmute/transmute-imut-to-mut.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell - --> $DIR/transmute-imut-to-mut.rs:6:32 - | -LL | let _a: &mut u8 = unsafe { transmute(&1u8) }; - | ^^^^^^^^^ - | - = note: `#[deny(mutable_transmutes)]` on by default - -error: aborting due to 1 previous error -