-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Create implicit_transmute_types
lint.
#11047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create implicit_transmute_types
lint.
#11047
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @xFrednet (or someone else) soon. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
e82a5f2
to
f4963c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty good, thank you! I've left some comments.
/// ``` | ||
/// | ||
/// If you decide that you *do* want the types to be inferred, | ||
/// you can silence the lint by conveying your intention explicitly: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like having a configuration option to also disallow this would be nice, you can probably check the 1st and 2nd arguments of the last segment to see if either are _
f4963c2
to
fcb3401
Compare
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.
fcb3401
to
c6e0145
Compare
Hey @Flying-Toast welcome to Clippy 👋 Thank you for starting the review @Centri3. You're welcome to continue it, and ping me once everything is done :) |
I think I've already said everything I wanted to ^^ feel free to give your thoughts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, I think this is a good start. I left some comments. You're welcome to reach out to @Centri3 or me if you have any questions! :)
/// [`transmute`]: https://doc.rust-lang.org/core/mem/fn.transmute.html | ||
#[clippy::version = "1.72.0"] | ||
pub IMPLICIT_TRANSMUTE_TYPES, | ||
style, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would classify this more as a restriction
lint and also think it should be allow-by-default.
I've looked at examples where I used transmute and found this one, where I like the code more without the types:
#[must_use]
pub fn a_to_b(&self, src: A) -> B {
assert_eq!(size_of::<A>(), 4);
assert_eq!(size_of::<B>(), 4);
unsafe { transmute(src) }
}
There are definitely good reasons to have the types, but most projects would probably not directly agree with this. So, could you please change to group to restriction
?
I think the amount of tests, which needed to be adjusted also speaks for this change :)
/// In most cases Rust's type inference is helpful, however it can cause | ||
/// problems with [`transmute`]. `transmute` is wildly unsafe | ||
/// unless the types being transmuted are known to be compatible. As such, | ||
/// a seemingly innocent change in something's type can end up making a | ||
/// previously-valid transmute suddenly become unsound. Thus it is | ||
/// good practice to always be explicit about the types you expect to be | ||
/// transmuting between, so that the compiler will force you to | ||
/// reexamine the transmute if either type changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mostly agree with this statement, but feel like the explanation could be clearer. Also the example of changing a type might be confusing. I imagined this:
struct A(u32);
struct B(f32);
let a = A(1);
let b: B = unsafe { transmute(src) };
changing B to be B(f64)
would be accepted by the lint, but would be unsound. I think it should be more highlighted that there could be a problem if the usage changes. How about this description:
/// ### Why is this bad?
/// Rust uses inference to determine the types if they're not specified.
/// This can cause the transmuted types to change accidentally and the
/// transmute becoming unsound. Specifying the types ensures that any
/// changes will either be compatible or cause an error.
///
/// ### Example
/// ```rust
/// # use core::mem::transmute;
/// # fn accept_dest(_: u32) {}
/// let source = 42;
/// let dest = unsafe { transmute(source) };
///
/// // Changing `accept_dest` might change the target type of the transmute
/// // and make it unsound.
/// accept_dest(dest);
/// ```
///
/// Use instead:
/// ```rust
/// # use core::mem::transmute;
/// # fn accept_dest(_: u32) {}
/// let source = 42;
/// let dest = unsafe { transmute::<i32, u32>(source) };
///
/// // Changing `accept_dest` will cause an error if the argument is
/// // no longer an u32.
/// accept_dest(dest);
/// ```
@@ -44,7 +44,7 @@ fn condition_is_unsafe_block() { | |||
let a: i32 = 1; | |||
|
|||
// this should not warn because the condition is an unsafe block | |||
if unsafe { 1u32 == std::mem::transmute(a) } { | |||
if unsafe { 1u32 == std::mem::transmute::<i32, u32>(a) } { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: You can undo the changes in this and the other file, when you move the lint to restriction
, as that will make the lint allow-by-default. This is fine, but that way we can make the PR a bit cleaner :)
|
||
impl<'tcx> LateLintPass<'tcx> for ImplicitTransmuteTypes { | ||
fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { | ||
if let ExprKind::Call(func, _) = &e.kind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a check, to ensure that the code doesn't come from macros? You can checkout in_external_macro
for this :)
suggestion_span, | ||
"consider specifying the types intended to be transmuted", | ||
format!("::<{srctype}, {dsttype}>"), | ||
Applicability::MachineApplicable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Printing types like this can sometimes cause problems, when rustc can't determine the correct path for an item. This should be:
Applicability::MachineApplicable | |
Applicability::MaybeIncorrect |
let _: u32 = mem::transmute::<i32, u32>(123i32); | ||
let _: u32 = mem::transmute::<_, _>(123i32); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some more tests, with structs and other more complex types? Maybe play with String
, struct A(u32)
etc. These files don't get executed, so the transmutes can be unsound, without spawning nostril dragons 😉
&& let ExprKind::Path(qpath) = &func.kind | ||
&& let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() | ||
&& is_diagnostic_item_or_ctor(cx, def_id, sym::transmute) | ||
&& last_path_segment(qpath).args.is_none() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we could optionally check, that the two types are not the infer ones ::<_, _>
. But that's an optional improvement. You're welcome to pick it up, or we just create a follow-up issue for this :)
Ping @Flying-Toast, this is a ping from triage. Do you plan to continue this PR in the near future? |
Probably not, so I'm fine closing it or if someone else wants to take it over. |
Okay, thank you for the time and effort you already put into this PR. :D |
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.