Skip to content

Commit f771457

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

10 files changed

+161
-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

+57-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,67 @@ 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+
11061131
declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
11071132

11081133
impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
11091134
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-
}
1135+
// Is our expr mem::transmute?
1136+
let hir::ExprKind::Path(ref qpath) = expr.kind else { return };
1137+
let def: Res = cx.qpath_res(qpath, expr.hir_id);
1138+
let Res::Def(DefKind::Fn, def_id) = def else { return };
1139+
if !cx.tcx.is_intrinsic(def_id, sym::transmute) {
1140+
return;
11161141
}
1142+
let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
1143+
let input = sig.inputs().skip_binder()[0];
1144+
let output = sig.output().skip_binder();
11171145

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;
1146+
// For both checks we only care if the input is an immutable ref
1147+
let &ty::Ref(_, input_deref, Mutability::Not) = input.kind() else { return };
1148+
match output.kind() {
1149+
// If the output is a mutable reference that's bad
1150+
&ty::Ref(_, _, Mutability::Mut) => {
1151+
cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes);
1152+
}
1153+
&ty::Ref(_, output_deref, Mutability::Not) => {
1154+
// If the input type doesn't have interior mutability but the output type does:
1155+
if input_deref.is_freeze(cx.tcx, cx.typing_env())
1156+
&& !output_deref.is_freeze(cx.tcx, cx.typing_env())
1157+
{
1158+
cx.emit_span_lint(
1159+
TRANSMUTE_TO_INTERIOR_MUTABILITY,
1160+
expr.span,
1161+
BuiltinTransmuteInteriorMutability { output_ty: output_deref },
1162+
);
11301163
}
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));
11351164
}
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)
1165+
// The user is transmuting a reference into something else, warn...?
1166+
_ => (),
11411167
}
11421168
}
11431169
}

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(extern_types)]
67

78
use std::marker::PhantomData;
89

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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/issue-55541.rs:18:3
3+
|
4+
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: `Wrapper` 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/issue-55541.rs:24:3
12+
|
13+
LL | std::mem::transmute(&MAGIC_FFI_STATIC)
14+
| ^^^^^^^^^^^^^^^^^^^
15+
|
16+
= help: `Wrapper2` has interior mutability
17+
18+
warning: 2 warnings emitted
19+
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)