-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add missing_transmute_annotations
lint
#12239
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
Changes from all commits
7976657
8e04961
ffa1279
ee25582
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{GenericArg, HirId, Local, Node, Path, TyKind}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::lint::in_external_macro; | ||
use rustc_middle::ty::Ty; | ||
|
||
use crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS; | ||
|
||
fn get_parent_local_binding_ty<'tcx>(cx: &LateContext<'tcx>, expr_hir_id: HirId) -> Option<Local<'tcx>> { | ||
let mut parent_iter = cx.tcx.hir().parent_iter(expr_hir_id); | ||
if let Some((_, node)) = parent_iter.next() { | ||
match node { | ||
Node::Local(local) => Some(*local), | ||
Node::Block(_) => { | ||
if let Some((parent_hir_id, Node::Expr(expr))) = parent_iter.next() | ||
&& matches!(expr.kind, rustc_hir::ExprKind::Block(_, _)) | ||
{ | ||
get_parent_local_binding_ty(cx, parent_hir_id) | ||
} else { | ||
None | ||
} | ||
}, | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool { | ||
let def_id = cx.tcx.hir().enclosing_body_owner(expr_hir_id); | ||
if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(def_id) { | ||
let body = cx.tcx.hir().body(body_id); | ||
return body.value.peel_blocks().hir_id == expr_hir_id; | ||
} | ||
false | ||
} | ||
|
||
pub(super) fn check<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
path: &Path<'tcx>, | ||
from_ty: Ty<'tcx>, | ||
to_ty: Ty<'tcx>, | ||
expr_hir_id: HirId, | ||
) -> bool { | ||
let last = path.segments.last().unwrap(); | ||
if in_external_macro(cx.tcx.sess, last.ident.span) { | ||
// If it comes from a non-local macro, we ignore it. | ||
return false; | ||
} | ||
let args = last.args; | ||
let missing_generic = match args { | ||
Some(args) if !args.args.is_empty() => args.args.iter().any(|arg| match arg { | ||
GenericArg::Infer(_) => true, | ||
GenericArg::Type(ty) => matches!(ty.kind, TyKind::Infer), | ||
_ => false, | ||
}), | ||
_ => true, | ||
}; | ||
if !missing_generic { | ||
return false; | ||
} | ||
// If it's being set as a local variable value... | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should include closures here imo, if they have type annotations. This could include the parameter if it's specified as well, but not something like: // i64 is known, but [i32; 2] is never specified
|x: i32, y: i32| -> i64 unsafe { transmute::<[i32; 2], _>([x, y]) }; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should special case closures either. It adds a lot of complexity to detect such cases for a very marginal gain imo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really. The return type part is easy, and the parameter is too - Get the I wouldn't block it on this so if it's not done I can add it after, eventually. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not in this sense. I meant, for the same reason we want to enforce having type annotations when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree with that as closures are usually private and not public. It's more akin to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why closures being private or public changes anything. If you want to bring this back to debate, let's continue on zulip. ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll bring up my current points on zulip sometime. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 There is an open thread already, don't hesitate to comment there directly. ;)
y21 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if let Some(local) = get_parent_local_binding_ty(cx, expr_hir_id) { | ||
// ... which does have type annotations. | ||
if let Some(ty) = local.ty | ||
// If this is a `let x: _ =`, we should lint. | ||
&& !matches!(ty.kind, TyKind::Infer) | ||
{ | ||
return false; | ||
} | ||
// We check if this transmute is not the only element in the function | ||
} else if is_function_block(cx, expr_hir_id) { | ||
GuillaumeGomez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false; | ||
} | ||
span_lint_and_sugg( | ||
cx, | ||
MISSING_TRANSMUTE_ANNOTATIONS, | ||
last.ident.span.with_hi(path.span.hi()), | ||
"transmute used without annotations", | ||
"consider adding missing annotations", | ||
format!("{}::<{from_ty}, {to_ty}>", last.ident.as_str()), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If one/more of the types can be left inferred, can we keep it like that here? e.g., There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not? Is it because it'd require rewriting it? I don't see why this shouldn't be done. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I misunderstood what you meant but this lint is about making it explicit what the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I meant was that, if it's within a I see what you mean that providing them is better, but special-casing it then not suggesting it is a bit contradictory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea behind my position is that if you ever remove the |
||
Applicability::MaybeIncorrect, | ||
); | ||
true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
//@aux-build:macro_rules.rs | ||
|
||
#![warn(clippy::missing_transmute_annotations)] | ||
#![allow(clippy::let_with_type_underscore)] | ||
|
||
#[macro_use] | ||
extern crate macro_rules; | ||
|
||
macro_rules! local_bad_transmute { | ||
($e:expr) => { | ||
std::mem::transmute::<[u16; 2], i32>($e) | ||
//~^ ERROR: transmute used without annotations | ||
}; | ||
} | ||
|
||
fn bar(x: i32) -> i32 { | ||
x | ||
} | ||
|
||
unsafe fn foo1() -> i32 { | ||
// Should not warn! | ||
std::mem::transmute([1u16, 2u16]) | ||
} | ||
|
||
// Should not warn! | ||
const _: i32 = unsafe { std::mem::transmute([1u16, 2u16]) }; | ||
|
||
#[repr(i32)] | ||
enum Foo { | ||
A = 0, | ||
} | ||
|
||
unsafe fn foo2() -> i32 { | ||
let mut i: i32 = 0; | ||
i = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
//~^ ERROR: transmute used without annotations | ||
i = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
//~^ ERROR: transmute used without annotations | ||
i = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
//~^ ERROR: transmute used without annotations | ||
i = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
//~^ ERROR: transmute used without annotations | ||
|
||
let x: i32 = bar(std::mem::transmute::<[u16; 2], i32>([1u16, 2u16])); | ||
//~^ ERROR: transmute used without annotations | ||
bar(std::mem::transmute::<[u16; 2], i32>([1u16, 2u16])); | ||
//~^ ERROR: transmute used without annotations | ||
|
||
i = local_bad_transmute!([1u16, 2u16]); | ||
|
||
// Should not warn. | ||
i = bad_transmute!([1u16, 2u16]); | ||
|
||
i = std::mem::transmute::<[i16; 2], i32>([0i16, 0i16]); | ||
//~^ ERROR: transmute used without annotations | ||
|
||
i = std::mem::transmute::<Foo, i32>(Foo::A); | ||
//~^ ERROR: transmute used without annotations | ||
|
||
i | ||
} | ||
|
||
fn main() { | ||
let x: _ = unsafe { std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]) }; | ||
//~^ ERROR: transmute used without annotations | ||
unsafe { | ||
let x: _ = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
//~^ ERROR: transmute used without annotations | ||
|
||
// Should not warn. | ||
std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); | ||
let x: i32 = std::mem::transmute::<[u16; 2], _>([1u16, 2u16]); | ||
let x: i32 = std::mem::transmute::<_, i32>([1u16, 2u16]); | ||
let x: i32 = std::mem::transmute([1u16, 2u16]); | ||
} | ||
let x: i32 = unsafe { std::mem::transmute([1u16, 2u16]) }; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.