Skip to content

add lint for transmute from &T to &mut T of a ADT argument - #15992

Open
mendelsshop wants to merge 8 commits into
rust-lang:masterfrom
mendelsshop:catch_adt_mut_transmut
Open

add lint for transmute from &T to &mut T of a ADT argument#15992
mendelsshop wants to merge 8 commits into
rust-lang:masterfrom
mendelsshop:catch_adt_mut_transmut

Conversation

@mendelsshop

@mendelsshop mendelsshop commented Oct 31, 2025

Copy link
Copy Markdown

View all comments

Checks transmutes between the same ADT, making sure type arguments don't go from &T to &mut T.

Like it would catch something like:

let _: Option<&mut i32> = std::mem::transmute(Some(&5i32));

changelog: new lint [mutable_adt_argument_transmute]

fixes #372

Caution

Concerns (1 active)

Managed by @rustbot—see help for details.

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch 2 times, most recently from 4d4c366 to 61c208c Compare October 31, 2025 02:38
@rustbot rustbot added the needs-fcp PRs that add, remove, or rename lints and need an FCP label Oct 31, 2025
@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch 2 times, most recently from 8b63f22 to cbe29ff Compare October 31, 2025 03:28
@mendelsshop mendelsshop changed the title add lint for transmute from &T to &mut T of a ADT arguement (WIP) add lint for transmute from &T to &mut T of a ADT argument (WIP) Oct 31, 2025
@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch 2 times, most recently from 27d8267 to fb142f2 Compare October 31, 2025 03:31
@mendelsshop

mendelsshop commented Oct 31, 2025

Copy link
Copy Markdown
Author

Edit:
I now skip the parts that don't match (like Option<_> and Result<_, _>), but this does require me to match on each type's .kind() to see if they are the same ADT, slice, ..., and if not I use skip_current_subtree().

The way I am doing it now, I take the from_ty and to_ty and just zip the ty.walk() of both of them, which leads to potential false positive like catching:

let _: Result<Option<&mut f32>, &usize> = std::mem::transmute(Option::<Result<&f32, usize>>::Some(Ok(&2f32)));

Because in the order of the walk() &mut f32 and &f32 are at the same position in their respective walk().

@mendelsshop mendelsshop changed the title add lint for transmute from &T to &mut T of a ADT argument (WIP) add lint for transmute from &T to &mut T of a ADT argument Oct 31, 2025
@mendelsshop
mendelsshop marked this pull request as ready for review October 31, 2025 14:55
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Oct 31, 2025
@rustbot

rustbot commented Oct 31, 2025

Copy link
Copy Markdown
Collaborator

r? @y21

rustbot has assigned @y21.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@github-actions

github-actions Bot commented Nov 20, 2025

Copy link
Copy Markdown

No changes for 7ac7844

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from 9df378f to 6d0d072 Compare November 20, 2025 20:58
@rustbot

This comment has been minimized.

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch 2 times, most recently from 053c5b8 to 1cc591c Compare November 20, 2025 21:16
@mendelsshop

Copy link
Copy Markdown
Author

r? clippy

@rustbot rustbot assigned dswij and unassigned y21 Dec 1, 2025
@mendelsshop

Copy link
Copy Markdown
Author

r? clippy

@rustbot rustbot assigned samueltardieu and unassigned dswij Dec 17, 2025

@samueltardieu samueltardieu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid false positives and get a better output, I would suggest that:

  • When different ADT or different primitive types are detected, return false immediately, as the types are structurally different
  • Instead of signaling the lint immediately (which will trigger several times if several references are cast into their mutable version), accumulate the pairs (from_ty, to_ty) into a vector. At the end, if the vector is not empty, trigger the lint and add a note for every different pair (you can use .dedup_by() from Itertools) through span_lint_and_then().
  • Add an example which checks that having several conversions is displayed properly (one lint, three notes), as in:
        let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32)));
        //~^ mutable_adt_argument_transmute

This should print something like:

error: transmuting references into their mutable version is unsound
  --> tests/ui/mutable_adt_argument_transmute.rs:17:57
   |
LL |         let _: Option<(&mut i32, &mut i32, &mut u32)> = std::mem::transmute(Some((&5i32, &10i32, &15u32)));
   |                                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: transmuting from &i32 to &mut i32
   = note: transmuting from &u32 to &mut u32

View changes since this review

Comment thread clippy_lints/src/transmute/mod.rs Outdated
/// std::mem::transmute::<Option<&i32>, Option<&mut i32>>(Some(&5));
/// }
/// ```
#[clippy::version = "1.92.0"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[clippy::version = "1.92.0"]
#[clippy::version = "1.95.0"]

use rustc_middle::ty::{self, GenericArgKind, Ty};

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
// assumes walk will return all types in the same order

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should ensure that only adts are matched against, or it may also trigger for other types if the behavior of wrong_transmute is changed for example, won't it?

@mendelsshop mendelsshop Jan 18, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit:
If you are talking about the arguments themselves, then they can be any type as long as they are both behind a reference, I currently do not check if the actual argument types that are behind the reference are the same.

I don't fully understand what you are asking, but it triggers for closure, fn, slice, reference, tuple, array and pointer types along with standard adts, basically any type that can have "arguments".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take for example the ty::FnPtr type: the type of the parameters may be safely transmuted from &T to &mut T, while the return type might not. In the two transmute examples below, only the second one should be linted:

        // This one is not dangerous, as the function will not try
        // to mutate its parameter anyway.
        fn f(x: &i32) {}
        let a: fn(&i32) = f;
        let b: fn(&mut u32) = std::mem::transmute(a);

        // This one should be linted because it would not be safe
        // to assume that the return value can be mutated.
        fn g() -> &'static i32 {
            &0
        }
        let c: fn() -> &'static i32 = g;
        let d: fn() -> &'static mut i32 = std::mem::transmute(c);

The same thing probably happens in closures as well, and maybe others. In this case, you should either special-case the lint (such as linting only FnPtr and closures return types and ignore parameters), or not lint at all in those cases.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jan 18, 2026
@rustbot

rustbot commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from 1cc591c to 2318c90 Compare January 18, 2026 17:01
Comment thread clippy_lints/src/transmute/mod.rs Outdated
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes between the same adt, where at least one of the type argument goes from &T to &mut T.
/// This is an a more complicated version of https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#mutable-transmutes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This is an a more complicated version of https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#mutable-transmutes.
/// This is a more complicated version of https://doc.rust-lang.org/rustc/lints/listing/deny-by-default.html#mutable-transmutes.

Also, maybe not "complicated".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe not "complicated".

Is "advanced" a better word?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"specialized"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@rustbot

This comment has been minimized.

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from 1b2eb41 to 7b6cb4e Compare March 6, 2026 20:11
@rustbot

This comment has been minimized.

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from 7b6cb4e to 07e26c6 Compare March 6, 2026 20:13
Comment thread clippy_lints/src/transmute/transmute_adt_argument.rs Outdated
Comment thread clippy_lints/src/transmute/transmute_adt_argument.rs Outdated
Comment thread clippy_lints/src/transmute/transmute_adt_argument.rs Outdated
Comment thread clippy_lints/src/transmute/transmute_adt_argument.rs Outdated
@Jarcho

Jarcho commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Ty::walk can't be used like this. It deduplicates types which means you can't keep the two iterators in sync and you'll miss problematic pairs.

Walking (&T, &mut T) will result in:

  • (&T, &mut T)
  • &T
  • T
  • &mut T

Walking (&T, &T) will result in:

  • (&T, &T)
  • &T
  • T

Here you would miss a &mut T to &T conversion because the second reference is skipped.

@mendelsshop

Copy link
Copy Markdown
Author

Ty::walk can't be used like this. It deduplicates types which means you can't keep the two iterators in sync and you'll miss problematic pairs.

Does it make sense to duplicate Ty::walk and have a version that doesn't deduplicate (seems relatively easy to do)? Or should I just use recursion?
It seems plausible that there might be other uses for a Ty::walk that doesn't deduplicate (but I don't have examples).

@samueltardieu

Copy link
Copy Markdown
Member

Does it make sense to duplicate Ty::walk and have a version that doesn't deduplicate (seems relatively easy to do)? Or should I just use recursion? It seems plausible that there might be other uses for a Ty::walk that doesn't deduplicate (but I don't have examples).

You need to deduplicate to avoid infinite recursion. But you could walk two types in parallel and deduplicate by pairs.

@Jarcho

Jarcho commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Infinite recursion only happens if you look at ADT fields.

@samueltardieu

Copy link
Copy Markdown
Member

Infinite recursion only happens if you look at ADT fields.

Ah, yes, that is what I had in mind, types can't be recursive even through pointers.

@Jarcho

Jarcho commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Does it make sense to duplicate Ty::walk and have a version that doesn't deduplicate (seems relatively easy to do)? Or should I just use recursion? It seems plausible that there might be other uses for a Ty::walk that doesn't deduplicate (but I don't have examples).

You want to walk both types in lockstep with each other. This doesn't map well to an iterator and everything that does this has it's own rules for how to walk the structure. Ty::walk works because the result ignores structure and just returns every unique type.

@mendelsshop

Copy link
Copy Markdown
Author

Ok, I moved away from ty::walk.
@rustbot ready

use rustc_middle::ty::{self, GenericArg, GenericArgKind, Ty};

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
// assumes walk will return all types in the same order

@samueltardieu samueltardieu Mar 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is walk here? Is the comment out of date?

View changes since the review

@mendelsshop mendelsshop Jun 17, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an out of date comment.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jun 17, 2026
@samueltardieu

Copy link
Copy Markdown
Member

Link to the FCP thread

@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from 7c4972b to a065770 Compare June 17, 2026 20:20
@mendelsshop
mendelsshop force-pushed the catch_adt_mut_transmut branch from a065770 to 7ac7844 Compare June 18, 2026 18:50
@rustbot

rustbot commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lint-nominated Create an FCP-thread on Zulip for this PR needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) S-waiting-on-concerns Status: This PR/issue has concerns that need to be addressed before moving forward with it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Catch transmuting Option<&T> to Option<&mut T>

7 participants