-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Implement volatile_composites
lint
#15686
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
base: master
Are you sure you want to change the base?
Conversation
rustbot has assigned @samueltardieu. Use |
Lintcheck changes for ec9f7c0
This comment will be updated if you push new changes |
This comment has been minimized.
This comment has been minimized.
7b7b0ee
to
14ae176
Compare
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. |
3c51bbd
to
ec5febf
Compare
Volatile reads and writes to non-primitive types are not well-defined, and can cause problems. See rust-lang#15529 for more details.
ec5febf
to
fc17293
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.
Thanks for the contribution. It is in a good shape already, but I have some questions.
Do not hesitate to add extra tests, especially if my comments were wrong, in order to distinguish between the cases you thought of when the code was more complete than what I suggest.
Also, I would like to have more doc comments on functions, so that it is easier to read.
/// } | ||
/// } | ||
/// ``` | ||
#[clippy::version = "1.91.0"] |
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.
#[clippy::version = "1.91.0"] | |
#[clippy::version = "1.92.0"] |
// NonNull::{read_volatile,write_volatile} | ||
|
||
// primitive type: | ||
// unit, [iu]{8,16,32,64,128?}, f{32,64}, thin pointer, usize, isize, bool, char |
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.
What about f16
and f128
?
What are those comments anyway? Were those notes you took during development that need to be removed?
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.
Yes, I'll clean these up. In practice this set of types is whatever Ty::is_primitive
returns true for, which includes f16
and f128
.
// Zero-sized types are intrinsically safe to use volatile on since they won't | ||
// actually generate *any* loads or stores. But this is also used to skip zero | ||
// fields of #[repr(transparent)] structures. |
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 use doc comments to comment out functions? This makes it easier to navigate when later editing the code, as IDEs will render them properly.
// actually generate *any* loads or stores. But this is also used to skip zero | ||
// fields of #[repr(transparent)] structures. | ||
fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { | ||
if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) |
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.
Doesn't layout_of()
already normalize the type? Are there cases that would not be covered by the following?
cx.tcx
.layout_of(cx.typing_env().as_query_input(ty))
.is_ok_and(|layout| layout.layout.is_zst())
If there are, it would be great to add them as tests.
if let ty::RawPtr(_inner, _) = ty.kind() { | ||
ty.pointee_metadata_ty_or_projection(cx.tcx).is_unit() | ||
} else { | ||
false | ||
} |
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.
Are there cases of real ()
metadata? Won't the following be equivalent?
matches!(ty.kind(), ty::RawPtr(inner, _) if inner.has_trivial_sizedness(cx.tcx, SizedTraitKind::Sized))
if let ty::Adt(adt_def, _args) = ty.kind() | ||
&& adt_def.is_enum() | ||
&& adt_def.repr().inhibit_struct_field_reordering() | ||
{ | ||
adt_def.is_payloadfree() | ||
} else { | ||
false | ||
} |
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: I prefer the shorter equivalent form but it is a matter of taste.
if let ty::Adt(adt_def, _args) = ty.kind() | |
&& adt_def.is_enum() | |
&& adt_def.repr().inhibit_struct_field_reordering() | |
{ | |
adt_def.is_payloadfree() | |
} else { | |
false | |
} | |
ty.ty_adt_def().is_some_and(|adt_def| { | |
adt_def.is_enum() && adt_def.repr().inhibit_struct_field_reordering() && adt_def.is_payloadfree() | |
}) |
|
||
// We can't know about a generic type, so just let it pass to avoid noise | ||
fn is_generic<'tcx>(_cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { | ||
ty.flags().intersects(ty::TypeFlags::HAS_PARAM) |
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.
ty.flags()
will be removed at some point. Can't you use ty.has_param()
? Or ty.has_non_region_param()
if a generic lifetime wouldn't change the lint (along with a test with raw pointer on a type with a lifetime)?
Also, this function is so short that you could probably remove it and replace its call by its content.
// Raw pointers | ||
ty::RawPtr(innerty, _) => report_volatile_safe(cx, expr, *innerty), | ||
// std::ptr::NonNull | ||
ty::Adt(adt_def, args) if cx.tcx.is_diagnostic_item(sym::NonNull, adt_def.did()) => { |
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: we have a utility for this check in clippy_utils::ty
:
ty::Adt(adt_def, args) if cx.tcx.is_diagnostic_item(sym::NonNull, adt_def.did()) => { | |
_ if is_type_diagnostic_item(cx, self_ty, sym::NonNull) => { |
cx.tcx.get_diagnostic_name(def_id), | ||
Some(sym::ptr_read_volatile | sym::ptr_write_volatile) | ||
) | ||
&& let ty::RawPtr(ptrty, _) = cx.typeck_results().expr_ty(arg_ptr).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.
Shouldn't you use expr_ty_adjusted()
here? The lint currently doesn't catch:
let mut s = String::from("foo");
unsafe {
std::ptr::write_volatile(&mut s, String::from("bar"));
//~^ volatile_composites
}
while it probably should.
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.
Yes, good catch. I wasn't sure what an "adjustment" was for types.
@samueltardieu Thanks very much for your feedback. I'll address your comments shortly. This is the first time I've delved into clippy's internals so I was drawing on existing examples and guesswork for a lot of this change, so your pointers are much appreciated. |
// Look for method calls to `write_volatile`/`read_volatile`, which | ||
// apply to both raw pointers and std::ptr::NonNull. | ||
ExprKind::MethodCall(name, self_arg, _, _) | ||
if matches!(name.ident.name, sym::read_volatile | sym::write_volatile) => |
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.
@samueltardieu By the way, I don't really understand how (or if) this works - I have a test to validate it and it seems to work as expected. Does this really only match the real read/write_volatile
functions or could it match anything named read_volatile
?
Likewise below for ExprKind::Call
to the std/core::ptr::read/write_volatile
functions.
I addressed review comments in a separate commit, but I can fold them if it all looks OK. |
340ae34
to
ec9f7c0
Compare
Volatile reads and writes to non-primitive types are not well-defined, and can cause problems.
See #15529 for more details.
Thank you for making Clippy better!
We're collecting our changelog from pull request descriptions.
If your PR only includes internal changes, you can just write
changelog: none
. Otherwise, please write a short commentexplaining your change.
It's also helpful for us that the lint name is put within backticks (
` `
),and then encapsulated by square brackets (
[]
), for example:If your PR fixes an issue, you can add
fixes #issue_number
into thisPR description. This way the issue will be automatically closed when
your PR is merged.
If you added a new lint, here's a checklist for things that will be
checked during review or continuous integration.
.stderr
file)cargo test
passes locallycargo dev update_lints
cargo dev fmt
Note that you can skip the above if you are just opening a WIP PR in
order to get feedback.
Delete this line and everything above before opening your PR.
Note that we are currently not taking in new PRs that add new lints. We are in a
feature freeze. Check out the book for more information. If you open a
feature-adding pull request, its review will be delayed.
Please write a short comment explaining your change (or "none" for internal only changes)
changelog: [
volatile_composites
]: Lint when read/write_volatile is used on composite types(structs, arrays, etc) as their semantics are not well defined. Fixes #15529.
Summary Notes
Managed by
@rustbot
—see help for details