Skip to content

Commit 053455f

Browse files
author
Orion Gonzalez
committed
Check for changes to interior mutability in mem::transmute.
1 parent 0c4f3a4 commit 053455f

10 files changed

+171
-32
lines changed

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ lint_builtin_special_module_name_used_lib = found module declaration for lib.rs
142142
lint_builtin_special_module_name_used_main = found module declaration for main.rs
143143
.note = a binary crate cannot be used as library
144144
145+
lint_builtin_transmute_to_interior_mutability =
146+
transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
147+
.help = `{$output_ty}` has interior mutability
148+
145149
lint_builtin_trivial_bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters
146150
147151
lint_builtin_type_alias_bounds_enable_feat_help = add `#![feature(lazy_type_alias)]` to the crate attributes to enable the desired semantics

compiler/rustc_lint/src/builtin.rs

+59-31
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ use crate::lints::{
5656
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
5757
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
5858
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
59-
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
60-
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
61-
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
62-
BuiltinWhileTrue, InvalidAsmLabel,
59+
BuiltinTransmuteInteriorMutability, BuiltinTrivialBounds, BuiltinTypeAliasBounds,
60+
BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub,
61+
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
62+
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
6363
};
6464
use crate::nonstandard_style::{MethodLateContext, method_context};
6565
use crate::{
@@ -1103,41 +1103,68 @@ declare_lint! {
11031103
"transmuting &T to &mut T is undefined behavior, even if the reference is unused"
11041104
}
11051105

1106+
declare_lint! {
1107+
/// The `transmute_to_interior_mutability` lint catches transmuting from `&T` or `*const T` to another type
1108+
/// with interior mutability because changing its value is [undefined behavior].
1109+
///
1110+
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
1111+
///
1112+
/// ### Example
1113+
///
1114+
/// ```rust,compile_fail
1115+
/// unsafe {
1116+
/// let x = std::mem::transmute::<&i32, UnsafeCell<i32>>(&5);
1117+
/// let y = std::mem::transmute::<&i32, AtomicI32>(&5);
1118+
/// }
1119+
/// ```
1120+
///
1121+
/// {{produces}}
1122+
///
1123+
/// ### Explanation
1124+
///
1125+
/// It's probably a mistake to transmute a type without interior mutability to a type with it.
1126+
TRANSMUTE_TO_INTERIOR_MUTABILITY,
1127+
Warn,
1128+
"transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content"
1129+
}
1130+
1131+
declare_lint_pass!(TransmuteToInteriorMutability => [TRANSMUTE_TO_INTERIOR_MUTABILITY]);
11061132
declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
11071133

11081134
impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
11091135
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
1110-
if let Some((&ty::Ref(_, _, from_mutbl), &ty::Ref(_, _, to_mutbl))) =
1111-
get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
1112-
{
1113-
if from_mutbl < to_mutbl {
1114-
cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
1115-
}
1136+
// Is our expr mem::transmute?
1137+
let hir::ExprKind::Path(ref qpath) = expr.kind else { return };
1138+
let def: Res = cx.qpath_res(qpath, expr.hir_id);
1139+
let Res::Def(DefKind::Fn, def_id) = def else { return };
1140+
if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
1141+
return;
11161142
}
1143+
let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
1144+
let input = sig.inputs().skip_binder()[0];
1145+
let output = sig.output().skip_binder();
11171146

1118-
fn get_transmute_from_to<'tcx>(
1119-
cx: &LateContext<'tcx>,
1120-
expr: &hir::Expr<'_>,
1121-
) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
1122-
let def = if let hir::ExprKind::Path(ref qpath) = expr.kind {
1123-
cx.qpath_res(qpath, expr.hir_id)
1124-
} else {
1125-
return None;
1126-
};
1127-
if let Res::Def(DefKind::Fn, did) = def {
1128-
if !def_id_is_transmute(cx, did) {
1129-
return None;
1147+
// For both checks we only care if the input is an immutable ref
1148+
let &ty::Ref(_, input_deref, Mutability::Not) = input.kind() else { return };
1149+
match output.kind() {
1150+
// If the output is a mutable reference that's bad
1151+
&ty::Ref(_, _, Mutability::Mut) => {
1152+
cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
1153+
}
1154+
&ty::Ref(_, output_deref, Mutability::Not) => {
1155+
// If the input type doesn't have interior mutability but the output type does:
1156+
if input_deref.is_freeze(cx.tcx, cx.typing_env())
1157+
&& !output_deref.is_freeze(cx.tcx, cx.typing_env())
1158+
{
1159+
cx.emit_span_lint(
1160+
TRANSMUTE_TO_INTERIOR_MUTABILITY,
1161+
expr.span,
1162+
BuiltinTransmuteInteriorMutability { output_ty: output_deref },
1163+
);
11301164
}
1131-
let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
1132-
let from = sig.inputs().skip_binder()[0];
1133-
let to = sig.output().skip_binder();
1134-
return Some((from, to));
11351165
}
1136-
None
1137-
}
1138-
1139-
fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
1140-
cx.tcx.is_intrinsic(def_id, sym::transmute)
1166+
// The user is transmuting a reference into something else, warn...?
1167+
_ => (),
11411168
}
11421169
}
11431170
}
@@ -1596,6 +1623,7 @@ declare_lint_pass!(
15961623
NO_MANGLE_CONST_ITEMS,
15971624
NO_MANGLE_GENERIC_ITEMS,
15981625
MUTABLE_TRANSMUTES,
1626+
TRANSMUTE_TO_INTERIOR_MUTABILITY,
15991627
UNSTABLE_FEATURES,
16001628
UNREACHABLE_PUB,
16011629
TYPE_ALIAS_BOUNDS,

compiler/rustc_lint/src/lints.rs

+7
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ pub(crate) struct BuiltinConstNoMangle {
228228
#[diag(lint_builtin_mutable_transmutes)]
229229
pub(crate) struct BuiltinMutablesTransmutes;
230230

231+
#[derive(LintDiagnostic)]
232+
#[diag(lint_builtin_transmute_to_interior_mutability)]
233+
#[help]
234+
pub(crate) struct BuiltinTransmuteInteriorMutability<'tcx> {
235+
pub output_ty: Ty<'tcx>,
236+
}
237+
231238
#[derive(LintDiagnostic)]
232239
#[diag(lint_builtin_unstable_features)]
233240
pub(crate) struct BuiltinUnstableFeatures;

compiler/rustc_lint/src/passes.rs

-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ macro_rules! late_lint_methods {
5757
//
5858
// FIXME: eliminate the duplication with `Visitor`. But this also
5959
// contains a few lint-specific methods with no equivalent in `Visitor`.
60-
6160
macro_rules! declare_late_lint_pass {
6261
([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
6362
pub trait LateLintPass<'tcx>: LintPass {

tests/ui/consts/const-eval/issue-55541.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Test that we can handle newtypes wrapping extern types
44

55
#![feature(extern_types)]
6+
#![allow(transmute_to_interior_mutability)]
67

78
use std::marker::PhantomData;
89

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
warning: unknown lint: `transmute_to_interior_mutability`
2+
--> $DIR/issue-55541.rs:6:10
3+
|
4+
LL | #![allow(transmute_to_interior_mutability)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(unknown_lints)]` on by default
8+
9+
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
10+
--> $DIR/issue-55541.rs:19:3
11+
|
12+
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
13+
| ^^^^^^^^^^^^^^^^^^^
14+
|
15+
= help: `Wrapper` has interior mutability
16+
= note: `#[warn(transmute_to_interior_mutability)]` on by default
17+
18+
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
19+
--> $DIR/issue-55541.rs:25:3
20+
|
21+
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
22+
| ^^^^^^^^^^^^^^^^^^^
23+
|
24+
= help: `Wrapper2` has interior mutability
25+
26+
warning: 3 warnings emitted
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ run-pass
2+
// https://github.com/rust-lang/rust/issues/111229
3+
4+
pub struct Foo(std::cell::UnsafeCell<usize>);
5+
pub struct Bar([u8; 0]);
6+
7+
pub fn foo(f: &Bar) {
8+
unsafe {
9+
let f = std::mem::transmute::<&Bar, &Foo>(f);
10+
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
11+
//~| HELP `Foo` has interior mutability
12+
*(f.0.get()) += 1;
13+
}
14+
}
15+
16+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
2+
--> $DIR/interior-mutability-issue-111229.rs:9:17
3+
|
4+
LL | let f = std::mem::transmute::<&Bar, &Foo>(f);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: `Foo` has interior mutability
8+
= note: `#[warn(transmute_to_interior_mutability)]` on by default
9+
10+
warning: 1 warning emitted
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![allow(unused)]
2+
use std::{cell::UnsafeCell, mem, sync::atomic::AtomicI32};
3+
4+
fn main() {
5+
unsafe {
6+
mem::transmute::<&i32, &UnsafeCell<i32>>(&42);
7+
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
8+
//~| HELP `UnsafeCell<i32>` has interior mutability
9+
// It's an error to transmute to a type containing unsafe cell
10+
mem::transmute::<&i32, &AtomicI32>(&42);
11+
//~^ WARNING transmuting from a type without interior mutability to a type with interior mutability
12+
//~| HELP `AtomicI32` has interior mutability
13+
// mutable_transmutes triggers before
14+
15+
// This one is here because & -> &mut is worse, to assert that this one triggers.
16+
mem::transmute::<&i32, &mut UnsafeCell<i32>>(&42);
17+
//~^ ERROR transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
18+
};
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
2+
--> $DIR/transmute-interior-mutability.rs:6:9
3+
|
4+
LL | mem::transmute::<&i32, &UnsafeCell<i32>>(&42);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: `UnsafeCell<i32>` has interior mutability
8+
= note: `#[warn(transmute_to_interior_mutability)]` on by default
9+
10+
warning: transmuting from a type without interior mutability to a type with interior mutability is undefined behaviour if you modify the content
11+
--> $DIR/transmute-interior-mutability.rs:10:9
12+
|
13+
LL | mem::transmute::<&i32, &AtomicI32>(&42);
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= help: `AtomicI32` has interior mutability
17+
18+
error: transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
19+
--> $DIR/transmute-interior-mutability.rs:16:9
20+
|
21+
LL | mem::transmute::<&i32, &mut UnsafeCell<i32>>(&42);
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
|
24+
= note: `#[deny(mutable_transmutes)]` on by default
25+
26+
error: aborting due to 1 previous error; 2 warnings emitted
27+

0 commit comments

Comments
 (0)