Auto merge of #68970 - matthewjasper:min-spec, r=nikomatsakis

Implement a feature for a sound specialization subset

This implements a new feature (`min_specialization`) that restricts specialization to a subset that is reasonable for the standard library to use.

The plan is to then:

* Update `libcore` and `liballoc` to compile with `min_specialization`.
* Add a lint to forbid use of `feature(specialization)` (and other unsound, type system extending features) in the standard library.
* Fix the soundness issues around `specialization`.
* Remove `min_specialization`

The rest of this is an overview from a comment in this PR

## Basic approach

To enforce this requirement on specializations we take the following approach:
1. Match up the substs for `impl2` so that the implemented trait and self-type match those for `impl1`.
2. Check for any direct use of `'static` in the substs of `impl2`.
3. Check that all of the generic parameters of `impl1` occur at most once in the *unconstrained* substs for `impl2`. A parameter is constrained if its value is completely determined by an associated type projection predicate.
4. Check that all predicates on `impl1` also exist on `impl2` (after matching substs).

## Example

Suppose we have the following always applicable impl:

```rust
impl<T> SpecExtend<T> for std::vec::IntoIter<T> { /* specialized impl */ }
impl<T, I: Iterator<Item=T>> SpecExtend<T> for I { /* default impl */ }
```

We get that the subst for `impl2` are `[T, std::vec::IntoIter<T>]`. `T` is constrained to be `<I as Iterator>::Item`, so we check only `std::vec::IntoIter<T>` for repeated parameters, which it doesn't have. The predicates of `impl1` are only `T: Sized`, which is also a predicate of impl2`. So this specialization is sound.

## Extensions

Unfortunately not all specializations in the standard library are allowed by this. So there are two extensions to these rules that allow specializing on some traits.

### rustc_specialization_trait

If a trait is always applicable, then it's sound to specialize on it. We check trait is always applicable in the same way as impls, except that step 4 is now "all predicates on `impl1` are always applicable". We require that `specialization` or `min_specialization` is enabled to implement these traits.

### rustc_specialization_marker

There are also some specialization on traits with no methods, including the `FusedIterator` trait which is advertised as allowing optimizations. We allow marking marker traits with an unstable attribute that means we ignore them in point 3 of the checks above. This is unsound but we allow it in the short term because it can't cause use after frees with purely safe code in the same way as specializing on traits methods can.

r? @nikomatsakis
cc #31844 #67194
This commit is contained in:
bors 2020-03-16 20:49:26 +00:00
commit e24252a12c
48 changed files with 1094 additions and 72 deletions

View file

@ -0,0 +1,6 @@
#![feature(rustc_attrs)]
#[rustc_specialization_trait]
pub trait SpecTrait {
fn method(&self);
}

View file

@ -0,0 +1,32 @@
// Test that associated types in trait objects are not considered to be
// constrained.
#![feature(min_specialization)]
trait Specializable {
fn f();
}
trait B<T> {
type Y;
}
trait C {
type Y;
}
impl<A: ?Sized> Specializable for A {
default fn f() {}
}
impl<'a, T> Specializable for dyn B<T, Y = T> + 'a {
//~^ ERROR specializing impl repeats parameter `T`
fn f() {}
}
impl<'a, T> Specializable for dyn C<Y = (T, T)> + 'a {
//~^ ERROR specializing impl repeats parameter `T`
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,20 @@
error: specializing impl repeats parameter `T`
--> $DIR/dyn-trait-assoc-types.rs:22:1
|
LL | / impl<'a, T> Specializable for dyn B<T, Y = T> + 'a {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: specializing impl repeats parameter `T`
--> $DIR/dyn-trait-assoc-types.rs:27:1
|
LL | / impl<'a, T> Specializable for dyn C<Y = (T, T)> + 'a {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to 2 previous errors

View file

@ -0,0 +1,16 @@
// Check that specialization traits can't be implemented without a feature.
// gate-test-min_specialization
// aux-build:specialization-trait.rs
extern crate specialization_trait;
struct A {}
impl specialization_trait::SpecTrait for A {
//~^ ERROR implementing `rustc_specialization_trait` traits is unstable
fn method(&self) {}
}
fn main() {}

View file

@ -0,0 +1,10 @@
error: implementing `rustc_specialization_trait` traits is unstable
--> $DIR/impl_specialization_trait.rs:11:1
|
LL | impl specialization_trait::SpecTrait for A {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: add `#![feature(min_specialization)]` to the crate attributes to enable
error: aborting due to previous error

View file

@ -0,0 +1,30 @@
// Test that specializing on the well-formed predicates of the trait and
// self-type of an impl is allowed.
// check-pass
#![feature(min_specialization)]
struct OrdOnly<T: Ord>(T);
trait SpecTrait<U> {
fn f();
}
impl<T, U> SpecTrait<U> for T {
default fn f() {}
}
impl<T: Ord> SpecTrait<()> for OrdOnly<T> {
fn f() {}
}
impl<T: Ord> SpecTrait<OrdOnly<T>> for () {
fn f() {}
}
impl<T: Ord, U: Ord, V: Ord> SpecTrait<(OrdOnly<T>, OrdOnly<U>)> for &[OrdOnly<V>] {
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,24 @@
// Test that projection bounds can't be specialized on.
#![feature(min_specialization)]
trait X {
fn f();
}
trait Id {
type This;
}
impl<T> Id for T {
type This = T;
}
impl<T: Id> X for T {
default fn f() {}
}
impl<I, V: Id<This = (I,)>> X for V {
//~^ ERROR cannot specialize on
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: cannot specialize on `Binder(ProjectionPredicate(ProjectionTy { substs: [V], item_def_id: DefId(0:6 ~ repeated_projection_type[317d]::Id[0]::This[0]) }, (I,)))`
--> $DIR/repeated_projection_type.rs:19:1
|
LL | / impl<I, V: Id<This = (I,)>> X for V {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error

View file

@ -0,0 +1,19 @@
// Test that directly specializing on repeated lifetime parameters is not
// allowed.
#![feature(min_specialization)]
trait X {
fn f();
}
impl<T> X for T {
default fn f() {}
}
impl<'a> X for (&'a u8, &'a u8) {
//~^ ERROR specializing impl repeats parameter `'a`
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: specializing impl repeats parameter `'a`
--> $DIR/repeating_lifetimes.rs:14:1
|
LL | / impl<'a> X for (&'a u8, &'a u8) {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error

View file

@ -0,0 +1,17 @@
// Test that specializing on two type parameters being equal is not allowed.
#![feature(min_specialization)]
trait X {
fn f();
}
impl<T> X for T {
default fn f() {}
}
impl<T> X for (T, T) {
//~^ ERROR specializing impl repeats parameter `T`
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: specializing impl repeats parameter `T`
--> $DIR/repeating_param.rs:12:1
|
LL | / impl<T> X for (T, T) {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error

View file

@ -0,0 +1,20 @@
// Check that we can specialize on a concrete iterator type. This requires us
// to consider which parameters in the parent impl are constrained.
// check-pass
#![feature(min_specialization)]
trait SpecFromIter<T> {
fn f(&self);
}
impl<'a, T: 'a, I: Iterator<Item = &'a T>> SpecFromIter<T> for I {
default fn f(&self) {}
}
impl<'a, T> SpecFromIter<T> for std::slice::Iter<'a, T> {
fn f(&self) {}
}
fn main() {}

View file

@ -0,0 +1,19 @@
// Check that lifetime parameters are allowed in specializing impls.
// check-pass
#![feature(min_specialization)]
trait MySpecTrait {
fn f();
}
impl<T> MySpecTrait for T {
default fn f() {}
}
impl<'a, T: ?Sized> MySpecTrait for &'a T {
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,17 @@
// Test that `rustc_unsafe_specialization_marker` is only allowed on marker traits.
#![feature(rustc_attrs)]
#[rustc_unsafe_specialization_marker]
trait SpecMarker {
fn f();
//~^ ERROR marker traits
}
#[rustc_unsafe_specialization_marker]
trait SpecMarker2 {
type X;
//~^ ERROR marker traits
}
fn main() {}

View file

@ -0,0 +1,15 @@
error[E0714]: marker traits cannot have associated items
--> $DIR/specialization_marker.rs:7:5
|
LL | fn f();
| ^^^^^^^
error[E0714]: marker traits cannot have associated items
--> $DIR/specialization_marker.rs:13:5
|
LL | type X;
| ^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0714`.

View file

@ -0,0 +1,18 @@
// Test that supertraits can't be assumed in impls of
// `rustc_specialization_trait`, as such impls would
// allow specializing on the supertrait.
#![feature(min_specialization)]
#![feature(rustc_attrs)]
#[rustc_specialization_trait]
trait SpecMarker: Default {
fn f();
}
impl<T: Default> SpecMarker for T {
//~^ ERROR cannot specialize
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: cannot specialize on trait `std::default::Default`
--> $DIR/specialization_super_trait.rs:13:1
|
LL | / impl<T: Default> SpecMarker for T {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error

View file

@ -0,0 +1,26 @@
// Test that `rustc_specialization_trait` requires always applicable impls.
#![feature(min_specialization)]
#![feature(rustc_attrs)]
#[rustc_specialization_trait]
trait SpecMarker {
fn f();
}
impl SpecMarker for &'static u8 {
//~^ ERROR cannot specialize
fn f() {}
}
impl<T> SpecMarker for (T, T) {
//~^ ERROR specializing impl
fn f() {}
}
impl<T: Clone> SpecMarker for [T] {
//~^ ERROR cannot specialize
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,29 @@
error: cannot specialize on `'static` lifetime
--> $DIR/specialization_trait.rs:11:1
|
LL | / impl SpecMarker for &'static u8 {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: specializing impl repeats parameter `T`
--> $DIR/specialization_trait.rs:16:1
|
LL | / impl<T> SpecMarker for (T, T) {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: cannot specialize on trait `std::clone::Clone`
--> $DIR/specialization_trait.rs:21:1
|
LL | / impl<T: Clone> SpecMarker for [T] {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to 3 previous errors

View file

@ -0,0 +1,24 @@
// Test that specializing on a `rustc_unsafe_specialization_marker` trait is
// allowed.
// check-pass
#![feature(min_specialization)]
#![feature(rustc_attrs)]
#[rustc_unsafe_specialization_marker]
trait SpecMarker {}
trait X {
fn f();
}
impl<T> X for T {
default fn f() {}
}
impl<T: SpecMarker> X for T {
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,27 @@
// Test that specializing on a `rustc_specialization_trait` trait is allowed.
// check-pass
#![feature(min_specialization)]
#![feature(rustc_attrs)]
#[rustc_specialization_trait]
trait SpecTrait {
fn g(&self);
}
trait X {
fn f(&self);
}
impl<T> X for T {
default fn f(&self) {}
}
impl<T: SpecTrait> X for T {
fn f(&self) {
self.g();
}
}
fn main() {}

View file

@ -0,0 +1,18 @@
// Test that directly specializing on `'static` is not allowed.
#![feature(min_specialization)]
trait X {
fn f();
}
impl<T> X for &'_ T {
default fn f() {}
}
impl X for &'static u8 {
//~^ ERROR cannot specialize on `'static` lifetime
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: cannot specialize on `'static` lifetime
--> $DIR/specialize_on_static.rs:13:1
|
LL | / impl X for &'static u8 {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error

View file

@ -0,0 +1,20 @@
// Test that specializing on a trait is not allowed in general.
#![feature(min_specialization)]
trait SpecMarker {}
trait X {
fn f();
}
impl<T> X for T {
default fn f() {}
}
impl<T: SpecMarker> X for T {
//~^ ERROR cannot specialize on trait `SpecMarker`
fn f() {}
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: cannot specialize on trait `SpecMarker`
--> $DIR/specialize_on_trait.rs:15:1
|
LL | / impl<T: SpecMarker> X for T {
LL | |
LL | | fn f() {}
LL | | }
| |_^
error: aborting due to previous error