-
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
Array and Vec's Clone specialization is maybe unsound with conditionally Copy types. #132442
Comments
I believe it was discussed before, and the conclusion was that the specialization is okay and it's invalid to implement |
@theemathas Is this at all specific to the array impl? Can you do the same thing with rust/library/alloc/src/slice.rs Line 156 in a8e1186
|
Yes. This code also doesn't print anything. struct Weird<'a>(&'a i32);
impl Clone for Weird<'_> {
fn clone(&self) -> Self {
println!("clone() called");
Weird(self.0)
}
}
impl Copy for Weird<'static> {}
fn main() {
let local = 1;
let _ = vec![Weird(&local)].clone();
} |
Implementing copy conditionally on lifetimes is nonsensical and unsupported. MIR building will lower all moves of such a value to copy's, which borrowck will then error about when it's not static. |
More discussion of |
Related to #89948. I think that until we actually forbid such Whether rust/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs Lines 58 to 66 in 7fbd4ce
However, given that |
I found a somewhat popular crate (2K stars) that specifically depends on this Clone specialization existing, and depends on a Copy impl that is conditional on type equality. /// Returns if the type `T` is equal to `U`, ignoring lifetimes.
|
Note that it's possible to avoid this specialization by using the |
lol, this issue has been known since 2020 in #71321 rust/library/core/src/marker.rs Lines 408 to 413 in 9fa9ef3
|
Note that there's plenty other uses of |
It's also worth noting that there's nothing in the docs that says |
https://doc.rust-lang.org/std/clone/trait.Clone.html#how-can-i-implement-clone
|
We don't need to accommodate that though, since it's not part of our stable API. I have asked people to not propagate this hack without attaching the appropriate warnings, but it appears that I'm shouting into the void. |
People do that because it solves a problem other methods can't solve. Adding an intended solution to macro_rules! implements_for_static {
($type:ty: $($trait:tt)*) => { /* compiler intrinsic */ };
}; such that |
I have not heard this as an official t-types opinion. If it is, we should start linting against this. |
WG-prioritization assigning priority (Zulip discussion). @rustbot label -I-prioritize +P-medium |
E-needs-investigation: should figure out if a decision was made previously and why, and if this should be linted against going forward. |
I have another example, where the Copy specialization is used to check at runtime if a lifetime is static. It's fine if this is non functional, but it shouldn't be unsound. |
That's definitely unsound I am afraid -- any kind of dispatch-of-lifetime is. Just because things are useful doesn't mean they are magically sound. |
Hi, I'm the person who implemented the We have only depended on this behaviour out of necessity, and without the typeid crate being available when we implemented this originally, it was simply the only option we could come up with (that wasn't some horrible casting or unnecessary As a reminder, we have historically used this because we absolutely need selective access to specialization. From our perspective, if core can do it, then we need to be able to as well. It would be even better if we could implement this properly with proper specialization, but as far as I know, specialization stops us with |
libcore (and the sysroot as a whole) gets special privileges no other crate gets -- so this is absolutely not an acceptable argument. We try to keep this at a minimum, but it cannot always be fully avoided. If this social contract starts eroding, that does not bode well for our ability to keep evolving the language. |
I understand that, at this time, there are certain features which cannot be guaranteed to be safe and are thus restricted. It is very frustrating, however, to have certain features be limited when they apply a non-negotiable constraint into what we can implement (or, at least, the level of optimisation with which we can do so) and this is really unfortunate. I understand from a design perspective, but from a user perspective, this can be very frustrating. I think this is mostly off-topic for this thread, so I'll move away here, but if there's more to be said I'm happy to follow up in the Zulip. |
Believe me, it is equally frustrating for me that we don't have specialization. :) However, it is also frustrating to see people abuse the compiler with (EDIT: I looked up that word and it doesn't mean what I thought it means, sorry for that.) You can't make a feature "non-negotiable" that just doesn't exist in Rust. There are many things I'd like to make non-negotiable about all sorts of things in life, but that doesn't mean I get to have them. ;) |
Code that has deliberately bypassed stability guarantees, knowing that it depends on unstable details, is not subject to stability guarantees. We are permitted to break your code tomorrow. |
If you want the powers of None. |
I understand the emotions here, but can we please get a bit less angry at people looking for alternatives and a bit more solution-oriented, especially on what has apparently become a tracking issue of sorts? As mentioned above, |
This issue will probably be closed without everyone finding a satisfactory alternative for whatever hack they implemented this way. That seems like it will be true whether it gets closed because we made these implementations illegal or removed the Copy specialization inside libcore, or whether it gets closed because we decide to do nothing. That suggests those matters would be new issues. If any of the dependent concerns are distinct, anyways, it would be easier to keep track of them that way, I think. |
stop specializing on `Copy` fixes rust-lang#132442 `std` specializes on `Copy` to optimize certain library functions such as `clone_from_slice`. This is unsound, however, as the `Copy` implementation may not be always applicable because of lifetime bounds, which specialization does not take into account; the result being that values are copied even though they are not `Copy`. For instance, this code: ```rust struct SometimesCopy<'a>(&'a Cell<bool>); impl<'a> Clone for SometimesCopy<'a> { fn clone(&self) -> Self { self.0.set(true); Self(self.0) } } impl Copy for SometimesCopy<'static> {} let clone_called = Cell::new(false); // As SometimesCopy<'clone_called> is not 'static, this must run `clone`, // setting the value to `true`. let _ = [SometimesCopy(&clone_called)].clone(); assert!(clone_called.get()); ``` should not panic, but does ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6be7a48cad849d8bd064491616fdb43c)). To solve this, this PR introduces a new `unsafe` trait: `TrivialClone`. This trait may be implemented whenever the `Clone` implementation is equivalent to copying the value (so e.g. `fn clone(&self) -> Self { *self }`). Because of lifetime erasure, there is no way for the `Clone` implementation to observe lifetime bounds, meaning that even if the `TrivialClone` has stricter bounds than the `Clone` implementation, its invariant still holds. Therefore, it is sound to specialize on `TrivialClone`. I've changed all `Copy` specializations in the standard library to specialize on `TrivialClone` instead. Unfortunately, the unsound `#[rustc_unsafe_specialization_marker]` attribute on `Copy` cannot be removed in this PR as `hashbrown` still depends on it. I'll make a PR updating `hashbrown` once this lands. With `Copy` no longer being considered for specialization, this change alone would result in the standard library optimizations not being applied for user types unaware of `TrivialClone`. To avoid this and restore the optimizations in most cases, I have changed the expansion of `#[derive(Clone)]`: Currently, whenever both `Clone` and `Copy` are derived, the `clone` method performs a copy of the value. With this PR, the derive macro also adds a `TrivialClone` implementation to make this case observable using specialization. I anticipate that most users will use `#[derive(Clone, Copy)]` whenever both are applicable, so most users will still profit from the library optimizations. Unfortunately, Hyrum's law applies to this PR: there are some popular crates which rely on the precise specialization behaviour of `core` to implement "specialization at home", e.g. [`libAFL`](https://github.com/AFLplusplus/LibAFL/blob/89cff637025c1652c24e8d97a30a2e3d01f187a4/libafl_bolts/src/tuples.rs#L27-L49). I have no remorse for breaking such horrible code, but perhaps we should open other, better ways to satisfy their needs – for example by dropping the `'static` bound on `TypeId::of`...
stop specializing on `Copy` fixes rust-lang#132442 `std` specializes on `Copy` to optimize certain library functions such as `clone_from_slice`. This is unsound, however, as the `Copy` implementation may not be always applicable because of lifetime bounds, which specialization does not take into account; the result being that values are copied even though they are not `Copy`. For instance, this code: ```rust struct SometimesCopy<'a>(&'a Cell<bool>); impl<'a> Clone for SometimesCopy<'a> { fn clone(&self) -> Self { self.0.set(true); Self(self.0) } } impl Copy for SometimesCopy<'static> {} let clone_called = Cell::new(false); // As SometimesCopy<'clone_called> is not 'static, this must run `clone`, // setting the value to `true`. let _ = [SometimesCopy(&clone_called)].clone(); assert!(clone_called.get()); ``` should not panic, but does ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6be7a48cad849d8bd064491616fdb43c)). To solve this, this PR introduces a new `unsafe` trait: `TrivialClone`. This trait may be implemented whenever the `Clone` implementation is equivalent to copying the value (so e.g. `fn clone(&self) -> Self { *self }`). Because of lifetime erasure, there is no way for the `Clone` implementation to observe lifetime bounds, meaning that even if the `TrivialClone` has stricter bounds than the `Clone` implementation, its invariant still holds. Therefore, it is sound to specialize on `TrivialClone`. I've changed all `Copy` specializations in the standard library to specialize on `TrivialClone` instead. Unfortunately, the unsound `#[rustc_unsafe_specialization_marker]` attribute on `Copy` cannot be removed in this PR as `hashbrown` still depends on it. I'll make a PR updating `hashbrown` once this lands. With `Copy` no longer being considered for specialization, this change alone would result in the standard library optimizations not being applied for user types unaware of `TrivialClone`. To avoid this and restore the optimizations in most cases, I have changed the expansion of `#[derive(Clone)]`: Currently, whenever both `Clone` and `Copy` are derived, the `clone` method performs a copy of the value. With this PR, the derive macro also adds a `TrivialClone` implementation to make this case observable using specialization. I anticipate that most users will use `#[derive(Clone, Copy)]` whenever both are applicable, so most users will still profit from the library optimizations. Unfortunately, Hyrum's law applies to this PR: there are some popular crates which rely on the precise specialization behaviour of `core` to implement "specialization at home", e.g. [`libAFL`](https://github.com/AFLplusplus/LibAFL/blob/89cff637025c1652c24e8d97a30a2e3d01f187a4/libafl_bolts/src/tuples.rs#L27-L49). I have no remorse for breaking such horrible code, but perhaps we should open other, better ways to satisfy their needs – for example by dropping the `'static` bound on `TypeId::of`...
I present to you, a type which unconditionally doesn't implement Copy (for some definition of "unconditional"), and yet specialization thinks that it does implement Copy. trait IsStatic {}
impl<'a: 'static> IsStatic for &'a () {}
struct NotCopy(i32);
impl Copy for NotCopy where for<'a> &'a (): IsStatic {}
impl Clone for NotCopy {
fn clone(&self) -> Self {
println!("cloned");
NotCopy(self.0)
}
}
fn main() {
// does not print anything
let _ = [NotCopy(1)].clone();
} |
stop specializing on `Copy` fixes rust-lang#132442 `std` specializes on `Copy` to optimize certain library functions such as `clone_from_slice`. This is unsound, however, as the `Copy` implementation may not be always applicable because of lifetime bounds, which specialization does not take into account; the result being that values are copied even though they are not `Copy`. For instance, this code: ```rust struct SometimesCopy<'a>(&'a Cell<bool>); impl<'a> Clone for SometimesCopy<'a> { fn clone(&self) -> Self { self.0.set(true); Self(self.0) } } impl Copy for SometimesCopy<'static> {} let clone_called = Cell::new(false); // As SometimesCopy<'clone_called> is not 'static, this must run `clone`, // setting the value to `true`. let _ = [SometimesCopy(&clone_called)].clone(); assert!(clone_called.get()); ``` should not panic, but does ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6be7a48cad849d8bd064491616fdb43c)). To solve this, this PR introduces a new `unsafe` trait: `TrivialClone`. This trait may be implemented whenever the `Clone` implementation is equivalent to copying the value (so e.g. `fn clone(&self) -> Self { *self }`). Because of lifetime erasure, there is no way for the `Clone` implementation to observe lifetime bounds, meaning that even if the `TrivialClone` has stricter bounds than the `Clone` implementation, its invariant still holds. Therefore, it is sound to specialize on `TrivialClone`. I've changed all `Copy` specializations in the standard library to specialize on `TrivialClone` instead. Unfortunately, the unsound `#[rustc_unsafe_specialization_marker]` attribute on `Copy` cannot be removed in this PR as `hashbrown` still depends on it. I'll make a PR updating `hashbrown` once this lands. With `Copy` no longer being considered for specialization, this change alone would result in the standard library optimizations not being applied for user types unaware of `TrivialClone`. To avoid this and restore the optimizations in most cases, I have changed the expansion of `#[derive(Clone)]`: Currently, whenever both `Clone` and `Copy` are derived, the `clone` method performs a copy of the value. With this PR, the derive macro also adds a `TrivialClone` implementation to make this case observable using specialization. I anticipate that most users will use `#[derive(Clone, Copy)]` whenever both are applicable, so most users will still profit from the library optimizations. Unfortunately, Hyrum's law applies to this PR: there are some popular crates which rely on the precise specialization behaviour of `core` to implement "specialization at home", e.g. [`libAFL`](https://github.com/AFLplusplus/LibAFL/blob/89cff637025c1652c24e8d97a30a2e3d01f187a4/libafl_bolts/src/tuples.rs#L27-L49). I have no remorse for breaking such horrible code, but perhaps we should open other, better ways to satisfy their needs – for example by dropping the `'static` bound on `TypeId::of`...
stop specializing on `Copy` fixes rust-lang#132442 `std` specializes on `Copy` to optimize certain library functions such as `clone_from_slice`. This is unsound, however, as the `Copy` implementation may not be always applicable because of lifetime bounds, which specialization does not take into account; the result being that values are copied even though they are not `Copy`. For instance, this code: ```rust struct SometimesCopy<'a>(&'a Cell<bool>); impl<'a> Clone for SometimesCopy<'a> { fn clone(&self) -> Self { self.0.set(true); Self(self.0) } } impl Copy for SometimesCopy<'static> {} let clone_called = Cell::new(false); // As SometimesCopy<'clone_called> is not 'static, this must run `clone`, // setting the value to `true`. let _ = [SometimesCopy(&clone_called)].clone(); assert!(clone_called.get()); ``` should not panic, but does ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6be7a48cad849d8bd064491616fdb43c)). To solve this, this PR introduces a new `unsafe` trait: `TrivialClone`. This trait may be implemented whenever the `Clone` implementation is equivalent to copying the value (so e.g. `fn clone(&self) -> Self { *self }`). Because of lifetime erasure, there is no way for the `Clone` implementation to observe lifetime bounds, meaning that even if the `TrivialClone` has stricter bounds than the `Clone` implementation, its invariant still holds. Therefore, it is sound to specialize on `TrivialClone`. I've changed all `Copy` specializations in the standard library to specialize on `TrivialClone` instead. Unfortunately, the unsound `#[rustc_unsafe_specialization_marker]` attribute on `Copy` cannot be removed in this PR as `hashbrown` still depends on it. I'll make a PR updating `hashbrown` once this lands. With `Copy` no longer being considered for specialization, this change alone would result in the standard library optimizations not being applied for user types unaware of `TrivialClone`. To avoid this and restore the optimizations in most cases, I have changed the expansion of `#[derive(Clone)]`: Currently, whenever both `Clone` and `Copy` are derived, the `clone` method performs a copy of the value. With this PR, the derive macro also adds a `TrivialClone` implementation to make this case observable using specialization. I anticipate that most users will use `#[derive(Clone, Copy)]` whenever both are applicable, so most users will still profit from the library optimizations. Unfortunately, Hyrum's law applies to this PR: there are some popular crates which rely on the precise specialization behaviour of `core` to implement "specialization at home", e.g. [`libAFL`](https://github.com/AFLplusplus/LibAFL/blob/89cff637025c1652c24e8d97a30a2e3d01f187a4/libafl_bolts/src/tuples.rs#L27-L49). I have no remorse for breaking such horrible code, but perhaps we should open other, better ways to satisfy their needs – for example by dropping the `'static` bound on `TypeId::of`...
Currently, the
Clone
impl for[T; N]
uses specialization. IfT
implementsCopy
, then cloning a[T; N]
will do a memcpy (ignoringT
'sClone
impl). IfT
doesn't implementCopy
, then it will iterate and callT
'sClone
impl.However, specialization doesn't look at lifetimes. Therefore, even if
T
implementsCopy
for only some lifetimes, cloning a[T; N]
will do a memcopy. This is incorrect in the case whereT
actually doesn't implementCopy
. For example:In the above code,
Weird
only implementsCopy
when its lifetime is'static
. Therefore, I think that cloning a[Weird<'a>; 1]
should call theclone()
method. However, running the above code in either stable (1.82.0) or nightly (1.84.0-nightly (2024-10-30 759e07f063fb8e6306ff)
) rust doesn't print anything. I believe that this is incorrect.I am unsure whether or not this can cause UB in purely safe code, (hence the "maybe" in the issue title). But maybe someone else could figure out how to do that?
However, I have written this code, which uses unsafe code to implement a
WeirdCow
type whose public API I believe is sound on its own, but can be used in combination with array's Clone specialization to cause use-after-free. Either thisWeirdCow
type is unsound, or the array's Clone implementation is unsound, and I can't figure out which.@rustbot label +I-unsound
The text was updated successfully, but these errors were encountered: