-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Trait predicate evaluation cache incorrectly handles EvaluatedToOk #80691
Comments
With #83007 landing, this is going to start causing ICEs (since the query result is actually changing across compilatioon sessions). I'm still not sure if this can actually cause unsoundness, but it should be fixed anyway, since it's a bug in a pretty subtle part of the trait system. cc @eddyb @matthewjasper - I'm not familiar enough with the relevant code to be sure of how to fix this. |
I wouldn't know where to look (or rather, I only know the vague shape of all of this, but not enough details) and would defer to @nikomatsakis or @matthewjasper, myself. The only thing I can think of is we should blanket use |
The same issue happens on https://github.com/substrate-developer-hub/substrate-node-template. |
@lqd managed to create a minimized reproducer in #83115 (comment): pub trait Interner {
type InternedVariableKinds;
}
trait RustIrDatabase<I: Interner> {
fn associated_ty_data(&self) -> AssociatedTyDatum<I>;
fn impl_datum(&self) -> ImplDatum<I>;
}
trait Fold<I: Interner> {
type Result;
}
impl<T, I: Interner> Fold<I> for Binders<T>
where
T: HasInterner<Interner = I> + Fold<I>,
<T as Fold<I>>::Result: HasInterner<Interner = I>,
I: Interner,
{
type Result = Binders<T::Result>;
}
impl<I: Interner> Fold<I> for WhereClause<I> {
type Result = Binders<WhereClause<I>>;
}
trait HasInterner {
type Interner: Interner;
}
impl<T: HasInterner> HasInterner for Vec<T> {
type Interner = T::Interner;
}
impl<T: HasInterner + ?Sized> HasInterner for &T {
type Interner = T::Interner;
}
pub struct VariableKind<I: Interner> {
_marker: std::marker::PhantomData<I>,
}
struct VariableKinds<I: Interner> {
_interned: I::InternedVariableKinds,
}
struct WhereClause<I: Interner> {
_marker: std::marker::PhantomData<I>,
}
impl<I: Interner> HasInterner for WhereClause<I> {
type Interner = I;
}
struct Binders<T> {
_marker: std::marker::PhantomData<T>,
}
impl<T: HasInterner> HasInterner for Binders<T> {
type Interner = T::Interner;
}
impl<T> Binders<&T> {
fn cloned(self) -> Binders<T> {
unimplemented!()
}
}
impl<T: HasInterner> Binders<T> {
fn map_ref<'a, U, OP>(&'a self, _op: OP) -> Binders<U>
where
OP: FnOnce(&'a T) -> U,
U: HasInterner<Interner = T::Interner>,
{
unimplemented!()
}
}
impl<T, I: Interner> Binders<T>
where
T: Fold<I> + HasInterner<Interner = I>,
I: Interner,
{
fn substitute(self) -> T::Result {
unimplemented!()
}
}
impl<V, U> IntoIterator for Binders<V>
where
V: HasInterner + IntoIterator<Item = U>,
U: HasInterner<Interner = V::Interner>,
{
type Item = Binders<U>;
type IntoIter = BindersIntoIterator<V>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
struct BindersIntoIterator<V: HasInterner> {
_binders: VariableKinds<V::Interner>,
}
impl<V> Iterator for BindersIntoIterator<V>
where
V: HasInterner + IntoIterator,
<V as IntoIterator>::Item: HasInterner<Interner = V::Interner>,
{
type Item = Binders<<V as IntoIterator>::Item>;
fn next(&mut self) -> Option<Self::Item> {
unimplemented!()
}
}
struct ImplDatum<I: Interner> {
binders: Binders<ImplDatumBound<I>>,
}
struct ImplDatumBound<I: Interner> {
where_clauses: Vec<Binders<WhereClause<I>>>,
}
impl<I: Interner> HasInterner for ImplDatumBound<I> {
type Interner = I;
}
struct AssociatedTyDatum<I: Interner> {
binders: Binders<AssociatedTyDatumBound<I>>,
}
struct AssociatedTyDatumBound<I: Interner> {
where_clauses: Vec<Binders<WhereClause<I>>>,
}
impl<I: Interner> HasInterner for AssociatedTyDatumBound<I> {
type Interner = I;
}
struct ClauseBuilder<'me, I: Interner> {
db: &'me dyn RustIrDatabase<I>,
}
impl<'me, I: Interner> ClauseBuilder<'me, I> {
fn new() -> Self {
unimplemented!()
}
fn push_clause(&mut self, _conditions: impl Iterator<Item = Binders<Binders<WhereClause<I>>>>) {
unimplemented!()
}
}
pub(crate) struct Forest<I: Interner> {
_marker: std::marker::PhantomData<I>,
}
impl<I: Interner> Forest<I> {
fn iter_answers<'f>(&'f self) {
let builder = &mut ClauseBuilder::<I>::new();
let impl_datum = builder.db.impl_datum();
let impl_where_clauses = impl_datum
.binders
.map_ref(|b| &b.where_clauses)
.into_iter()
.map(|wc| wc.cloned().substitute());
let associated_ty = builder.db.associated_ty_data();
let assoc_ty_where_clauses = associated_ty
.binders
.map_ref(|b| &b.where_clauses)
.into_iter()
.map(|wc| wc.cloned().substitute());
builder.push_clause(impl_where_clauses.chain(assoc_ty_where_clauses));
}
}
pub struct SLGSolver {
pub(crate) forest: Forest<ChalkIr>,
}
impl SLGSolver {
fn new() -> Self {
unimplemented!()
}
fn solve_multiple(&self) {
let _answers = self.forest.iter_answers();
}
}
pub struct ChalkIr;
impl Interner for ChalkIr {
type InternedVariableKinds = Vec<VariableKind<ChalkIr>>;
}
fn main() {
let solver = SLGSolver::new();
solver.solve_multiple();
} |
…komatsakis Use `EvaluatedToOkModuloRegions` whenever we erase regions Fixes rust-lang#80691 When we evaluate a trait predicate, we convert an `EvaluatedToOk` result to `EvaluatedToOkModuloRegions` if we erased any regions. We cache the result under a region-erased 'freshened' predicate, so `EvaluatedToOk` may not be correct for other predicates that have the same cache key.
Also removes expect_none usages as it has been removed and recommended to be replaced with asserts by the compiler devs
Split out from the discussion in #80336. See #80336 (comment) for the original comments.
The issue is caused by the way that we handle regions in the evaluation cache. When we insert a result into the evaluation cache (either
infcx.evaluation_cache
ortcx.evaluation_cache
, we use a 'freshened' version of the originalTraitPredicate
as the key. The 'freshened'TraitPredicate
has all non-late-bound regions erased.Unfortunately, this can lead to issues if we try to evaluate the following two predicates:
When we evaluate
SomeType<'static> as SomeTrait
, we'll getEvaluatedToOk
, since the region parameter in our trait ref is known to match the impl. We will then cache the result as<SomeType<'erased> as SomeTrait> -> EvaluatedToOk
.If we later try to evaluate
SomeType<'#_r> as SomeTrait
, we will end up matching the evaluation cache entry, giving us a result ofEvaluatedToOk
. However, we should have gotten a result ofEvaluatedToOkModuloRegions
, since we don't know that'#_r == 'static
holds.This is really difficult to observe in practice, for a number of reasons:
FulfillmentContext
.evaluate_obligation
query, which canonicalizes the regions in the input trait ref. To end up trying to evaluateSomeType<'static> as SomeTrait
, we need to end up callingevaluate_predicate_recursively
on it, with a different original trait ref used in the original query.EvaluatedToOkModuloRegions
instead ofEvaluatedToOk
only seems to cause an error when it results in the incremental hash changing (I don't know if it's possible to weaponize this into extending a lifetime).As a result, I haven't been able to minimize this.
The text was updated successfully, but these errors were encountered: