the following test nearly triggers an incompleteness of alias-relate, which would result in overlapping impls getting accepted:
struct W<T: ?Sized>(*const T);
trait Bound<T: ?Sized> {}
impl Bound<W<u8>> for u8 {}
impl Bound<W<u16>> for u16 {}
trait Constrain<T: ?Sized> {
type Assoc: ?Sized;
}
impl<T: ?Sized> Constrain<W<T>> for u8
where
u8: Bound<W<T>>,
{
type Assoc = u8;
}
trait ToId {
type Assoc: ?Sized;
}
impl<T: ?Sized> ToId for T {
type Assoc = <T as Id>::This;
}
trait Id {
type This: ?Sized;
}
impl<T: ?Sized> Id for T {
type This = T;
}
trait Overlap<T> {}
impl<T: ?Sized> Overlap<<T as ToId>::Assoc> for T {}
impl<T: ?Sized> Overlap<<u8 as Constrain<W<T>>>::Assoc> for T {}
The idea is as follows: we prove AliasRelate(<?t as ToId>::Assoc, <u8 as Constrain<W<?t>>>::Assoc) during coherence. This does the following:
structurally normalize lhs to ?lhs with NormalizesTo(<?t as ToId>::Assoc, ?lhs)
structurally normalize rhs to u8, constraining ?t to u8.
At this point, evaluating the goals added by trying to structurally normalize <?t as ToId>::Assoc constrains ?lhs to <u8 as Id>::This, which is not a rigid alias. If we were to first evaluate added goals here and then structurally equate, this would result in an error, allowing these two impls to coexist even though they clearly overlap. Alternatively, changing commit_if_ok to inherit the nested goals of the parent would also enable this unsoundness. The underlying issue is that after structurally normalizing the lhs, we do not check whether it results in a normalizeable alias when considering the constraints from structurally normalizing the rhs.
I feel like it is likely this issue can be theoretically exploited. I am also confident that this cannot be hit by accident and is fairly straightforward to fix by changing NormalizesTo to recursively normalize the projected term. It currently only normalizes by one-step and requires AliasRelate to recursively normalize. This would change NormalizesTo from normalizing exactly one-step to normalizingh at least one step. It would therefore require using commit_if_ok in both AliasRelate and at the end of NormalizesTo.
the following test nearly triggers an incompleteness of alias-relate, which would result in overlapping impls getting accepted:
The idea is as follows: we prove
AliasRelate(<?t as ToId>::Assoc, <u8 as Constrain<W<?t>>>::Assoc)
during coherence. This does the following:?lhs
withNormalizesTo(<?t as ToId>::Assoc, ?lhs)
u8
, constraining?t
tou8
.At this point, evaluating the goals added by trying to structurally normalize
<?t as ToId>::Assoc
constrains?lhs
to<u8 as Id>::This
, which is not a rigid alias. If we were to first evaluate added goals here and then structurally equate, this would result in an error, allowing these two impls to coexist even though they clearly overlap. Alternatively, changingcommit_if_ok
to inherit the nested goals of the parent would also enable this unsoundness. The underlying issue is that after structurally normalizing thelhs
, we do not check whether it results in a normalizeable alias when considering the constraints from structurally normalizing therhs
.We currently instead reach the following branch, first constraining
?lhs
tou8
and then provingNormalizesTo(<u8 as ToId>::Assoc, u8)
. This uses semantic equality when relatingu8
with<u8 as Id>::This
, which succeeds. https://github.com/rust-lang/rust/blob/a0569fa8f91b5271e92d2f73fd252de7d3d05b9c/compiler/rustc_trait_selection/src/solve/alias_relate.rs#L56-L57I feel like it is likely this issue can be theoretically exploited. I am also confident that this cannot be hit by accident and is fairly straightforward to fix by changing
NormalizesTo
to recursively normalize the projected term. It currently only normalizes by one-step and requiresAliasRelate
to recursively normalize. This would changeNormalizesTo
from normalizing exactly one-step to normalizingh at least one step. It would therefore require usingcommit_if_ok
in bothAliasRelate
and at the end ofNormalizesTo
.