Skip to content

non_send_fields_in_send_ty seems to be misguided #8045

Open

Description

@tspiteri

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.

Activity

  1. added
    C-bugCategory: Clippy is not doing the correct thing
    I-false-positiveIssue: The lint was triggered on code it shouldn't have
    on Nov 30, 2021
  2. tspiteri commented on Nov 30, 2021

    @tspiteri
    ContributorAuthor

    And here's a reduced version of actual code which triggered the false positive for me.

    use core::cell::UnsafeCell;
    use core::ptr::NonNull;
    pub struct S(UnsafeCell<NonNull<()>>);
    // Safety: S ensures that there is no aliasing even though
    // a pointer (NonNull) is used rather than a reference.
    unsafe impl Send for S {}
  3. sfackler commented on Dec 1, 2021

    @sfackler
    Member

    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!

  4. GuillaumeGomez commented on Dec 2, 2021

    @GuillaumeGomez
    Member

    This is indeed very unhelpful...

  5. sdroege commented on Dec 2, 2021

    @sdroege

    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).

  6. kpreid commented on Dec 2, 2021

    @kpreid
    Contributor

    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.)

  7. tspiteri commented on Dec 2, 2021

    @tspiteri
    ContributorAuthor

    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.

  8. xFrednet commented on Dec 2, 2021

    @xFrednet
    Contributor

    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.


    cc: @qwaz What are your thoughts on this? 🙃

  9. kpreid commented on Dec 2, 2021

    @kpreid
    Contributor

    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.

  10. qwaz commented on Dec 2, 2021

    @qwaz
    Contributor

    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).

  11. kpreid commented on Dec 2, 2021

    @kpreid
    Contributor

    Given that clarification, I'd say the lint documentation has two problems:

    1. 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.

    2. "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.

  12. qwaz commented on Dec 2, 2021

    @qwaz
    Contributor

    That description is indeed incorrect and I'll include that in the fix

  13. sdroege commented on Dec 3, 2021

    @sdroege

    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.

  14. tspiteri commented on Dec 3, 2021

    @tspiteri
    ContributorAuthor

    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.

  15. 39 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: Clippy is not doing the correct thingI-false-positiveIssue: The lint was triggered on code it shouldn't haveL-nurseryLint: Currently in the nursery group

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions