You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
non_send_fields_in_send_ty seems to be misguided #8045
The non_send_fields_in_send_ty lint wrongly states that implementations of Send are unsound because some fields are !Send.
If the fields were Send, there would be no need to write unsafe impl Send for S {}, as S would be automatically Send. The only time you need to explicitly implement Send for a struct is when some fields aren't Send. That is why the code needs to be marked unsafe.
Reacted by Mads Marquart, Sebastian Dröge, Bilal Elmoussaoui, Guillaume Gomez, Jacob Kiesel, Benjamin Saunders, Nicolas Abram, Bruno Kolenbrander, Yosh, Andrew Lamb and 12 more
And here's a reduced version of actual code which triggered the false positive for me.
use core::cell::UnsafeCell;use core::ptr::NonNull;pubstructS(UnsafeCell<NonNull<()>>);// Safety: S ensures that there is no aliasing even though// a pointer (NonNull) is used rather than a reference.unsafeimplSendforS{}
I am also confused as to what the purpose of this lint is supposed to be - the entire point of unsafe impls of Send is to implement Send for a type with !Send fields!
This was added in #7709, FWIW. But the explanation there is also not very helpful, and also the example is arguably half-wrong (it shows two unsound uses, but the one concering the Rc is completely sound if the refcount of the Rc is 1, i.e. it would depend on the context and other checks that are in place or not if there is a problem).
This is not about whether the lint is useful, but while reading 1.57 release notes, I noticed that the documentation for it is also wrong — it says
Sending the struct to another thread will transfer the ownership to the new thread by dropping in the current thread during the transfer.
which is not how sending works (sending is not observable / does not run code, let alone Drop). The original proposal wrote
Sending the struct to another thread and drops it there will also drop the field in the new thread.
which is a more correct (s/and drops/and then dropping/) description of the problem.
On the original question of whether there's any value at all, maybe this lint should be looking for fields which are !Send and implement Drop / have drop glue? That way, it would warn on things like Rc, which need extra caution, but not raw pointers, because they don't implement Drop. (I have no opinion on whether that's worthwhile; just brainstorming.)
On the original question of whether there's any value at all, maybe this lint should be looking for fields which are !Send and implement Drop / have drop glue? That way, it would warn on things like Rc, which need extra caution, but not raw pointers, because they don't implement Drop. (I have no opinion on whether that's worthwhile; just brainstorming.)
You can have a Rust struct wrapping an FFI struct that in turn contains a pointer. The Rust struct makes sure there is no aliasing so that it can be Send as it ensures the pointer is not aliased, but it needs Drop to free the pointer in the end.
but the one concering the Rc is completely sound if the refcount of the Rc is 1
Even if it is completely safe in that case, I think that it is correct to mark this as suspicious.
On the original question of whether there's any value at all, maybe this lint should be looking for fields which are !Send and implement Drop / have drop glue? That way, it would warn on things like Rc, which need extra caution, but not raw pointers, because they don't implement Drop. (I have no opinion on whether that's worthwhile; just brainstorming.)
Personally, I like the notion to only mark allow structs to be Send if all fields are also Send but that might be too restrictive in some applications. There is the enable-raw-pointer-heuristic-for-send configuration to allow the lint for raw pointers, we could maybe add a new configuration to have this more accepting linting behavior.
Personally, I like the notion to only mark allow structs to be Send if all fields are also Send
This is exactly what the automatic implementation of Send does already for all types.
but that might be too restrictive in some applications.
Which is when you use unsafe impl Send. All uses of unsafe impl Send are either intentionally bypassing the restriction, or are redundant with the auto implementation.
Reacted by Mads Marquart, Nicolas Abram, srrrse and Taylor Cramer
Thanks for bringing this false positive to the attention.
TL;DR (1) This lint has a rule that handles Copy types and raw pointers, but unfortunaetly NonNull was not included. I'll work on that as soon as possible (hopefully upload a PR by the end of tomorrow EST). (2) Many people being confused about the purpose of the lint indicate that the current lint description needs an improvement.
Let me start with the background (and excuse) for this lint. This lint is designed after actual soundness bugs we found during our ecosystem scale memory safety bugs finding work, Rudra. The original proposal #7703 included 4 representative bugs, but the lint was designed after reviewing more than a hundred of Send/Sync soundness bugs throughout the Rust ecosystem (the bugs with SV tag in the PoC repository).
This lint is most useful when writing a generic data structure. There are surprising amount of unsound Send impls in published Rust crates that do unsafe impl<T> Send for Wrapper<T> without T: Send bound, and this lint helps find and fix them. However, since the lint itself is not limited only to this scope, I tried to write the description generic and beginner-friendly, and unfortunaetly it seems that it did not work well. Many people being confused about the purpose of the lint indicate that the description needs an improvement.
Regarding false postives, it already has a rule to suppress false positives related to Copy types and pointer types. The lint comes with various test cases from real world bugs, and @xFrednet ran the lintcheck tool to make sure it doesn't cause false positives for the default list of crates. Unfortunately, I overlooked NonNull and didn't include it in the logic. I'll create a PR to address that as soon as possible, hopefully by the end of tomorrow in EST.
It would be extra helpful if anyone can share (1) types that are specifically designed !Send to encourage manual Send implementation (like raw pointers and NonNull) and (2) the rationale behind using UnsafeCell<NonNull<()>> instead of using NonNull<()> directly (direct use of NonNull is in fact allowed even in the current implementation).
Reacted by Mads Marquart, Fridtjof Stoldt, Antoine Martin, Jonas Platte, Nicolas Abram and Jorge Leitao
Given that clarification, I'd say the lint documentation has two problems:
Instead of “This lint has a heuristic to filter out false positives”, the documentation should be written to focus on the idea that it's distinguishing between likely unsound cases (such as impl<T> for Wrapper<T> as you mention) cases and likely sound cases (bare pointers), and warning about the former only. Without that context, it sounds like the lint is redundant with the fact that Send is already an unsafe trait.
"Sending the struct to another thread will transfer the ownership to the new thread by dropping in the current thread during the transfer." is completely incorrect; it is both false that dropping is a part of transferring ownership and false that dropping in the current thread is the problem. This should be fixed ASAP to avoid people learning false information about how sending works when they look up this lint.
Reacted by Mads Marquart, Yechan Bae, Jonas Platte, Nicolas Abram and Taylor Cramer
the rationale behind using UnsafeCell<NonNull<()>> instead of using NonNull<()> directly (direct use of NonNull is in fact allowed even in the current implementation).
Not exactly the same situation, but in gtk-rs we have helper structs around NonNull<T> that provide some additional functionality for the underlying raw pointers (including e.g. a Drop impl). These are never Send (or Sync), but the user-facing structs that are wrapping around them might be.
Currently this lint triggers for each of these user-facing structs that are actually Send because the lint does not know about our helper structs.
the rationale behind using UnsafeCell<NonNull<()>> instead of using NonNull<()> directly (direct use of NonNull is in fact allowed even in the current implementation).
I hit this in Rug. This is somehow similar to having a small vector which can be borrowed as a full vector, that is there is a way to get &Vec from &SmallVec. The small vector has two fields: one that mimics the Vec struct and one which is the data. The Vec itself contains a pointer to data, so the function that converts &SmallVec to &Vec has to update the pointer in its vector-mimic, so the pointer requires interior mutability, hence UnsafeCell. Of course, since the vector is borrowed it cannot be moved for as long as the &Vec exists so the pointer remains valid while &Vec exists.
Activity
tspiteri commentedon Nov 30, 2021
And here's a reduced version of actual code which triggered the false positive for me.
sfackler commentedon Dec 1, 2021
I am also confused as to what the purpose of this lint is supposed to be - the entire point of
unsafe impls ofSendis to implementSendfor a type with!Sendfields!GuillaumeGomez commentedon Dec 2, 2021
This is indeed very unhelpful...
sdroege commentedon Dec 2, 2021
This was added in #7709, FWIW. But the explanation there is also not very helpful, and also the example is arguably half-wrong (it shows two unsound uses, but the one concering the
Rcis completely sound if the refcount of theRcis 1, i.e. it would depend on the context and other checks that are in place or not if there is a problem).kpreid commentedon Dec 2, 2021
This is not about whether the lint is useful, but while reading 1.57 release notes, I noticed that the documentation for it is also wrong — it says
which is not how sending works (sending is not observable / does not run code, let alone
Drop). The original proposal wrotewhich is a more correct (s/and drops/and then dropping/) description of the problem.
On the original question of whether there's any value at all, maybe this lint should be looking for fields which are
!Sendand implementDrop/ have drop glue? That way, it would warn on things likeRc, which need extra caution, but not raw pointers, because they don't implementDrop. (I have no opinion on whether that's worthwhile; just brainstorming.)tspiteri commentedon Dec 2, 2021
You can have a Rust struct wrapping an FFI struct that in turn contains a pointer. The Rust struct makes sure there is no aliasing so that it can be
Sendas it ensures the pointer is not aliased, but it needsDropto free the pointer in the end.xFrednet commentedon Dec 2, 2021
Even if it is completely safe in that case, I think that it is correct to mark this as suspicious.
Personally, I like the notion to only mark allow structs to be
Sendif all fields are alsoSendbut that might be too restrictive in some applications. There is theenable-raw-pointer-heuristic-for-sendconfiguration to allow the lint for raw pointers, we could maybe add a new configuration to have this more accepting linting behavior.cc: @qwaz What are your thoughts on this? 🙃
kpreid commentedon Dec 2, 2021
This is exactly what the automatic implementation of
Senddoes already for all types.Which is when you use
unsafe impl Send. All uses ofunsafe impl Sendare either intentionally bypassing the restriction, or are redundant with the auto implementation.qwaz commentedon Dec 2, 2021
Thanks for bringing this false positive to the attention.
TL;DR (1) This lint has a rule that handles
Copytypes and raw pointers, but unfortunaetlyNonNullwas not included. I'll work on that as soon as possible (hopefully upload a PR by the end of tomorrow EST). (2) Many people being confused about the purpose of the lint indicate that the current lint description needs an improvement.Let me start with the background (and excuse) for this lint. This lint is designed after actual soundness bugs we found during our ecosystem scale memory safety bugs finding work, Rudra. The original proposal #7703 included 4 representative bugs, but the lint was designed after reviewing more than a hundred of Send/Sync soundness bugs throughout the Rust ecosystem (the bugs with SV tag in the PoC repository).
This lint is most useful when writing a generic data structure. There are surprising amount of unsound
Sendimpls in published Rust crates that dounsafe impl<T> Send for Wrapper<T>withoutT: Sendbound, and this lint helps find and fix them. However, since the lint itself is not limited only to this scope, I tried to write the description generic and beginner-friendly, and unfortunaetly it seems that it did not work well. Many people being confused about the purpose of the lint indicate that the description needs an improvement.Regarding false postives, it already has a rule to suppress false positives related to
Copytypes and pointer types. The lint comes with various test cases from real world bugs, and @xFrednet ran the lintcheck tool to make sure it doesn't cause false positives for the default list of crates. Unfortunately, I overlookedNonNulland didn't include it in the logic. I'll create a PR to address that as soon as possible, hopefully by the end of tomorrow in EST.It would be extra helpful if anyone can share (1) types that are specifically designed
!Sendto encourage manualSendimplementation (like raw pointers andNonNull) and (2) the rationale behind usingUnsafeCell<NonNull<()>>instead of usingNonNull<()>directly (direct use ofNonNullis in fact allowed even in the current implementation).kpreid commentedon Dec 2, 2021
Given that clarification, I'd say the lint documentation has two problems:
Instead of “This lint has a heuristic to filter out false positives”, the documentation should be written to focus on the idea that it's distinguishing between likely unsound cases (such as
impl<T> for Wrapper<T>as you mention) cases and likely sound cases (bare pointers), and warning about the former only. Without that context, it sounds like the lint is redundant with the fact thatSendis already anunsafe trait."Sending the struct to another thread will transfer the ownership to the new thread by dropping in the current thread during the transfer." is completely incorrect; it is both false that dropping is a part of transferring ownership and false that dropping in the current thread is the problem. This should be fixed ASAP to avoid people learning false information about how sending works when they look up this lint.
qwaz commentedon Dec 2, 2021
That description is indeed incorrect and I'll include that in the fix
sdroege commentedon Dec 3, 2021
Not exactly the same situation, but in gtk-rs we have helper structs around
NonNull<T>that provide some additional functionality for the underlying raw pointers (including e.g. aDropimpl). These are neverSend(orSync), but the user-facing structs that are wrapping around them might be.Currently this lint triggers for each of these user-facing structs that are actually
Sendbecause the lint does not know about our helper structs.tspiteri commentedon Dec 3, 2021
I hit this in Rug. This is somehow similar to having a small vector which can be borrowed as a full vector, that is there is a way to get
&Vecfrom&SmallVec. The small vector has two fields: one that mimics theVecstruct and one which is the data. TheVecitself contains a pointer to data, so the function that converts&SmallVecto&Vechas to update the pointer in its vector-mimic, so the pointer requires interior mutability, henceUnsafeCell. Of course, since the vector is borrowed it cannot be moved for as long as the&Vecexists so the pointer remains valid while&Vecexists.39 remaining items
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…
rust: sync: remove unneeded `#[allow(clippy::non_send_fields_in_send_…