Skip to content

Commit cbbece4

Browse files
committed
Auto merge of rust-lang#135634 - joboet:trivial-clone, r=Mark-Simulacrum
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`...
2 parents cddcb91 + 6397a4e commit cbbece4

File tree

25 files changed

+212
-71
lines changed

25 files changed

+212
-71
lines changed

alloc/src/boxed/convert.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use core::any::Any;
2+
#[cfg(not(no_global_oom_handling))]
3+
use core::clone::TrivialClone;
24
use core::error::Error;
35
use core::mem;
46
use core::pin::Pin;
@@ -75,11 +77,13 @@ impl<T: Clone> BoxFromSlice<T> for Box<[T]> {
7577
}
7678

7779
#[cfg(not(no_global_oom_handling))]
78-
impl<T: Copy> BoxFromSlice<T> for Box<[T]> {
80+
impl<T: TrivialClone> BoxFromSlice<T> for Box<[T]> {
7981
#[inline]
8082
fn from_slice(slice: &[T]) -> Self {
8183
let len = slice.len();
8284
let buf = RawVec::with_capacity(len);
85+
// SAFETY: since `T` implements `TrivialClone`, this is sound and
86+
// equivalent to the above.
8387
unsafe {
8488
ptr::copy_nonoverlapping(slice.as_ptr(), buf.ptr(), len);
8589
buf.into_box(slice.len()).assume_init()

alloc/src/collections/vec_deque/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
88
#![stable(feature = "rust1", since = "1.0.0")]
99

10+
#[cfg(not(no_global_oom_handling))]
11+
use core::clone::TrivialClone;
1012
use core::cmp::{self, Ordering};
1113
use core::hash::{Hash, Hasher};
1214
use core::iter::{ByRefSized, repeat_n, repeat_with};
@@ -3419,7 +3421,7 @@ impl<T: Clone, A: Allocator> SpecExtendFromWithin for VecDeque<T, A> {
34193421
}
34203422

34213423
#[cfg(not(no_global_oom_handling))]
3422-
impl<T: Copy, A: Allocator> SpecExtendFromWithin for VecDeque<T, A> {
3424+
impl<T: TrivialClone, A: Allocator> SpecExtendFromWithin for VecDeque<T, A> {
34233425
unsafe fn spec_extend_from_within(&mut self, src: Range<usize>) {
34243426
let dst = self.len();
34253427
let count = src.end - src.start;

alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
#![feature(std_internals)]
147147
#![feature(str_internals)]
148148
#![feature(temporary_niche_types)]
149+
#![feature(trivial_clone)]
149150
#![feature(trusted_fused)]
150151
#![feature(trusted_len)]
151152
#![feature(trusted_random_access)]

alloc/src/rc.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@
243243

244244
use core::any::Any;
245245
use core::cell::{Cell, CloneFromCell};
246-
#[cfg(not(no_global_oom_handling))]
247-
use core::clone::CloneToUninit;
248246
use core::clone::UseCloned;
247+
#[cfg(not(no_global_oom_handling))]
248+
use core::clone::{CloneToUninit, TrivialClone};
249249
use core::cmp::Ordering;
250250
use core::hash::{Hash, Hasher};
251251
use core::intrinsics::abort;
@@ -2224,7 +2224,8 @@ impl<T> Rc<[T]> {
22242224

22252225
/// Copy elements from slice into newly allocated `Rc<[T]>`
22262226
///
2227-
/// Unsafe because the caller must either take ownership or bind `T: Copy`
2227+
/// Unsafe because the caller must either take ownership, bind `T: Copy` or
2228+
/// bind `T: TrivialClone`.
22282229
#[cfg(not(no_global_oom_handling))]
22292230
unsafe fn copy_from_slice(v: &[T]) -> Rc<[T]> {
22302231
unsafe {
@@ -2314,9 +2315,11 @@ impl<T: Clone> RcFromSlice<T> for Rc<[T]> {
23142315
}
23152316

23162317
#[cfg(not(no_global_oom_handling))]
2317-
impl<T: Copy> RcFromSlice<T> for Rc<[T]> {
2318+
impl<T: TrivialClone> RcFromSlice<T> for Rc<[T]> {
23182319
#[inline]
23192320
fn from_slice(v: &[T]) -> Self {
2321+
// SAFETY: `T` implements `TrivialClone`, so this is sound and equivalent
2322+
// to the above.
23202323
unsafe { Rc::copy_from_slice(v) }
23212324
}
23222325
}

alloc/src/slice.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
use core::borrow::{Borrow, BorrowMut};
1313
#[cfg(not(no_global_oom_handling))]
14+
use core::clone::TrivialClone;
15+
#[cfg(not(no_global_oom_handling))]
1416
use core::cmp::Ordering::{self, Less};
1517
#[cfg(not(no_global_oom_handling))]
1618
use core::mem::MaybeUninit;
@@ -439,7 +441,7 @@ impl<T> [T] {
439441
}
440442
}
441443

442-
impl<T: Copy> ConvertVec for T {
444+
impl<T: TrivialClone> ConvertVec for T {
443445
#[inline]
444446
fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> {
445447
let mut v = Vec::with_capacity_in(s.len(), alloc);
@@ -822,7 +824,7 @@ impl<T: Clone, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
822824
}
823825

824826
#[cfg(not(no_global_oom_handling))]
825-
impl<T: Copy, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
827+
impl<T: TrivialClone, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
826828
fn clone_into(&self, target: &mut Vec<T, A>) {
827829
target.clear();
828830
target.extend_from_slice(self);

alloc/src/sync.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use core::any::Any;
1212
use core::cell::CloneFromCell;
1313
#[cfg(not(no_global_oom_handling))]
1414
use core::clone::CloneToUninit;
15+
#[cfg(not(no_global_oom_handling))]
16+
use core::clone::TrivialClone;
1517
use core::clone::UseCloned;
1618
use core::cmp::Ordering;
1719
use core::hash::{Hash, Hasher};
@@ -2156,7 +2158,8 @@ impl<T> Arc<[T]> {
21562158

21572159
/// Copy elements from slice into newly allocated `Arc<[T]>`
21582160
///
2159-
/// Unsafe because the caller must either take ownership or bind `T: Copy`.
2161+
/// Unsafe because the caller must either take ownership, bind `T: Copy` or
2162+
/// bind `T: TrivialClone`.
21602163
#[cfg(not(no_global_oom_handling))]
21612164
unsafe fn copy_from_slice(v: &[T]) -> Arc<[T]> {
21622165
unsafe {
@@ -2248,9 +2251,11 @@ impl<T: Clone> ArcFromSlice<T> for Arc<[T]> {
22482251
}
22492252

22502253
#[cfg(not(no_global_oom_handling))]
2251-
impl<T: Copy> ArcFromSlice<T> for Arc<[T]> {
2254+
impl<T: TrivialClone> ArcFromSlice<T> for Arc<[T]> {
22522255
#[inline]
22532256
fn from_slice(v: &[T]) -> Self {
2257+
// SAFETY: `T` implements `TrivialClone`, so this is sound and equivalent
2258+
// to the above.
22542259
unsafe { Arc::copy_from_slice(v) }
22552260
}
22562261
}

alloc/src/vec/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
7474
#![stable(feature = "rust1", since = "1.0.0")]
7575

76+
#[cfg(not(no_global_oom_handling))]
77+
use core::clone::TrivialClone;
7678
#[cfg(not(no_global_oom_handling))]
7779
use core::cmp;
7880
use core::cmp::Ordering;
@@ -3494,7 +3496,7 @@ impl<T: Clone, A: Allocator> ExtendFromWithinSpec for Vec<T, A> {
34943496
}
34953497

34963498
#[cfg(not(no_global_oom_handling))]
3497-
impl<T: Copy, A: Allocator> ExtendFromWithinSpec for Vec<T, A> {
3499+
impl<T: TrivialClone, A: Allocator> ExtendFromWithinSpec for Vec<T, A> {
34983500
unsafe fn spec_extend_from_within(&mut self, src: Range<usize>) {
34993501
let count = src.len();
35003502
{
@@ -3507,8 +3509,8 @@ impl<T: Copy, A: Allocator> ExtendFromWithinSpec for Vec<T, A> {
35073509
// SAFETY:
35083510
// - Both pointers are created from unique slice references (`&mut [_]`)
35093511
// so they are valid and do not overlap.
3510-
// - Elements are :Copy so it's OK to copy them, without doing
3511-
// anything with the original values
3512+
// - Elements implement `TrivialClone` so this is equivalent to calling
3513+
// `clone` on every one of them.
35123514
// - `count` is equal to the len of `source`, so source is valid for
35133515
// `count` reads
35143516
// - `.reserve(count)` guarantees that `spare.len() >= count` so spare

alloc/src/vec/spec_extend.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use core::clone::TrivialClone;
12
use core::iter::TrustedLen;
23
use core::slice::{self};
34

@@ -48,7 +49,7 @@ where
4849

4950
impl<'a, T: 'a, A: Allocator> SpecExtend<&'a T, slice::Iter<'a, T>> for Vec<T, A>
5051
where
51-
T: Copy,
52+
T: TrivialClone,
5253
{
5354
fn spec_extend(&mut self, iterator: slice::Iter<'a, T>) {
5455
let slice = iterator.as_slice();

alloctests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#![feature(slice_range)]
4141
#![feature(std_internals)]
4242
#![feature(temporary_niche_types)]
43+
#![feature(trivial_clone)]
4344
#![feature(trusted_fused)]
4445
#![feature(trusted_len)]
4546
#![feature(trusted_random_access)]

alloctests/tests/vec.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,20 +2299,6 @@ fn test_vec_swap() {
22992299
assert_eq!(n, 0);
23002300
}
23012301

2302-
#[test]
2303-
fn test_extend_from_within_spec() {
2304-
#[derive(Copy)]
2305-
struct CopyOnly;
2306-
2307-
impl Clone for CopyOnly {
2308-
fn clone(&self) -> Self {
2309-
panic!("extend_from_within must use specialization on copy");
2310-
}
2311-
}
2312-
2313-
vec![CopyOnly, CopyOnly].extend_from_within(..);
2314-
}
2315-
23162302
#[test]
23172303
fn test_extend_from_within_clone() {
23182304
let mut v = vec![String::from("sssss"), String::from("12334567890"), String::from("c")];

0 commit comments

Comments
 (0)