Skip to content

Commit f14a7ea

Browse files
committed
Auto merge of #135634 - joboet:trivial-clone, r=<try>
stop specializing on `Copy` fixes #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 9c67cec + c5e262c commit f14a7ea

File tree

41 files changed

+276
-87
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+276
-87
lines changed

compiler/rustc_builtin_macros/src/deriving/bounds.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::MetaItem;
1+
use rustc_ast::{MetaItem, Safety};
22
use rustc_expand::base::{Annotatable, ExtCtxt};
33
use rustc_span::Span;
44

@@ -23,6 +23,7 @@ pub(crate) fn expand_deriving_copy(
2323
methods: Vec::new(),
2424
associated_types: Vec::new(),
2525
is_const,
26+
safety: Safety::Default,
2627
};
2728

2829
trait_def.expand(cx, mitem, item, push);
@@ -46,6 +47,7 @@ pub(crate) fn expand_deriving_const_param_ty(
4647
methods: Vec::new(),
4748
associated_types: Vec::new(),
4849
is_const,
50+
safety: Safety::Default,
4951
};
5052

5153
trait_def.expand(cx, mitem, item, push);
@@ -60,6 +62,7 @@ pub(crate) fn expand_deriving_const_param_ty(
6062
methods: Vec::new(),
6163
associated_types: Vec::new(),
6264
is_const,
65+
safety: Safety::Default,
6366
};
6467

6568
trait_def.expand(cx, mitem, item, push);
@@ -83,6 +86,7 @@ pub(crate) fn expand_deriving_unsized_const_param_ty(
8386
methods: Vec::new(),
8487
associated_types: Vec::new(),
8588
is_const,
89+
safety: Safety::Default,
8690
};
8791

8892
trait_def.expand(cx, mitem, item, push);

compiler/rustc_builtin_macros/src/deriving/clone.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use rustc_ast::{self as ast, Generics, ItemKind, MetaItem, VariantData};
1+
use rustc_ast::{self as ast, Generics, ItemKind, MetaItem, Safety, VariantData};
22
use rustc_data_structures::fx::FxHashSet;
33
use rustc_expand::base::{Annotatable, ExtCtxt};
4-
use rustc_span::{Ident, Span, kw, sym};
4+
use rustc_span::{DUMMY_SP, Ident, Span, kw, sym};
55
use thin_vec::{ThinVec, thin_vec};
66

77
use crate::deriving::generic::ty::*;
@@ -68,6 +68,25 @@ pub(crate) fn expand_deriving_clone(
6868
_ => cx.dcx().span_bug(span, "`#[derive(Clone)]` on trait item or impl item"),
6969
}
7070

71+
// If the clone method is just copying the value, also mark the type as
72+
// `TrivialClone` to allow some library optimizations.
73+
if is_simple {
74+
let trivial_def = TraitDef {
75+
span,
76+
path: path_std!(clone::TrivialClone),
77+
skip_path_as_bound: false,
78+
needs_copy_as_bound_if_packed: true,
79+
additional_bounds: bounds.clone(),
80+
supports_unions: true,
81+
methods: Vec::new(),
82+
associated_types: Vec::new(),
83+
is_const,
84+
safety: Safety::Unsafe(DUMMY_SP),
85+
};
86+
87+
trivial_def.expand_ext(cx, mitem, item, push, true);
88+
}
89+
7190
let trait_def = TraitDef {
7291
span,
7392
path: path_std!(clone::Clone),
@@ -87,6 +106,7 @@ pub(crate) fn expand_deriving_clone(
87106
}],
88107
associated_types: Vec::new(),
89108
is_const,
109+
safety: Safety::Default,
90110
};
91111

92112
trait_def.expand_ext(cx, mitem, item, push, is_simple)

compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::{self as ast, MetaItem};
1+
use rustc_ast::{self as ast, MetaItem, Safety};
22
use rustc_data_structures::fx::FxHashSet;
33
use rustc_expand::base::{Annotatable, ExtCtxt};
44
use rustc_span::{Span, sym};
@@ -43,6 +43,7 @@ pub(crate) fn expand_deriving_eq(
4343
}],
4444
associated_types: Vec::new(),
4545
is_const,
46+
safety: Safety::Default,
4647
};
4748
trait_def.expand_ext(cx, mitem, item, push, true)
4849
}

compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::MetaItem;
1+
use rustc_ast::{MetaItem, Safety};
22
use rustc_expand::base::{Annotatable, ExtCtxt};
33
use rustc_span::{Ident, Span, sym};
44
use thin_vec::thin_vec;
@@ -34,6 +34,7 @@ pub(crate) fn expand_deriving_ord(
3434
}],
3535
associated_types: Vec::new(),
3636
is_const,
37+
safety: Safety::Default,
3738
};
3839

3940
trait_def.expand(cx, mitem, item, push)

compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_ast::ptr::P;
2-
use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability};
2+
use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability, Safety};
33
use rustc_expand::base::{Annotatable, ExtCtxt};
44
use rustc_span::{Span, sym};
55
use thin_vec::thin_vec;
@@ -84,6 +84,7 @@ pub(crate) fn expand_deriving_partial_eq(
8484
methods: Vec::new(),
8585
associated_types: Vec::new(),
8686
is_const: false,
87+
safety: Safety::Default,
8788
};
8889
structural_trait_def.expand(cx, mitem, item, push);
8990

@@ -110,6 +111,7 @@ pub(crate) fn expand_deriving_partial_eq(
110111
methods,
111112
associated_types: Vec::new(),
112113
is_const,
114+
safety: Safety::Default,
113115
};
114116
trait_def.expand(cx, mitem, item, push)
115117
}

compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::{ExprKind, ItemKind, MetaItem, PatKind};
1+
use rustc_ast::{ExprKind, ItemKind, MetaItem, PatKind, Safety};
22
use rustc_expand::base::{Annotatable, ExtCtxt};
33
use rustc_span::{Ident, Span, sym};
44
use thin_vec::thin_vec;
@@ -64,6 +64,7 @@ pub(crate) fn expand_deriving_partial_ord(
6464
methods: vec![partial_cmp_def],
6565
associated_types: Vec::new(),
6666
is_const,
67+
safety: Safety::Default,
6768
};
6869
trait_def.expand(cx, mitem, item, push)
6970
}

compiler/rustc_builtin_macros/src/deriving/debug.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::{self as ast, EnumDef, MetaItem};
1+
use rustc_ast::{self as ast, EnumDef, MetaItem, Safety};
22
use rustc_expand::base::{Annotatable, ExtCtxt};
33
use rustc_session::config::FmtDebug;
44
use rustc_span::{Ident, Span, Symbol, sym};
@@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_debug(
4141
}],
4242
associated_types: Vec::new(),
4343
is_const,
44+
safety: Safety::Default,
4445
};
4546
trait_def.expand(cx, mitem, item, push)
4647
}

compiler/rustc_builtin_macros/src/deriving/default.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use core::ops::ControlFlow;
22

3-
use rustc_ast as ast;
43
use rustc_ast::visit::visit_opt;
5-
use rustc_ast::{EnumDef, VariantData, attr};
4+
use rustc_ast::{self as ast, EnumDef, Safety, VariantData, attr};
65
use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
76
use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
87
use smallvec::SmallVec;
@@ -51,6 +50,7 @@ pub(crate) fn expand_deriving_default(
5150
}],
5251
associated_types: Vec::new(),
5352
is_const,
53+
safety: Safety::Default,
5454
};
5555
trait_def.expand(cx, mitem, item, push)
5656
}

compiler/rustc_builtin_macros/src/deriving/generic/mod.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ pub(crate) use SubstructureFields::*;
183183
use rustc_ast::ptr::P;
184184
use rustc_ast::{
185185
self as ast, AnonConst, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind,
186-
Generics, Mutability, PatKind, VariantData,
186+
Generics, Mutability, PatKind, Safety, VariantData,
187187
};
188188
use rustc_attr_parsing::{AttributeKind, AttributeParser, ReprPacked};
189189
use rustc_expand::base::{Annotatable, ExtCtxt};
@@ -221,6 +221,9 @@ pub(crate) struct TraitDef<'a> {
221221
pub associated_types: Vec<(Ident, Ty)>,
222222

223223
pub is_const: bool,
224+
225+
/// The safety of the `impl`.
226+
pub safety: Safety,
224227
}
225228

226229
pub(crate) struct MethodDef<'a> {
@@ -792,7 +795,7 @@ impl<'a> TraitDef<'a> {
792795
Ident::empty(),
793796
attrs,
794797
ast::ItemKind::Impl(Box::new(ast::Impl {
795-
safety: ast::Safety::Default,
798+
safety: self.safety,
796799
polarity: ast::ImplPolarity::Positive,
797800
defaultness: ast::Defaultness::Final,
798801
constness: if self.is_const { ast::Const::Yes(DUMMY_SP) } else { ast::Const::No },

compiler/rustc_builtin_macros/src/deriving/hash.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::{MetaItem, Mutability};
1+
use rustc_ast::{MetaItem, Mutability, Safety};
22
use rustc_expand::base::{Annotatable, ExtCtxt};
33
use rustc_span::{Span, sym};
44
use thin_vec::thin_vec;
@@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_hash(
4141
}],
4242
associated_types: Vec::new(),
4343
is_const,
44+
safety: Safety::Default,
4445
};
4546

4647
hash_trait_def.expand(cx, mitem, item, push);

compiler/rustc_hir/src/lang_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ language_item_table! {
172172
Clone, sym::clone, clone_trait, Target::Trait, GenericRequirement::None;
173173
CloneFn, sym::clone_fn, clone_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
174174
UseCloned, sym::use_cloned, use_cloned_trait, Target::Trait, GenericRequirement::None;
175+
TrivialClone, sym::trivial_clone, trivial_clone_trait, Target::Trait, GenericRequirement::None;
175176
Sync, sym::sync, sync_trait, Target::Trait, GenericRequirement::Exact(0);
176177
DiscriminantKind, sym::discriminant_kind, discriminant_kind_trait, Target::Trait, GenericRequirement::None;
177178
/// The associated item of the `DiscriminantKind` trait.

compiler/rustc_middle/src/ty/context.rs

+1
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ bidirectional_lang_item_map! {
719719
Poll,
720720
Sized,
721721
TransmuteTrait,
722+
TrivialClone,
722723
Tuple,
723724
Unpin,
724725
Unsize,

compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,11 @@ where
397397
} else {
398398
match cx.as_lang_item(trait_def_id) {
399399
Some(TraitSolverLangItem::Sized) => G::consider_builtin_sized_candidate(self, goal),
400-
Some(TraitSolverLangItem::Copy | TraitSolverLangItem::Clone) => {
401-
G::consider_builtin_copy_clone_candidate(self, goal)
402-
}
400+
Some(
401+
TraitSolverLangItem::Copy
402+
| TraitSolverLangItem::Clone
403+
| TraitSolverLangItem::TrivialClone,
404+
) => G::consider_builtin_copy_clone_candidate(self, goal),
403405
Some(TraitSolverLangItem::Fn) => {
404406
G::consider_builtin_fn_trait_candidates(self, goal, ty::ClosureKind::Fn)
405407
}

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ symbols! {
364364
ToString,
365365
TokenStream,
366366
Trait,
367+
TrivialClone,
367368
Try,
368369
TryCaptureGeneric,
369370
TryCapturePrintable,
@@ -2081,6 +2082,7 @@ symbols! {
20812082
transparent_enums,
20822083
transparent_unions,
20832084
trivial_bounds,
2085+
trivial_clone,
20842086
truncf128,
20852087
truncf16,
20862088
truncf32,

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
108108
&mut candidates,
109109
);
110110
} else {
111-
if tcx.is_lang_item(def_id, LangItem::Clone) {
111+
if tcx.is_lang_item(def_id, LangItem::Clone)
112+
|| tcx.is_lang_item(def_id, LangItem::TrivialClone)
113+
{
112114
// Same builtin conditions as `Copy`, i.e., every type which has builtin support
113115
// for `Copy` also has builtin support for `Clone`, and tuples/arrays of `Clone`
114116
// types have builtin support for `Clone`.

compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+2
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
261261
self.copy_clone_conditions(obligation)
262262
} else if tcx.is_lang_item(trait_def, LangItem::Clone) {
263263
self.copy_clone_conditions(obligation)
264+
} else if tcx.is_lang_item(trait_def, LangItem::TrivialClone) {
265+
self.copy_clone_conditions(obligation)
264266
} else if tcx.is_lang_item(trait_def, LangItem::FusedIterator) {
265267
self.fused_iterator_conditions(obligation)
266268
} else {

compiler/rustc_type_ir/src/lang_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub enum TraitSolverLangItem {
3636
Poll,
3737
Sized,
3838
TransmuteTrait,
39+
TrivialClone,
3940
Tuple,
4041
Unpin,
4142
Unsize,

library/alloc/src/boxed/convert.rs

+5-1
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()

library/alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
#![feature(std_internals)]
148148
#![feature(str_internals)]
149149
#![feature(temporary_niche_types)]
150+
#![feature(trivial_clone)]
150151
#![feature(trusted_fused)]
151152
#![feature(trusted_len)]
152153
#![feature(trusted_random_access)]

library/alloc/src/rc.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,9 @@
243243

244244
use core::any::Any;
245245
use core::cell::Cell;
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;
@@ -2150,7 +2150,8 @@ impl<T> Rc<[T]> {
21502150

21512151
/// Copy elements from slice into newly allocated `Rc<[T]>`
21522152
///
2153-
/// Unsafe because the caller must either take ownership or bind `T: Copy`
2153+
/// Unsafe because the caller must either take ownership, bind `T: Copy` or
2154+
/// bind `T: TrivialClone`.
21542155
#[cfg(not(no_global_oom_handling))]
21552156
unsafe fn copy_from_slice(v: &[T]) -> Rc<[T]> {
21562157
unsafe {
@@ -2240,9 +2241,11 @@ impl<T: Clone> RcFromSlice<T> for Rc<[T]> {
22402241
}
22412242

22422243
#[cfg(not(no_global_oom_handling))]
2243-
impl<T: Copy> RcFromSlice<T> for Rc<[T]> {
2244+
impl<T: TrivialClone> RcFromSlice<T> for Rc<[T]> {
22442245
#[inline]
22452246
fn from_slice(v: &[T]) -> Self {
2247+
// SAFETY: `T` implements `TrivialClone`, so this is sound and equivalent
2248+
// to the above.
22462249
unsafe { Rc::copy_from_slice(v) }
22472250
}
22482251
}

library/alloc/src/slice.rs

+4-2
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;
@@ -440,7 +442,7 @@ impl<T> [T] {
440442
}
441443
}
442444

443-
impl<T: Copy> ConvertVec for T {
445+
impl<T: TrivialClone> ConvertVec for T {
444446
#[inline]
445447
fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A> {
446448
let mut v = Vec::with_capacity_in(s.len(), alloc);
@@ -825,7 +827,7 @@ impl<T: Clone, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
825827
}
826828

827829
#[cfg(not(no_global_oom_handling))]
828-
impl<T: Copy, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
830+
impl<T: TrivialClone, A: Allocator> SpecCloneIntoVec<T, A> for [T] {
829831
fn clone_into(&self, target: &mut Vec<T, A>) {
830832
target.clear();
831833
target.extend_from_slice(self);

0 commit comments

Comments
 (0)