-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
New lint: _.as_ptr() as *mut _
is Undefined Behavior
#4774
Comments
This is basically covered by #4771 and the discussion there |
I think this would be better phrased as a lint for casting something when there's a method that just gives you what you want. The cast here isn't UB, but I agree that you shouldn't be doing |
#4771 would be allow-by-default while this is more specific and can be deny-by-default |
Wow, Rust standard library itself had this bug: https://blog.rust-lang.org/2017/02/09/Rust-1.15.1.html Also turns out this might not be instant UB depending on how the underlying slice was obtained, but it's still a really bad idea. I'll update the description with this info. |
This is not entirely correct... something like Oh, looks like you found that yourself already. Might be good to update the initial paragraph then. ;) |
FWIW I've also opened a request for such lint in the compiler, but the resolution was "start out the lint in Clippy and then uplift it based on how the implementation looks". |
To be perfectly clear: What is UB is using a pointer whose provenance is a shared In fact In conclusion, it would definitely be correct to warn-by-default use of This might be able to generically apply to any |
Shocking that it hasn't been said yet: C FFI often needs to accept a |
Notice that
Where does a const-to-mut cast occur here? Is it when Rust code calls C code? But then one could also choose the C function signature in Rust to use |
You can PR |
Sure, if they choose a certain coding stale that might mean more lints that show up for them. I am just saying that there's nothing fundamental about FFI here. |
Do I get that right? We want to lint sequences of casts (with no other expressions in between) where the original expression is an immutable ref or ptr and the result is a mutable ref or ptr? |
*const T as *mut T
is Undefined Behavior_.as_ptr() as *mut _
is Undefined Behavior
If the original expression is an immutable ref or ptr, what you don't want is ending up in a mutable ref. AFAICT a mutable pointer is fine, and kind of required if you want to store a pointer derived from an immutable ref as a |
@gnzlbg thank you, then I will only lint casts resulting in |
@llogiq you probably meant To sum up:
Note that |
Seemingly the last remaining valid use case for going from But it hasn't seen any progress in over a year |
No, the FFI case remains even after that. |
Thanks @Shnatsel, that summary is correct AFAICT.
Yes, @Shnatsel has an example in the OP that uses let mut x = [1, 2, 3];
let y: &mut i32 = unsafe { transmute(x.as_ptr()) }; // ERROR
let y: &mut i32 = unsafe { transmute(x.as_mut_ptr()) }; // OK The problem is that
I think there are a couple more valid use cases than that. @Lokathor mentioned the situation where FFI declarations for C functions use Some Rust APIs also expect a let x = [1, 2, 3];
let y = std::ptr::NonNull::<i32>::new(x.as_ptr() as *mut _)?; but that creates a So we probably want to have multiple, independent lints.
|
I think the type 3 and 4 lints can be a a single lint, warn by defaults, and projects that know what they're doing can turn off the lint. If you don't know enough of what you're doing to be confident in turning off the lint, you sure as ferris should not be doing it. |
I think already existing |
@Lokathor the problem with making it a single lint is that even if you have to use it for e.g. Personally, I agree type three should be a warn-by-default Though, to be fair, |
What? No, if you need it in one specific constructor for a thing using NonNull then then just turn it off in that one constructor, not any other place. |
There are almost certainly multiple places that you need to do the transformation, rather than one localized one. I know that's the case for |
Between those two links I see one instance of I'm not saying that no one would ever hit the warning, I'm saying that warnings exist to be fixed or turned off. If there's a warning that you might be doing something fishy and you mark it as fine and turn if off, that's still a good. You've still helped everyone who looks at the code in the future. "What were they trying to do here, did they even consider this case? Oh, there's an |
Note that the impls may have to change to use If you count If we know one case of the lint is much more prone to "questionable positives", and uses of that case tend to cluster, it makes sense to allow control of that separate part of the lint separately. I personally am very happy to litter my code with very granular warning allows, because I'm quite pedantic about linting. But I'd be quite tempted to turn off a "cast It's perfectly fine as a pedantic lint, but a regular lint really shouldn't cover these known problem cases. At least in the FFI case, it'd almost certainly be clearer not write a wrapper to do the cast unless directly exposing a safe version of the API (prefer thinner wrappers, etc). I'll admit I'm probably overestimating the impact the lint would have on pointer-heavy code, though, as the lint should only fire when the transform is |
Casting a
*const T
to*mut T
may lead to memory corruption since it allows mutation of shared state. Even if the*const T
happened to be unique, it is still undefined behavior and the optimizer may break such code in interesting ways. In a nutshell, this is as bad as transmuting a&
into&mut
.Update: turns out there are cases when this is not immediately trigger UB, but in those cases you shouldn't be doing these casts anyway.
This often occurs when people try to consume a data structure and create a new one from it, e.g.
in which case the proper solution is to rewrite it as
This also may happen when people try to mutate shared state through a
&
, in which case they need aCell
,RefCell
or anUnsafeCell
instead.Playground with a real-world snippet that passes Clippy as of version 0.0.212 but fails MIRI: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b28a15e3d99616b03caafdd794550946
This pattern seems to be quite widespread - quoting @RalfJung on Zulip:
Rust stdlib also had this bug: https://blog.rust-lang.org/2017/02/09/Rust-1.15.1.html
The text was updated successfully, but these errors were encountered: