-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
lint / ImproperCTypes: better handling of indirections, take 2 #134697
base: master
Are you sure you want to change the base?
Conversation
( |
ah, and before I forget: a small part of the new test file is commented out because it hits ICE #134587, but there should be more than decent coverage anyway |
unfortunately the lint needs to be gutted and rewritten. |
Also while I was possibly having a mild case of get-there-itis and thus mostly tried to just make sure things were coherent, I would prefer all new code for the lint be in |
The version cut happened so there will be less time pressure now. |
I have a first version for you probably have things to say about its architecture, even if the whole thing still have a bunch of TODO comments
If you want to take a look in this state, should I just commit it here? (possibly put the PR in draft mode while I'm at it?) |
☔ The latest upstream changes (presumably #135525) made this pull request unmergeable. Please resolve the merge conflicts. |
5764b36
to
6c19cff
Compare
aaaand I think I have something that's "first draft" material! (should I put this pull in the "draft" state?) here's a list of some of my remaining questions and concerns:
|
This comment has been minimized.
This comment has been minimized.
☔ The latest upstream changes (presumably #135921) made this pull request unmergeable. Please resolve the merge conflicts. |
…sts] This reverts commit 1fcbb1e.
[commit does not pass tests]
[does not pass tests]
- removed special-case logic for a few cases (including the unit type, which is now only checked for in one place) - moved a lot of type-checking code in their dedicated visit_* methods - reworked FfiResult type to handle multiple diagnostics per type (currently imperfect due to type caching) - reworked the messages around CStr and CString
…ed from non-rust code
6c19cff
to
13720e7
Compare
The job Click to see the possible cause of the failure (guessed by this bot)
|
hmm. |
Yes, it's a good marker for "I don't want this merged yet, even if it looks done". |
It probably is.
Yes, but in particular, not to just repartition them between: I think breaking them into as many conceptually-smaller lints as possible is good, as long as each one is a distinct idea (no splitting just for the sake of splitting!).
I'm not sure what you mean?
We must allow Rust code to declare a pointer in a C signature to be
Yes, probably. |
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.
some initial nits on a first pass
// match (self, other) { | ||
// (Self::FfiUnsafe(_), _) => { | ||
// // nothing to do | ||
// }, | ||
// (_, Self::FfiUnsafe(_)) => { | ||
// *self = other; | ||
// }, | ||
// (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { | ||
// println!("whoops, both FfiPhantom: self({:?}) += other({:?})", ty1, ty2); | ||
// }, | ||
// (Self::FfiSafe,Self::FfiPhantom(_)) => { | ||
// *self = other; | ||
// }, | ||
// (_, Self::FfiSafe) => { | ||
// // nothing to do | ||
// }, | ||
// } | ||
|
||
let s_disc = std::mem::discriminant(self); | ||
let o_disc = std::mem::discriminant(&other); | ||
if s_disc == o_disc { | ||
match (self, &mut other) { | ||
(Self::FfiUnsafe(ref mut s_inner), Self::FfiUnsafe(ref mut o_inner)) => { | ||
s_inner.append(o_inner); | ||
} | ||
(Self::FfiPhantom(ref ty1), Self::FfiPhantom(ty2)) => { | ||
debug!("whoops: both FfiPhantom, self({:?}) += other({:?})", ty1, ty2); | ||
} | ||
(Self::FfiSafe, Self::FfiSafe) => {} | ||
_ => unreachable!(), | ||
} | ||
} else { | ||
if let Self::FfiUnsafe(_) = self { | ||
return; | ||
} | ||
match other { | ||
Self::FfiUnsafe(o_inner) => { | ||
// self is Safe or Phantom: Unsafe wins | ||
*self = Self::FfiUnsafe(o_inner); | ||
} | ||
Self::FfiSafe => { | ||
// self is always "wins" | ||
return; | ||
} | ||
Self::FfiPhantom(o_inner) => { | ||
// self is Safe: Phantom wins | ||
*self = Self::FfiPhantom(o_inner); | ||
} | ||
} | ||
} |
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.
The following compiles:
// match (self, other) { | |
// (Self::FfiUnsafe(_), _) => { | |
// // nothing to do | |
// }, | |
// (_, Self::FfiUnsafe(_)) => { | |
// *self = other; | |
// }, | |
// (Self::FfiPhantom(ref ty1),Self::FfiPhantom(ty2)) => { | |
// println!("whoops, both FfiPhantom: self({:?}) += other({:?})", ty1, ty2); | |
// }, | |
// (Self::FfiSafe,Self::FfiPhantom(_)) => { | |
// *self = other; | |
// }, | |
// (_, Self::FfiSafe) => { | |
// // nothing to do | |
// }, | |
// } | |
let s_disc = std::mem::discriminant(self); | |
let o_disc = std::mem::discriminant(&other); | |
if s_disc == o_disc { | |
match (self, &mut other) { | |
(Self::FfiUnsafe(ref mut s_inner), Self::FfiUnsafe(ref mut o_inner)) => { | |
s_inner.append(o_inner); | |
} | |
(Self::FfiPhantom(ref ty1), Self::FfiPhantom(ty2)) => { | |
debug!("whoops: both FfiPhantom, self({:?}) += other({:?})", ty1, ty2); | |
} | |
(Self::FfiSafe, Self::FfiSafe) => {} | |
_ => unreachable!(), | |
} | |
} else { | |
if let Self::FfiUnsafe(_) = self { | |
return; | |
} | |
match other { | |
Self::FfiUnsafe(o_inner) => { | |
// self is Safe or Phantom: Unsafe wins | |
*self = Self::FfiUnsafe(o_inner); | |
} | |
Self::FfiSafe => { | |
// self is always "wins" | |
return; | |
} | |
Self::FfiPhantom(o_inner) => { | |
// self is Safe: Phantom wins | |
*self = Self::FfiPhantom(o_inner); | |
} | |
} | |
} | |
match (self, other) { | |
(Self::FfiUnsafe(_), _) => { | |
// nothing to do | |
} | |
(myself, other @ Self::FfiUnsafe(_)) => { | |
*myself = other; | |
} | |
(Self::FfiPhantom(ref ty1), Self::FfiPhantom(ty2)) => { | |
println!("whoops, both FfiPhantom: self({:?}) += other({:?})", ty1, ty2); | |
} | |
(myself @ Self::FfiSafe, other @ Self::FfiPhantom(_)) => { | |
*myself = other; | |
} | |
(_, Self::FfiSafe) => { | |
// nothing to do | |
} | |
} |
} | ||
|
||
impl CTypesVisitorState { | ||
/// wether the type is used (directly or not) in a static variable |
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.
/// wether the type is used (directly or not) in a static variable | |
/// whether the type is used (directly or not) in a static variable |
fn is_in_static(self) -> bool { | ||
((self as u8) & 0b0010) != 0 | ||
} | ||
/// wether the type is used (directly or not) in a function, in return position |
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.
/// wether the type is used (directly or not) in a function, in return position | |
/// whether the type is used (directly or not) in a function, in return position |
/// wether the type is used (directly or not) in a defined function | ||
/// in other words, wether or not we allow non-FFI-safe types behind a C pointer, |
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.
/// wether the type is used (directly or not) in a defined function | |
/// in other words, wether or not we allow non-FFI-safe types behind a C pointer, | |
/// whether the type is used (directly or not) in a defined function | |
/// in other words, whether or not we allow non-FFI-safe types behind a C pointer, |
ret | ||
} | ||
|
||
/// wether the value for that type might come from the non-rust side of a FFI boundary |
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.
/// wether the value for that type might come from the non-rust side of a FFI boundary | |
/// whether the value for that type might come from the non-rust side of a FFI boundary |
// but for some reason one can just go and write function *pointers* like that: | ||
// `type Foo = extern "C" fn(::std::ffi::CStr);` |
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.
- Because unsized function parameters are something we may want to support.
- The code may not be well-formed: as you may have noticed at some point, you get warnings even if you get errors (usually), and this is because we lint even on "bad" code. This is because rustc didn't use to, once upon a time, and it was a bad debugging experience.
// you would think that int-range pattern types that exclude 0 would have Option layout optimisation | ||
// they don't (see tests/ui/type/pattern_types/range_patterns.stderr) | ||
// so there's no need to allow Option<pattern_type!(u32 in 1..)>. |
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.
oh, I should fix that probably
|
||
type Sig<'tcx> = Binder<TyCtxt<'tcx>, FnSig<TyCtxt<'tcx>>>; | ||
|
||
/// for a given `extern "ABI"`, tell wether that ABI is *not* considered a FFI boundary |
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.
/// for a given `extern "ABI"`, tell wether that ABI is *not* considered a FFI boundary | |
/// for a given `extern "ABI"`, tell whether that ABI is *not* considered a FFI boundary |
} | ||
} | ||
|
||
/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it |
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.
/// Determine if a type is sized or not, and wether it affects references/pointers/boxes to it | |
/// Determine if a type is sized or not, and whether it affects references/pointers/boxes to it |
impl CTypesVisitorState { | ||
/// wether the type is used (directly or not) in a static variable | ||
fn is_in_static(self) -> bool { | ||
((self as u8) & 0b0010) != 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.
((self as u8) & 0b0010) != 0 | |
((self as u8) & (Self::StaticTy as u8) != 0 |
} | ||
/// wether the type is used (directly or not) in a function, in return position | ||
fn is_in_function_return(self) -> bool { | ||
let ret = ((self as u8) & 0b0100) != 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.
let ret = ((self as u8) & 0b0100) != 0; | |
let ret = ((self as u8) & (Self::ReturnTyInDeclaration as u8)) != 0; |
/// in other words, wether or not we allow non-FFI-safe types behind a C pointer, | ||
/// to be treated as an opaque type on the other side of the FFI boundary | ||
fn is_in_defined_function(self) -> bool { | ||
let ret = ((self as u8) & 0b1000) != 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.
etc... please encase the magic numbers.
} | ||
|
||
impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { | ||
/// Checks wether an `extern "ABI" fn` function pointer is indeed FFI-safe to call |
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.
/// Checks wether an `extern "ABI" fn` function pointer is indeed FFI-safe to call | |
/// Checks whether an `extern "ABI" fn` function pointer is indeed FFI-safe to call |
// 'fake' declarations (in traits, needed to be implemented elsewhere), and definitions. | ||
// (for instance, definitions should worry about &self with Self:?Sized, but fake declarations shouldn't) | ||
|
||
// wether they are FFI-safe or not does not depend on the indirections involved (&Self, &T, Box<impl Trait>), |
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.
Simpler spellcheckers cannot catch this because "wether" is in fact a word (though definitely not the one you meant), and more complex ones are infuriating because they are never quite smart enough to understand when you meant to use a word that way.
// wether they are FFI-safe or not does not depend on the indirections involved (&Self, &T, Box<impl Trait>), | |
// whether they are FFI-safe or not does not depend on the indirections involved (&Self, &T, Box<impl Trait>), |
if def.variants().is_empty() { | ||
// Empty enums are implicitely handled as the never type: | ||
// FIXME think about the FFI-safety of functions that use that | ||
return FfiSafe; | ||
} |
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.
!
is never a valid argument, but it is a valid return value.
alright, sorry for taking a while! I'm currently planning what changes I'll do in terms of splitting the lint(s)
well, this is more or less answered in what I said before that, but my question was about how to deal with "switching" from checking arguments for, say, a function definition, to checking the arguments of a FnPtr argument?
I... maybe? I can't for the life of me find the link to that again but I think I saw a discussion about that and type covariance/contravariance, Though you'll definitely know more than me on all the moving parts. As for the rest of your advice, I already took all this in! |
honestly, I think that initially we might be better off just cutting out linting on thin pointers based on their pointees. then we can spend a bit of time rethinking the justification for the lint and rewriting the lint for that case, I think, and having it be specifically a lint for an |
this sounds like we might have combinatorics on our hands with that. (one lint for repr(C) structures, one for pointees within, one for extern function definitions, one for pointees in their arguments, etc.) |
Hm... I think it's possible to have a case where a lint fires only when two lints are enabled. |
...Regardless, I think any reworking should start with scaling as far as possible back to "this is definitely inappropriate" versus "this is trying to catch a pattern that can be used correctly but usually isn't". And I think that anything that assumes that a C-ABI-compatible pointer with an ABI-incompatible pointee is not merely being used opaquely is an example of the latter, of course. |
This PR tries to re-add the changes in #131669 (which were reverted in #134064 after one (1) nightly),
and adds better coverage of ty_kinds:
The changes in the original PR aim to make ImproperCTypes/ImproperCTypesDefinitions produce better warnings when dealing with indirections (Box, &T, *T), especially for those to DSTs.
r? workingjubilee (because you reviewed the first attempt)