Skip to content

Commit c6e0145

Browse files
committed
Create implicit_transmute_types lint.
Type inference is useful in most cases, but it can cause issues around usage of transmute. Even though a specific usage of transmute may have been confirmed to be sound, someone might unintentionally change a piece of surrounding code that causes the transmuted types to change and possibly become unsound. Despite that, transmute() will happily continue to infer these changed types. This commit adds a lint `implicit_transmute_types` that catches calls to transmute without an explicit turbofish. The idea is that the initial author of code that uses a transmute will verify the soundness of converting between the two types, and enfore those types on the transmute call. If the types end up changing later, typecheck will fail and force the transmute call to be updated and remind the user to reassess the soundness of transmuting between the new types.
1 parent 9020937 commit c6e0145

35 files changed

+345
-157
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4857,6 +4857,7 @@ Released 2018-09-13
48574857
[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
48584858
[`implicit_saturating_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_add
48594859
[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
4860+
[`implicit_transmute_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_transmute_types
48604861
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
48614862
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
48624863
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
206206
crate::implicit_return::IMPLICIT_RETURN_INFO,
207207
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
208208
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
209+
crate::implicit_transmute_types::IMPLICIT_TRANSMUTE_TYPES_INFO,
209210
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
210211
crate::incorrect_impls::INCORRECT_CLONE_IMPL_ON_COPY_TYPE_INFO,
211212
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use clippy_utils::{diagnostics::span_lint_and_then, is_diagnostic_item_or_ctor, last_path_segment};
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{self as hir, ExprKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
use rustc_span::symbol::sym;
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
/// Checks for calls to [`transmute`] without explicit type parameters
11+
/// (i.e. without turbofish syntax).
12+
///
13+
/// ### Why is this bad?
14+
/// In most cases Rust's type inference is helpful, however it can cause
15+
/// problems with [`transmute`]. `transmute` is wildly unsafe
16+
/// unless the types being transmuted are known to be compatible. As such,
17+
/// a seemingly innocent change in something's type can end up making a
18+
/// previously-valid transmute suddenly become unsound. Thus it is
19+
/// good practice to always be explicit about the types you expect to be
20+
/// transmuting between, so that the compiler will force you to
21+
/// reexamine the transmute if either type changes.
22+
///
23+
/// ### Example
24+
/// ```rust
25+
/// #[repr(transparent)]
26+
/// struct CharWrapper {
27+
/// _inner: char,
28+
/// }
29+
///
30+
/// let wrapped = CharWrapper { _inner: 'a' };
31+
/// let transmuted = unsafe { core::mem::transmute(wrapped) };
32+
///
33+
/// // This is sound now, but if it gets changed in the future to
34+
/// // something that expects a type other than `char`, the transmute
35+
/// // would infer it returns that type, which is likely unsound.
36+
/// let _ = char::is_lowercase(transmuted);
37+
/// ```
38+
///
39+
/// Specify type parameters:
40+
/// ```rust
41+
/// # #[repr(transparent)]
42+
/// # struct CharWrapper {
43+
/// # _inner: char,
44+
/// # }
45+
/// let wrapped = CharWrapper { _inner: 'a' };
46+
/// // Because we explicitly specify the types for the transmute, any change in
47+
/// // surrounding code that would cause the transmute call to infer different
48+
/// // types will now be caught by typechecking, forcing us to come back and
49+
/// // reassess the soundsness of transmuting between the new types.
50+
/// let transmuted = unsafe { core::mem::transmute::<CharWrapper, char>(wrapped) };
51+
///
52+
/// let _ = char::is_lowercase(transmuted);
53+
/// ```
54+
///
55+
/// If you decide that you *do* want the types to be inferred,
56+
/// you can silence the lint by conveying your intention explicitly:
57+
/// ```rust
58+
/// # use std::mem::transmute;
59+
/// # fn main() {
60+
/// # unsafe {
61+
/// # let foo: i32 = 123;
62+
/// # let _: u32 =
63+
/// transmute::<_, _>(foo);
64+
/// # }
65+
/// # }
66+
/// ```
67+
///
68+
/// [`transmute`]: https://doc.rust-lang.org/core/mem/fn.transmute.html
69+
#[clippy::version = "1.72.0"]
70+
pub IMPLICIT_TRANSMUTE_TYPES,
71+
style,
72+
"calling mem::transmute without explicit type parameters"
73+
}
74+
75+
declare_lint_pass!(ImplicitTransmuteTypes => [IMPLICIT_TRANSMUTE_TYPES]);
76+
77+
impl<'tcx> LateLintPass<'tcx> for ImplicitTransmuteTypes {
78+
fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
79+
if let ExprKind::Call(func, _) = &e.kind
80+
&& let ExprKind::Path(qpath) = &func.kind
81+
&& let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id()
82+
&& is_diagnostic_item_or_ctor(cx, def_id, sym::transmute)
83+
&& last_path_segment(qpath).args.is_none()
84+
{
85+
let suggestion_span = qpath.span().shrink_to_hi();
86+
87+
let substs = cx.typeck_results().node_substs(func.hir_id);
88+
let srctype = substs.type_at(0);
89+
let dsttype = substs.type_at(1);
90+
91+
92+
span_lint_and_then(
93+
cx,
94+
IMPLICIT_TRANSMUTE_TYPES,
95+
e.span,
96+
"`transmute` called without explicit type parameters",
97+
|b| {
98+
b.span_suggestion_verbose(
99+
suggestion_span,
100+
"consider specifying the types intended to be transmuted",
101+
format!("::<{srctype}, {dsttype}>"),
102+
Applicability::MachineApplicable
103+
);
104+
}
105+
);
106+
}
107+
}
108+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ mod implicit_hasher;
149149
mod implicit_return;
150150
mod implicit_saturating_add;
151151
mod implicit_saturating_sub;
152+
mod implicit_transmute_types;
152153
mod inconsistent_struct_constructor;
153154
mod incorrect_impls;
154155
mod index_refutable_slice;
@@ -1072,6 +1073,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10721073
});
10731074
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
10741075
store.register_early_pass(|| Box::new(visibility::Visibility));
1076+
store.register_late_pass(|_| Box::new(implicit_transmute_types::ImplicitTransmuteTypes));
10751077
// add lints here, do not remove this comment, it's used in `new_lint`
10761078
}
10771079

tests/ui/author/issue_3849.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(dead_code)]
22
#![allow(clippy::zero_ptr)]
33
#![allow(clippy::transmute_ptr_to_ref)]
4-
#![allow(clippy::transmuting_null)]
4+
#![allow(clippy::transmuting_null, clippy::implicit_transmute_types)]
55

66
pub const ZPTR: *const usize = 0 as *const _;
77

tests/ui/blocks_in_if_conditions.fixed

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ fn condition_is_unsafe_block() {
4444
let a: i32 = 1;
4545

4646
// this should not warn because the condition is an unsafe block
47-
if unsafe { 1u32 == std::mem::transmute(a) } {
47+
if unsafe { 1u32 == std::mem::transmute::<i32, u32>(a) } {
4848
println!("1u32 == a");
4949
}
5050
}

tests/ui/blocks_in_if_conditions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ fn condition_is_unsafe_block() {
4444
let a: i32 = 1;
4545

4646
// this should not warn because the condition is an unsafe block
47-
if unsafe { 1u32 == std::mem::transmute(a) } {
47+
if unsafe { 1u32 == std::mem::transmute::<i32, u32>(a) } {
4848
println!("1u32 == a");
4949
}
5050
}

tests/ui/crashes/ice-4968.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Test for https://github.com/rust-lang/rust-clippy/issues/4968
22

33
#![warn(clippy::unsound_collection_transmute)]
4-
#![allow(clippy::transmute_undefined_repr)]
4+
#![allow(clippy::transmute_undefined_repr, clippy::implicit_transmute_types)]
55

66
trait Trait {
77
type Assoc;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::implicit_transmute_types)]
4+
5+
use std::mem;
6+
7+
fn main() {
8+
unsafe {
9+
let _: u32 = mem::transmute::<i32, u32>(123i32);
10+
11+
let _: u32 = mem::transmute::<i32, u32>(123i32);
12+
let _: u32 = mem::transmute::<_, _>(123i32);
13+
}
14+
}

tests/ui/implicit_transmute_types.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::implicit_transmute_types)]
4+
5+
use std::mem;
6+
7+
fn main() {
8+
unsafe {
9+
let _: u32 = mem::transmute(123i32);
10+
11+
let _: u32 = mem::transmute::<i32, u32>(123i32);
12+
let _: u32 = mem::transmute::<_, _>(123i32);
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: `transmute` called without explicit type parameters
2+
--> $DIR/implicit_transmute_types.rs:9:22
3+
|
4+
LL | let _: u32 = mem::transmute(123i32);
5+
| ^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::implicit-transmute-types` implied by `-D warnings`
8+
help: consider specifying the types intended to be transmuted
9+
|
10+
LL | let _: u32 = mem::transmute::<i32, u32>(123i32);
11+
| ++++++++++++
12+
13+
error: aborting due to previous error
14+

tests/ui/missing_const_for_fn/could_be_const.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::missing_const_for_fn)]
2-
#![allow(incomplete_features, clippy::let_and_return)]
2+
#![allow(incomplete_features, clippy::let_and_return, clippy::implicit_transmute_types)]
33
#![feature(const_mut_refs)]
44
#![feature(const_trait_impl)]
55

tests/ui/ptr_cast_constness.fixed

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
//@aux-build:proc_macros.rs:proc-macro
33

44
#![warn(clippy::ptr_cast_constness)]
5-
#![allow(clippy::transmute_ptr_to_ref, clippy::unnecessary_cast, unused)]
5+
#![allow(
6+
clippy::implicit_transmute_types,
7+
clippy::transmute_ptr_to_ref,
8+
clippy::unnecessary_cast,
9+
unused
10+
)]
611

712
extern crate proc_macros;
813
use proc_macros::{external, inline_macros};

tests/ui/ptr_cast_constness.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
//@aux-build:proc_macros.rs:proc-macro
33

44
#![warn(clippy::ptr_cast_constness)]
5-
#![allow(clippy::transmute_ptr_to_ref, clippy::unnecessary_cast, unused)]
5+
#![allow(
6+
clippy::implicit_transmute_types,
7+
clippy::transmute_ptr_to_ref,
8+
clippy::unnecessary_cast,
9+
unused
10+
)]
611

712
extern crate proc_macros;
813
use proc_macros::{external, inline_macros};

tests/ui/ptr_cast_constness.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
error: `as` casting between raw pointers while changing only its constness
2-
--> $DIR/ptr_cast_constness.rs:11:41
2+
--> $DIR/ptr_cast_constness.rs:16:41
33
|
44
LL | let _: &mut T = std::mem::transmute(p as *mut T);
55
| ^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `p.cast_mut()`
66
|
77
= note: `-D clippy::ptr-cast-constness` implied by `-D warnings`
88

99
error: `as` casting between raw pointers while changing only its constness
10-
--> $DIR/ptr_cast_constness.rs:12:19
10+
--> $DIR/ptr_cast_constness.rs:17:19
1111
|
1212
LL | let _ = &mut *(p as *mut T);
1313
| ^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `p.cast_mut()`
1414

1515
error: `as` casting between raw pointers while changing only its constness
16-
--> $DIR/ptr_cast_constness.rs:27:17
16+
--> $DIR/ptr_cast_constness.rs:32:17
1717
|
1818
LL | let _ = *ptr_ptr as *mut u32;
1919
| ^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `(*ptr_ptr).cast_mut()`
2020

2121
error: `as` casting between raw pointers while changing only its constness
22-
--> $DIR/ptr_cast_constness.rs:30:13
22+
--> $DIR/ptr_cast_constness.rs:35:13
2323
|
2424
LL | let _ = ptr as *mut u32;
2525
| ^^^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `ptr.cast_mut()`
2626

2727
error: `as` casting between raw pointers while changing only its constness
28-
--> $DIR/ptr_cast_constness.rs:31:13
28+
--> $DIR/ptr_cast_constness.rs:36:13
2929
|
3030
LL | let _ = mut_ptr as *const u32;
3131
| ^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_const`, a safer alternative: `mut_ptr.cast_const()`
3232

3333
error: `as` casting between raw pointers while changing only its constness
34-
--> $DIR/ptr_cast_constness.rs:60:13
34+
--> $DIR/ptr_cast_constness.rs:65:13
3535
|
3636
LL | let _ = ptr as *mut u32;
3737
| ^^^^^^^^^^^^^^^ help: try `pointer::cast_mut`, a safer alternative: `ptr.cast_mut()`
3838

3939
error: `as` casting between raw pointers while changing only its constness
40-
--> $DIR/ptr_cast_constness.rs:61:13
40+
--> $DIR/ptr_cast_constness.rs:66:13
4141
|
4242
LL | let _ = mut_ptr as *const u32;
4343
| ^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast_const`, a safer alternative: `mut_ptr.cast_const()`

tests/ui/transmute.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
#![allow(dead_code, clippy::borrow_as_ptr, clippy::needless_lifetimes)]
1+
#![allow(
2+
dead_code,
3+
clippy::implicit_transmute_types,
4+
clippy::borrow_as_ptr,
5+
clippy::needless_lifetimes
6+
)]
27

38
extern crate core;
49

0 commit comments

Comments
 (0)