Skip to content

improper_ctypes fires for &mut T, &T, *const T and *mut T (when T: Sized) #66220

Closed

Description

@CryZe

I'm not sure if this is intentional, but I haven't found any discussion regarding this. The improper_ctypes lint fires on pointer types when T is not FFI safe. However pointers have defined layout that is always FFI safe, independent of what the pointer is pointing to. It certainly seems valid to point out the pointer won't be able to be dereferenced by C. This should probably be a clippy lint though. Rust itself shouldn't fire false positives like this.

Activity

  1. sfackler commented on Nov 8, 2019

    @sfackler
    Member

    Is T: Sized?

  2. CryZe commented on Nov 8, 2019

    @CryZe
    ContributorAuthor

    Yes, here's the playground example

    pub struct Foo {
        _x: i32,
    }
    
    #[no_mangle]
    pub extern "C" fn foo(_: *const Foo) {}
    warning: `extern` fn uses type `Foo`, which is not FFI-safe
     --> src/lib.rs:6:26
      |
    6 | pub extern "C" fn foo(_: *const Foo) {}
      |                          ^^^^^^^^^^ not FFI-safe
      |
      = note: `#[warn(improper_ctypes)]` on by default
      = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
      = note: this struct has unspecified layout
    note: type defined here
     --> src/lib.rs:1:1
      |
    1 | / pub struct Foo {
    2 | |     _x: i32,
    3 | | }
      | |_^
    
  3. hanna-kruppe commented on Nov 8, 2019

    @hanna-kruppe
    Contributor

    I am pretty sure this is intentional (or was, when this part of the lint was implemented), but that doesn't mean it can't change. This is a scenario where reasonable interpretations of the lint disagree with each other:

    • On the one hand, pointers to sized types are like C pointers, so they have defined memory layout and call ABI. If the intent of the code is that the other side of the FFI just passes the pointer around, then everything is fine and the warning is a false positive.
    • On the other hand, the pointee type is not FFI-safe and code behind the FFI cannot interact with that type correctly. If the intent is that the other side of the FFI dereference the pointer, then most likely the author of the Rust side made a mistake (e.g., missed a repr(C) annotation) and the warning is a true positive.

    It is not obvious to me which side the improper_ctypes lint is supposed to serve. It's true that rustc lints ought to err on the side of fewer false positives, but on the other hand there are a lot of potential bugs (and very subtle ones, as things may appear to work correctly) that the current behavior catches, and false positives can be avoided by using *mut c_void or pointers to extern types for APIs where the pointee type is supposed to be opaque for the other side of the FFI.

  4. CryZe commented on Nov 8, 2019

    @CryZe
    ContributorAuthor

    I believe this could possibly be a separate lint (and with a different message too Pointer can't be dereferenced by C or so). Because otherwise improper_ctypes is likely to be disabled module or crate wide (as seen in quiche for example), which means actual calling convention mistakes won't show up anymore.

  5. hanna-kruppe commented on Nov 8, 2019

    @hanna-kruppe
    Contributor

    Splitting up into two different lints seems interesting (as long as both stay in rustc and enabled by default).

    It comes with an interesting backwards compatibility question, though. We probably wouldn't want projects that used allow(improper_ctypes) to see warnings they opted out of again just because we shuffled around where they're emitted. So would we turn improper_ctypes into a lint group encompassing both new lints? Of course, that means that any old code (and any new code that copy-pastes or is written by someone with ingrained habits) will automatically disable both lints at once even if it would benefit from the "ABI is unspecified" subset.

  6. Rantanen commented on Nov 8, 2019

    @Rantanen
    Contributor

    Is there any way to mark a type (instead of the method) as "I know this is improper but don't warn me about it"?

    #[repr(transparent)]
    #[allow(improper_ctypes)]
    pub struct Opaque<T>(T);
    
    pub extern "C" fn foo(_: *const Opaque<String>) {}

    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=ccd4a6a4c205c17d84e17eb6d1ceac4f

    The following does work, which surprised me because PhantomData alone is not FFI safe and this uses a tuple struct with two fields and claims to be repr(transparent) which I thought would hate structs with two fields, even if one of them was PhantomData...

    #[repr(transparent)]
    pub struct OpaquePtr<T>(*mut std::ffi::c_void, std::marker::PhantomData<T>);
    pub extern "C" fn bar(_: OpaquePtr<String>) {}

    I'm just looking for ways to describe the "FFI just passes the pointer around" intent in the type system. I realize the use of this might be noisy at scale given there's no automatic conversion between the types and would need manual wrapping and unwrapping.

  7. CryZe commented on Nov 8, 2019

    @CryZe
    ContributorAuthor

    Well idea would be to mark it with #[allow(opaque_c_ptr)] or so. Just passing *const String should be entirely fine (as long as you truly treat it as opaque).

  8. hanna-kruppe commented on Nov 8, 2019

    @hanna-kruppe
    Contributor

    Is there any way to mark a type (instead of the method) as "I know this is improper but don't warn me about it"?

    I believe this doesn't work because the lint message is associated with the function rather than the type(s) which the lint finds objectionable. I don't know how difficult it would be to support this, I guess it would be something of a special case at best.

    The following does work, which surprised me because PhantomData alone is not FFI safe and this uses a tuple struct with two fields and claims to be repr(transparent) which I thought would hate structs with two fields, even if one of them was PhantomData...

    repr(transparent) explicitly allows extra fields that have size zero and alignment one, to enable the use of PhantomData and similar tricks for more type safety in FFI interfaces. PhantomData alone is not FFI-safe because it's a ZST which is not a standard C construct and it's not clear (or wasn't when the lint was written) to what degree we support the GNU extensions that introduce ZSTs to C. *const PhantomData<String> works, though it's a bit obscure, so your OpaquePtr<T> newtype seems best to me.

  9. added
    A-FFIArea: Foreign function interface (FFI)
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on Nov 9, 2019
  10. SimonSapin commented on Nov 13, 2019

    @SimonSapin
    Contributor

    #65134 adds thousands of new warnings to Servo. The pervasive pattern is that we have many unsafe extern fn functions that take *const Foo parameters. The functions are extern so that they can be called from C++. The Foo Rust type is not FFI-safe, but in this case the pointer to it is FFI-safe because it is an opaque pointer as far as C++ code is concerned.

    Changing the parameter type to *const c_void and adding an as cast near the top of the function body “fixes” the warning, but I feel it’s really not an improvement. Type-safety is diminished for no benefit that I can see.

    Now I’m tempted to add #![allow(improper_ctypes)] to the parts of the crate that do the most FFI in order to unblock the compiler upgrade. But that feels very unproductive.

    Please consider reverting #65134, or making raw pointers unconditionally FFI-safe.

    CC @rust-lang/lang

  11. shepmaster commented on Nov 13, 2019

    @shepmaster
    Member

    It comes with an interesting backwards compatibility question, though

    Does it really? It’s nightly-only still, isn’t it? That level of churn (in the small timeframe present) seems acceptable in nightly.

  12. 23 remaining items

  13. hanna-kruppe commented on Nov 22, 2019

    @hanna-kruppe
    Contributor

    FWIW, personally I have come to believe that we should definitely split up the lint (with or without the other option) since it's useful independently of this specific issue, but I don't think I can commit the time to explore this further or write up a more detailed proposal any time soon.

    (If I get pinged to discuss that in Discord or Zulip, I probably won't be able to resist being nerd-sniped. Please use this knowledge responsibly :P)

  14. davidtwco commented on Nov 22, 2019

    @davidtwco
    Member

    I’m happy to perform any implementation required here, but I don’t have any particular opinions on the semantics - I just spotted #19834 as work needing done.

    I’m happy to proceed with making the lint behave differently (for this case) depending on extern “C” fn or extern “C” {}; or with a separate lint if someone has opinions on what each lint would do.

  15. pnkfelix commented on Nov 28, 2019

    @pnkfelix
    Contributor

    triage: since PR #66378 has landed, I am reclassifying this as P-medium.

  16. added and removed
    P-highHigh priority
    on Nov 28, 2019
  17. pnkfelix commented on Dec 5, 2019

    @pnkfelix
    Contributor

    removing nomination label. T-compiler does not need to discuss this, at least not until the follow-up work is done.

  18. added a commit that references this issue on Jun 23, 2020
  19. added 2 commits that reference this issue on Jun 25, 2020
  20. davidtwco commented on Jul 2, 2020

    @davidtwco
    Member

    Closing, #72700 fixes this.

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

    A-FFIArea: Foreign function interface (FFI)A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.C-enhancementCategory: An issue proposing an enhancement or a PR with one.P-mediumMedium priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions