From 2ea057842f4b98e5585d172a835bc55a11266216 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Jul 2024 15:19:00 +0300 Subject: [PATCH] Lint against &T to &mut T and &T to &UnsafeCell transmutes This adds the lint against `&T`->`&UnsafeCell` transmutes, and also check in struct fields, and reference casts (`&*(&a as *const u8 as *const UnsafeCell)`). The code is quite complex; I've tried my best to simplify and comment it. This is missing one parts: array transmutes. When transmuting an array, this only consider the first element. The reason for that is that the code is already quite complex, and I didn't want to complicate it more. This catches the most common pattern of transmuting an array into an array of the same length with type of the same size; more complex cases are likely not properly handled. We could take a bigger sample, for example the first and last elements to increase the chance that the lint will catch mistakes, but then the runtime complexity becomes exponential with the nesting of the arrays (`[[[[[T; 2]; 2]; 2]; 2]; 2]` has complexity of O(2**5), for instance). --- compiler/rustc_lint/messages.ftl | 6 + compiler/rustc_lint/src/builtin.rs | 78 +- compiler/rustc_lint/src/lib.rs | 2 + compiler/rustc_lint/src/lints.rs | 35 +- compiler/rustc_lint/src/mutable_transmutes.rs | 718 ++++++++++++++++++ .../concurrency/read_only_atomic_cmpxchg.rs | 1 + .../read_only_atomic_load_acquire.rs | 1 + .../read_only_atomic_load_large.rs | 1 + .../miri/tests/pass/atomic-readonly-load.rs | 1 + .../pass/tree_borrows/transmute-unsafecell.rs | 1 + .../allowed-cli-deny-by-default-lint.stderr | 1 + .../allowed-deny-by-default-lint.stderr | 1 + .../force-warn/deny-by-default-lint.stderr | 1 + tests/ui/lint/mutable_transmutes/allow.rs | 35 + tests/ui/lint/mutable_transmutes/lint.rs | 163 ++++ tests/ui/lint/mutable_transmutes/lint.stderr | 124 +++ .../mutable_transmutes/no_ice_when_allowed.rs | 9 + .../lint/mutable_transmutes/non_c_layout.rs | 23 + tests/ui/transmute/transmute-imut-to-mut.rs | 8 - .../ui/transmute/transmute-imut-to-mut.stderr | 10 - 20 files changed, 1126 insertions(+), 93 deletions(-) create mode 100644 compiler/rustc_lint/src/mutable_transmutes.rs create mode 100644 tests/ui/lint/mutable_transmutes/allow.rs create mode 100644 tests/ui/lint/mutable_transmutes/lint.rs create mode 100644 tests/ui/lint/mutable_transmutes/lint.stderr create mode 100644 tests/ui/lint/mutable_transmutes/no_ice_when_allowed.rs create mode 100644 tests/ui/lint/mutable_transmutes/non_c_layout.rs delete mode 100644 tests/ui/transmute/transmute-imut-to-mut.rs delete mode 100644 tests/ui/transmute/transmute-imut-to-mut.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 9df0c50868cb1..9ea028a4c829e 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_transmutes = + transmuting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + .label = transmuting happend here + .note = transmute 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..a436252577752 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,39 @@ pub(crate) struct BuiltinConstNoMangle { pub suggestion: Span, } +// This would be more convenient as `from: String` and `to: String`, but then we'll ICE for formatting a `Ty` +// when the lint is `#[allow]`ed. +pub(crate) struct TransmuteBreadcrumbs<'a> { + pub before_ty: &'static str, + pub ty: Ty<'a>, + pub after_ty: String, +} + +impl IntoDiagArg for TransmuteBreadcrumbs<'_> { + 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: TransmuteBreadcrumbs<'a>, + pub to: TransmuteBreadcrumbs<'a>, +} + +// mutable_transmutes.rs +#[derive(LintDiagnostic)] +#[diag(lint_unsafe_cell_transmutes)] +#[note] +pub(crate) struct UnsafeCellTransmutes<'a> { + #[label] + pub orig_cast: Option, + pub from: TransmuteBreadcrumbs<'a>, + pub to: TransmuteBreadcrumbs<'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..cef166595d5b6 --- /dev/null +++ b/compiler/rustc_lint/src/mutable_transmutes.rs @@ -0,0 +1,718 @@ +//! This module check that we are not transmuting `&T` to `&mut T` or `&UnsafeCell`. +//! +//! The complexity comes from the fact that we want to lint against this cast 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 transmute `&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, TransmuteBreadcrumbs, UnsafeCellTransmutes}; +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 this could panic, 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>, + ) -> TransmuteBreadcrumbs<'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); + } + TransmuteBreadcrumbs { 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>, + ) -> TransmuteBreadcrumbs<'tcx> { + let mut after_ty = String::new(); + if !self.0.is_empty() { + self.translate_for_diagnostics(cx, &mut after_ty, ty); + } + TransmuteBreadcrumbs { 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 as 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_TRANSMUTES, + expr.span, + UnsafeCellTransmutes { 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 transmutes 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_TRANSMUTES, expr.span, UnsafeCellTransmutes { + 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_transmutes` 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_TRANSMUTES, + Deny, + "transmuting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior" +} + +declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES, UNSAFE_CELL_TRANSMUTES]); + +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..0ff187eadbece --- /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 transmuting &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 transmuting &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 transmuting &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 transmuting &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 transmuting &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 transmuting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: &D = unsafe { transmute(&C(0, UnsafeCell::new(0))) }; + //~^ ERROR transmuting &T to &UnsafeCell is error-prone, rarely intentional and may cause undefined behavior + let _: &F = unsafe { transmute(&E(0, UnsafeCell::new(0), 0)) }; + //~^ ERROR transmuting &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 transmuting &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 transmuting &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 transmuting &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..995081925a93b --- /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: transmuting &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: transmute from `*&u8` to `(*main::A.a.b.c).d` + = note: `#[deny(unsafe_cell_transmutes)]` on by default + +error: transmuting &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: transmute from `(*main::E.e.f).g.h` to `(*main::A.a.b.c).d` + +error: transmuting &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: transmute 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: transmuting &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: transmute 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: transmuting &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: transmute from `*main::Other.1` to `*main::Foo<&UnsafeCell>.b.0.0` + +error: transmuting &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: transmute from `(*&main::A).0` to `(*&main::B).1` + +error: transmuting &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: transmute from `(*&main::C).0` to `(*&main::D).0` + +error: transmuting &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: transmute from `(*&main::E).0` to `(*&main::F).0` + +error: transmuting &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: transmute 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: transmuting &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: transmute from `main::A.b` to `main::B.b` + +error: transmuting &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: transmute 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..739c7d2dfc120 --- /dev/null +++ b/tests/ui/lint/mutable_transmutes/no_ice_when_allowed.rs @@ -0,0 +1,9 @@ +//@ check-pass + +#![allow(unsafe_cell_transmutes)] + +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 -