Skip to content

mem::transmute warns if transmuting to a type with interior mutability #133653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ lint_builtin_special_module_name_used_lib = found module declaration for lib.rs
lint_builtin_special_module_name_used_main = found module declaration for main.rs
.note = a binary crate cannot be used as library

lint_builtin_transmute_to_interior_mutability =
transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
.help = `{$output_ty}` has interior mutability

lint_builtin_trivial_bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters

lint_builtin_type_alias_bounds_enable_feat_help = add `#![feature(lazy_type_alias)]` to the crate attributes to enable the desired semantics
Expand Down
90 changes: 59 additions & 31 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ use crate::lints::{
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
BuiltinWhileTrue, InvalidAsmLabel,
BuiltinTransmuteInteriorMutability, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub,
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
};
use crate::nonstandard_style::{MethodLateContext, method_context};
use crate::{
Expand Down Expand Up @@ -1103,41 +1103,68 @@ declare_lint! {
"transmuting &T to &mut T is undefined behavior, even if the reference is unused"
}

declare_lint! {
/// The `transmute_to_interior_mutability` lint catches transmuting from `&T` or `*const T` to another type
/// with interior mutability because changing its value is [undefined behavior].
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// ### Example
///
/// ```rust,compile_fail
/// unsafe {
/// let x = std::mem::transmute::<&i32, UnsafeCell<i32>>(&5);
/// let y = std::mem::transmute::<&i32, AtomicI32>(&5);
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// It's probably a mistake to transmute a type without interior mutability to a type with it.
TRANSMUTE_TO_INTERIOR_MUTABILITY,
Warn,
"transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content"
}

declare_lint_pass!(TransmuteToInteriorMutability => [TRANSMUTE_TO_INTERIOR_MUTABILITY]);
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);
}
// Is our expr mem::transmute?
let hir::ExprKind::Path(ref qpath) = expr.kind else { return };
let def: Res = cx.qpath_res(qpath, expr.hir_id);
let Res::Def(DefKind::Fn, def_id) = def else { return };
if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
return;
}
let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
let input = sig.inputs().skip_binder()[0];
let output = sig.output().skip_binder();

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;
// For both checks we only care if the input is an immutable ref
let &ty::Ref(_, input_deref, Mutability::Not) = input.kind() else { return };
match output.kind() {
// If the output is a mutable reference that's bad
&ty::Ref(_, _, Mutability::Mut) => {
cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
}
&ty::Ref(_, output_deref, Mutability::Not) => {
// If the input type doesn't have interior mutability but the output type does:
if input_deref.is_freeze(cx.tcx, cx.typing_env())
&& !output_deref.is_freeze(cx.tcx, cx.typing_env())
{
cx.emit_span_lint(
TRANSMUTE_TO_INTERIOR_MUTABILITY,
expr.span,
BuiltinTransmuteInteriorMutability { output_ty: output_deref },
);
}
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)
// The user is transmuting a reference into something else, warn...?
_ => (),
}
}
}
Expand Down Expand Up @@ -1596,6 +1623,7 @@ declare_lint_pass!(
NO_MANGLE_CONST_ITEMS,
NO_MANGLE_GENERIC_ITEMS,
MUTABLE_TRANSMUTES,
TRANSMUTE_TO_INTERIOR_MUTABILITY,
UNSTABLE_FEATURES,
UNREACHABLE_PUB,
TYPE_ALIAS_BOUNDS,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ pub(crate) struct BuiltinConstNoMangle {
#[diag(lint_builtin_mutable_transmutes)]
pub(crate) struct BuiltinMutablesTransmutes;

#[derive(LintDiagnostic)]
#[diag(lint_builtin_transmute_to_interior_mutability)]
#[help]
pub(crate) struct BuiltinTransmuteInteriorMutability<'tcx> {
pub output_ty: Ty<'tcx>,
}

#[derive(LintDiagnostic)]
#[diag(lint_builtin_unstable_features)]
pub(crate) struct BuiltinUnstableFeatures;
Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_lint/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ macro_rules! late_lint_methods {
//
// FIXME: eliminate the duplication with `Visitor`. But this also
// contains a few lint-specific methods with no equivalent in `Visitor`.

macro_rules! declare_late_lint_pass {
([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
pub trait LateLintPass<'tcx>: LintPass {
Expand Down
1 change: 1 addition & 0 deletions tests/ui/consts/const-eval/issue-55541.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Test that we can handle newtypes wrapping extern types

#![feature(extern_types)]
#![allow(transmute_to_interior_mutability)]

use std::marker::PhantomData;

Expand Down
27 changes: 27 additions & 0 deletions tests/ui/consts/const-eval/issue-55541.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
warning: unknown lint: `transmute_to_interior_mutability`
--> $DIR/issue-55541.rs:6:10
|
LL | #![allow(transmute_to_interior_mutability)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unknown_lints)]` on by default

warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
--> $DIR/issue-55541.rs:19:3
|
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
| ^^^^^^^^^^^^^^^^^^^
|
= help: `Wrapper` has interior mutability
= note: `#[warn(transmute_to_interior_mutability)]` on by default

warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
--> $DIR/issue-55541.rs:25:3
|
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
| ^^^^^^^^^^^^^^^^^^^
|
= help: `Wrapper2` has interior mutability

warning: 3 warnings emitted

16 changes: 16 additions & 0 deletions tests/ui/transmute/interior-mutability-issue-111229.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//@ run-pass
// https://github.com/rust-lang/rust/issues/111229

pub struct Foo(std::cell::UnsafeCell<usize>);
pub struct Bar([u8; 0]);

pub fn foo(f: &Bar) {
unsafe {
let f = std::mem::transmute::<&Bar, &Foo>(f);
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
//~| HELP `Foo` has interior mutability
*(f.0.get()) += 1;
}
}

fn main() {}
11 changes: 11 additions & 0 deletions tests/ui/transmute/interior-mutability-issue-111229.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
--> $DIR/interior-mutability-issue-111229.rs:9:17
|
LL | let f = std::mem::transmute::<&Bar, &Foo>(f);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `Foo` has interior mutability
= note: `#[warn(transmute_to_interior_mutability)]` on by default

warning: 1 warning emitted

19 changes: 19 additions & 0 deletions tests/ui/transmute/transmute-interior-mutability.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![allow(unused)]
use std::{cell::UnsafeCell, mem, sync::atomic::AtomicI32};

fn main() {
unsafe {
mem::transmute::<&i32, &UnsafeCell<i32>>(&42);
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
//~| HELP `UnsafeCell<i32>` has interior mutability
// It's an error to transmute to a type containing unsafe cell
mem::transmute::<&i32, &AtomicI32>(&42);
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
//~| HELP `AtomicI32` has interior mutability
// mutable_transmutes triggers before

// This one is here because & -> &mut is worse, to assert that this one triggers.
mem::transmute::<&i32, &mut UnsafeCell<i32>>(&42);
//~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
};
}
27 changes: 27 additions & 0 deletions tests/ui/transmute/transmute-interior-mutability.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
--> $DIR/transmute-interior-mutability.rs:6:9
|
LL | mem::transmute::<&i32, &UnsafeCell<i32>>(&42);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `UnsafeCell<i32>` has interior mutability
= note: `#[warn(transmute_to_interior_mutability)]` on by default

warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
--> $DIR/transmute-interior-mutability.rs:10:9
|
LL | mem::transmute::<&i32, &AtomicI32>(&42);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `AtomicI32` has interior mutability

error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
--> $DIR/transmute-interior-mutability.rs:16:9
|
LL | mem::transmute::<&i32, &mut UnsafeCell<i32>>(&42);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[deny(mutable_transmutes)]` on by default

error: aborting due to 1 previous error; 2 warnings emitted

Loading