Skip to content

Commit ff6a3db

Browse files
Lint against &T to &mut T and &T to &UnsafeCell<T> transmutes
This adds the lint against `&T`->`&UnsafeCell<T>` transmutes, and also check in struct fields, and reference casts (`&*(&a as *const u8 as *const UnsafeCell<u8>)`). 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).
1 parent 7e565cc commit ff6a3db

20 files changed

+1131
-93
lines changed

compiler/rustc_lint/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ lint_builtin_missing_doc = missing documentation for {$article} {$desc}
119119
120120
lint_builtin_mutable_transmutes =
121121
transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell
122+
.note = transmute from `{$from}` to `{$to}`
122123
123124
lint_builtin_no_mangle_fn = declaration of a `no_mangle` function
124125
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
888889
.label = usage of unsafe attribute
889890
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
890891
892+
lint_unsafe_cell_conversions =
893+
converting &T to &UnsafeCell<T> is error-prone, rarely intentional and may cause undefined behavior
894+
.label = conversion happend here
895+
.note = conversion from `{$from}` to `{$to}`
896+
891897
lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´
892898
893899
lint_untranslatable_diag = diagnostics should be created using translatable messages

compiler/rustc_lint/src/builtin.rs

+5-73
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,11 @@ use crate::lints::{
5454
BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives,
5555
BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures,
5656
BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents,
57-
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes,
58-
BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed,
59-
BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller,
60-
BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub,
61-
BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub,
62-
BuiltinWhileTrue, InvalidAsmLabel,
57+
BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinNoMangleGeneric,
58+
BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds,
59+
BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit,
60+
BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures,
61+
BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel,
6362
};
6463
use crate::nonstandard_style::{MethodLateContext, method_context};
6564
use crate::{
@@ -1076,72 +1075,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
10761075
}
10771076
}
10781077

1079-
declare_lint! {
1080-
/// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut
1081-
/// T` because it is [undefined behavior].
1082-
///
1083-
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
1084-
///
1085-
/// ### Example
1086-
///
1087-
/// ```rust,compile_fail
1088-
/// unsafe {
1089-
/// let y = std::mem::transmute::<&i32, &mut i32>(&5);
1090-
/// }
1091-
/// ```
1092-
///
1093-
/// {{produces}}
1094-
///
1095-
/// ### Explanation
1096-
///
1097-
/// Certain assumptions are made about aliasing of data, and this transmute
1098-
/// violates those assumptions. Consider using [`UnsafeCell`] instead.
1099-
///
1100-
/// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
1101-
MUTABLE_TRANSMUTES,
1102-
Deny,
1103-
"transmuting &T to &mut T is undefined behavior, even if the reference is unused"
1104-
}
1105-
1106-
declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
1107-
1108-
impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
1109-
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-
}
1116-
}
1117-
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;
1130-
}
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));
1135-
}
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)
1141-
}
1142-
}
1143-
}
1144-
11451078
declare_lint! {
11461079
/// The `unstable_features` lint detects uses of `#![feature]`.
11471080
///
@@ -1595,7 +1528,6 @@ declare_lint_pass!(
15951528
UNUSED_DOC_COMMENTS,
15961529
NO_MANGLE_CONST_ITEMS,
15971530
NO_MANGLE_GENERIC_ITEMS,
1598-
MUTABLE_TRANSMUTES,
15991531
UNSTABLE_FEATURES,
16001532
UNREACHABLE_PUB,
16011533
TYPE_ALIAS_BOUNDS,

compiler/rustc_lint/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ mod lints;
6161
mod macro_expr_fragment_specifier_2024_migration;
6262
mod map_unit_fn;
6363
mod multiple_supertrait_upcastable;
64+
mod mutable_transmutes;
6465
mod non_ascii_idents;
6566
mod non_fmt_panic;
6667
mod non_local_def;
@@ -98,6 +99,7 @@ use let_underscore::*;
9899
use macro_expr_fragment_specifier_2024_migration::*;
99100
use map_unit_fn::*;
100101
use multiple_supertrait_upcastable::*;
102+
use mutable_transmutes::*;
101103
use non_ascii_idents::*;
102104
use non_fmt_panic::NonPanicFmt;
103105
use non_local_def::*;

compiler/rustc_lint/src/lints.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use std::num::NonZero;
55
use rustc_errors::codes::*;
66
use rustc_errors::{
77
Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString, ElidedLifetimeInPathSubdiag,
8-
EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
8+
EmissionGuarantee, IntoDiagArg, LintDiagnostic, MultiSpan, SubdiagMessageOp, Subdiagnostic,
9+
SuggestionStyle,
910
};
1011
use rustc_hir::def::Namespace;
1112
use rustc_hir::def_id::DefId;
@@ -224,9 +225,41 @@ pub(crate) struct BuiltinConstNoMangle {
224225
pub suggestion: Span,
225226
}
226227

228+
// This would be more convenient as `from: String` and `to: String` instead of `from` and `to`
229+
// being `ConversionBreadcrumbs`, but that'll mean we'll have to format the `Ty`s in the lint code,
230+
// and then we'll ICE when the lint is allowed, because formatting a `Ty` is expensive and so the
231+
// compiler panics if it is done when there aren't diagnostics.
232+
pub(crate) struct ConversionBreadcrumbs<'a> {
233+
pub before_ty: &'static str,
234+
pub ty: Ty<'a>,
235+
pub after_ty: String,
236+
}
237+
238+
impl IntoDiagArg for ConversionBreadcrumbs<'_> {
239+
fn into_diag_arg(self) -> DiagArgValue {
240+
format!("{}{}{}", self.before_ty, self.ty, self.after_ty).into_diag_arg()
241+
}
242+
}
243+
244+
// mutable_transmutes.rs
227245
#[derive(LintDiagnostic)]
228246
#[diag(lint_builtin_mutable_transmutes)]
229-
pub(crate) struct BuiltinMutablesTransmutes;
247+
#[note]
248+
pub(crate) struct BuiltinMutablesTransmutes<'a> {
249+
pub from: ConversionBreadcrumbs<'a>,
250+
pub to: ConversionBreadcrumbs<'a>,
251+
}
252+
253+
// mutable_transmutes.rs
254+
#[derive(LintDiagnostic)]
255+
#[diag(lint_unsafe_cell_conversions)]
256+
#[note]
257+
pub(crate) struct UnsafeCellConversions<'a> {
258+
#[label]
259+
pub orig_cast: Option<Span>,
260+
pub from: ConversionBreadcrumbs<'a>,
261+
pub to: ConversionBreadcrumbs<'a>,
262+
}
230263

231264
#[derive(LintDiagnostic)]
232265
#[diag(lint_builtin_unstable_features)]

0 commit comments

Comments
 (0)