Skip to content
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

[no_mangle_with_rust_abi]: Check for statics with a non #[repr(Rust)] type #11253

Closed
wants to merge 3 commits into from

Conversation

Centri3
Copy link
Member

@Centri3 Centri3 commented Jul 29, 2023

Fixes #11219

changelog: [no_mangle_with_rust_abi]: Check for statics with an unstable ABI

@rustbot
Copy link
Collaborator

rustbot commented Jul 29, 2023

r? @Alexendoo

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jul 29, 2023
@Alexendoo
Copy link
Member

We could add a check for (non unit) tuples as well as ADTs

@Centri3
Copy link
Member Author

Centri3 commented Aug 1, 2023

iirc slices (&[T], not [T]) also have an undefined layout too, so we can maybe lint that as well? (Unless that's incorrect, I'm not sure)

@bors
Copy link
Contributor

bors commented Aug 11, 2023

☔ The latest upstream changes (presumably #11239) made this pull request unmergeable. Please resolve the merge conflicts.

@asquared31415
Copy link
Contributor

I think that the current implementation is correct given a conservative approach to the current unsafe guarantees, but I believe that there are wishes to make more of repr(Rust) defined. If that happens, this lint may need to be updated to be less restrictive.

@Alexendoo
Copy link
Member

Yeah sounds good to me, we can adapt this if future guarantees are ever added

r=me with rebase/squash

@Alexendoo
Copy link
Member

Oh @asquared31415's comment reminded me of something, Option<&T> and such

@Centri3
Copy link
Member Author

Centri3 commented Aug 14, 2023

iirc Option<&T> still doesn't have a stable ABI, even if the underlying type is (which, in this case, has a stable ABI). The discriminant can be whatever it wants so I think it should still lint it.

(Unless you meant something other than not linting it, not sure)

@Centri3
Copy link
Member Author

Centri3 commented Aug 14, 2023

Will rebase later today

@Alexendoo
Copy link
Member

https://doc.rust-lang.org/std/primitive.reference.html says

In fact, Option<&T> has the same memory representation as a nullable but aligned pointer, and can be passed across FFI boundaries as such.

@asquared31415
Copy link
Contributor

This is a complete list of the types that have well defined layouts inside Option: https://doc.rust-lang.org/std/option/index.html#representation

@Centri3
Copy link
Member Author

Centri3 commented Aug 15, 2023

doc.rust-lang.org/std/primitive.reference.html says

In fact, Option<&T> has the same memory representation as a nullable but aligned pointer, and can be passed across FFI boundaries as such.

Odd, the type layout section of the reference should really mention that

While Option<&T> is the same as &T, the underlying type must be repr(C) for it to be FFI safe. I mean, you can pass it around, but you can't easily give it to an external function expecting it. So we'll need to probe a little deeper into Option types to see if T is repr(Rust)

@asquared31415
Copy link
Contributor

This does not need to do any inspection of the type behind one of the pointer-like types because Option<&T> is as FFI-safe as a raw pointer. We are not be linting on *const NotFFISafe, because the inner type of a pointer (when it's Sized) does not affect the ABI, So none of the pointer-like Options need inspection besides "is an Option of one of these pointer types"
(for T: Sized):

  • Option<Box<T>>
  • Option<&T>
  • Option<&mut T>
  • Option<fn> (with or without an extern ABI)
  • Option<ptr::NonNull<T>>

The only remaining case that has guaranteed layout is the Option<NonZeroX> types, which are represented as plain integers (and don't have types to inspect further, like references).

@Centri3
Copy link
Member Author

Centri3 commented Aug 15, 2023

Option<&T> is as FFI-safe as a raw pointer.

Yes, the Option<&...> part is safe, but the T is absolutely not. I was not referring to Option<&...> in specific; that's FFI-safe. The underlying type isn't however, and because of that it's basically useless.

You can pass it around safely, but accessing the inner data on its own won't work. Unless all fields are private doing that is a terrible idea.

We are not be linting on *const NotFFISafe

And we probably should with #[no_mangle].

As I said, you can pass the pointer/reference around care-free, but you can't read from it in an external function. You can pass it to a function defined in the crate that defines NotFFISafe, that returns a mutable reference to that field. So it's better than nothing, but not as useful as a repr(C) type.

I don't see any reason to assume the author has gone through that effort if there are public fields. They're likely to just do a null check, then read it and cause a multitude of issues.

(If there are public fields, that's a major footgun anyway for rust code in a cdylib.)

Code in an external language though would be weird because the concept of visibility doesn't carry over, and would need to redefine the type. They're 100% free to read the fields then anyway, even though this wouldn't lint

@asquared31415
Copy link
Contributor

In my experience the "opaque pointer" pattern is fairly common. And while we could say "use a FFI-safe type", that won't satisfy most people. In fact we even have an exception for *const T such that it does not trigger the FFI safety lint in rustc for similar reasons. Especially *const () which is suggested as a opaque pointer type.

@asquared31415
Copy link
Contributor

example of what does and does not lint: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3f5774c16df2d4ef9c564d97fbf2138d

Notice that pointers never lint, even when their pointee does.

@Centri3
Copy link
Member Author

Centri3 commented Aug 16, 2023

There isn't any issue with using repr(C) on an opaque type. The nomicon even suggests it in fact (is it Rustonomicon or the nomicon?), I'd say you should since it immediately represents it comes from/is sent to C. It's definitely pedantic, since it's never constructed (only theoretically pointed to by a pointer, but it's casted before it's read anyway), but clippy can be more pedantic than rustc by default.

I think it makes sense to not lint this stuff in rustc. It can be correct, but it most of the time isn't (or could be written better). imo, in clippy it'd make sense to lint this since either the code is incorrect or the purpose of the struct can be stated better (both ways are solved by adding repr(C).

Especially *const () which is suggested as a opaque pointer type.

We already don't lint *const () or ().

bors added a commit to rust-lang-ci/rust that referenced this pull request Aug 25, 2023
…Lapkin

Allow explicit `#[repr(Rust)]`

This is identical to no `repr()` at all. For `Rust, packed` and `Rust, align(x)`, it should be the same as no `Rust` at all (as, afaik, `#[repr(align(16))]` uses the Rust ABI.)

The main use case for this is being able to explicitly say "I want to use the Rust ABI" in very very rare circumstances where the first obvious choice would be the C ABI yet is undesirable, which is already possible with functions as `extern "Rust"`. This would be useful for silencing rust-lang/rust-clippy#11253. It's also more consistent with `extern`.

The lack of this also tripped me up a bit when I was new to Rust, as I expected this to be possible.
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Aug 26, 2023
Allow explicit `#[repr(Rust)]`

This is identical to no `repr()` at all. For `Rust, packed` and `Rust, align(x)`, it should be the same as no `Rust` at all (as, afaik, `#[repr(align(16))]` uses the Rust ABI.)

The main use case for this is being able to explicitly say "I want to use the Rust ABI" in very very rare circumstances where the first obvious choice would be the C ABI yet is undesirable, which is already possible with functions as `extern "Rust"`. This would be useful for silencing rust-lang/rust-clippy#11253. It's also more consistent with `extern`.

The lack of this also tripped me up a bit when I was new to Rust, as I expected this to be possible.
@Alexendoo
Copy link
Member

I think aligning with improper_ctypes would be the best place to start, the pointer doesn't have to be opaque on the rust side to be opaque to C

@Centri3
Copy link
Member Author

Centri3 commented Sep 1, 2023

For now, yes. I'll update this soon to do so. However, in the future we should have an off-by-default configuration option to make it a bit more pedantic.

@xFrednet
Copy link
Member

Hey this is triage, I'm closing this due to inactivity. Currently, @Centri3 sadly doesn't have the time to continue this implementation. If anyone is interested in continuing this PR, you're more than welcome to create a new PR and push it over the finish line. :D

Thank you to @Centri3 and the reviewers for the time, that you already put into this!

@rustbot label +S-inactive-closed -S-waiting-on-author -S-waiting-on-review

@xFrednet xFrednet closed this Mar 31, 2024
@rustbot rustbot added S-inactive-closed Status: Closed due to inactivity and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Mar 31, 2024
@Centri3 Centri3 deleted the #11219 branch April 1, 2024 16:45
@Centri3
Copy link
Member Author

Centri3 commented Apr 1, 2024

Triaging my old PRs in case someone wants to pick them up, this needs a tiny bit of work. Mostly around opaque pointers, but also I left a TODO for when repr(Rust) is explicitly allowed - it now is, so we should handle it.

In terms of opaque pointers, tbh I don't think *const T will show up enough for it to matter. By that I mean, we can just ignore it like the other lint does.

I feel like another lint is still in order for this case since opaque pointers feel... weird, considering you can always define another unit struct. Generally rust prefers explicit over implicit, does it not? So I think that makes sense.

I will also finish this at some point if nobody else does, since I do quite like this lint.

lnicola pushed a commit to lnicola/rust-analyzer that referenced this pull request Apr 7, 2024
Allow explicit `#[repr(Rust)]`

This is identical to no `repr()` at all. For `Rust, packed` and `Rust, align(x)`, it should be the same as no `Rust` at all (as, afaik, `#[repr(align(16))]` uses the Rust ABI.)

The main use case for this is being able to explicitly say "I want to use the Rust ABI" in very very rare circumstances where the first obvious choice would be the C ABI yet is undesirable, which is already possible with functions as `extern "Rust"`. This would be useful for silencing rust-lang/rust-clippy#11253. It's also more consistent with `extern`.

The lack of this also tripped me up a bit when I was new to Rust, as I expected this to be possible.
RalfJung pushed a commit to RalfJung/rust-analyzer that referenced this pull request Apr 27, 2024
Allow explicit `#[repr(Rust)]`

This is identical to no `repr()` at all. For `Rust, packed` and `Rust, align(x)`, it should be the same as no `Rust` at all (as, afaik, `#[repr(align(16))]` uses the Rust ABI.)

The main use case for this is being able to explicitly say "I want to use the Rust ABI" in very very rare circumstances where the first obvious choice would be the C ABI yet is undesirable, which is already possible with functions as `extern "Rust"`. This would be useful for silencing rust-lang/rust-clippy#11253. It's also more consistent with `extern`.

The lack of this also tripped me up a bit when I was new to Rust, as I expected this to be possible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive-closed Status: Closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lint against #[no_mangle] for non-repr(C) pub statics
7 participants