Repository navigation
Avoid leaking opaque hidden types via auto trait candidates #159589
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
Changes from all commits
abc0117
4d62602
9c18639
b3d3ab1
d86652a
bae45db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt; | |
| use rustc_type_ir::inherent::*; | ||
| use rustc_type_ir::lang_items::SolverTraitLangItem; | ||
| use rustc_type_ir::solve::{ | ||
| AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, MaybeInfo, | ||
| AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, ExternalConstraintsData, MaybeInfo, | ||
| NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunNonErased, | ||
| RerunReason, RerunResultExt, SizedTraitKind, | ||
| }; | ||
|
|
@@ -236,19 +236,11 @@ where | |
| // when merging candidates anyways. | ||
| // | ||
| // See tests/ui/impl-trait/auto-trait-leakage/avoid-query-cycle-via-item-bound.rs. | ||
| if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, .. }) = | ||
| if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) = | ||
| goal.predicate.self_ty().kind() | ||
| { | ||
| debug_assert!(is_rigid == ty::IsRigid::Yes); | ||
|
|
||
| for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() { | ||
| if item_bound | ||
| .as_trait_clause() | ||
| .is_some_and(|b| b.def_id() == goal.predicate.def_id()) | ||
| { | ||
| return Err(NoSolution.into()); | ||
| } | ||
| } | ||
| return ecx.consider_auto_trait_candidate_for_opaque_ty(goal, def_id, args); | ||
| } | ||
|
|
||
| // We need to make sure to stall any coroutines we are inferring to avoid query cycles. | ||
|
|
@@ -1286,6 +1278,77 @@ where | |
| .enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)) | ||
| } | ||
|
|
||
| fn consider_auto_trait_candidate_for_opaque_ty( | ||
| &mut self, | ||
| goal: Goal<I, TraitClause<I>>, | ||
| def_id: I::OpaqueTyId, | ||
| args: I::GenericArgs, | ||
| ) -> Result<Candidate<I>, NoSolutionOrRerunNonErased> { | ||
| let cx = self.cx(); | ||
| let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc); | ||
|
|
||
| for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() { | ||
| if item_bound.as_trait_clause().is_some_and(|b| b.def_id() == goal.predicate.def_id()) { | ||
| return Err(NoSolution.into()); | ||
| } | ||
| } | ||
|
|
||
| let candidate = self.probe_trait_candidate(source).enter(|ecx| { | ||
| let hidden_ty = cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(); | ||
| ecx.add_goal( | ||
| GoalSource::ImplWhereBound, | ||
| goal.with(cx, goal.predicate.with_replaced_self_ty(cx, hidden_ty)), | ||
| )?; | ||
| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) | ||
| })?; | ||
|
|
||
| // Proving an auto trait for the hidden type must not constrain inference | ||
| // variables, as that would leak the hidden type itself. | ||
| if !candidate.result.value.var_values.is_identity_modulo_regions() { | ||
| return self.forced_ambiguity(MaybeInfo::AMBIGUOUS); | ||
| } | ||
|
|
||
| let ExternalConstraintsData { | ||
| region_constraints: _, | ||
| ref opaque_types, | ||
| ref normalization_nested_goals, | ||
| } = *candidate.result.value.external_constraints; | ||
| debug_assert!(normalization_nested_goals.is_empty()); | ||
|
|
||
| // New defining uses may leak an opaque's hidden type into the caller's | ||
| // inference state. This is safe after typeck, where hidden types are already | ||
| // fixed and only regions are inferred, but not during typeck while hidden | ||
| // types may still contain inference variables. | ||
| if !opaque_types.is_empty() { | ||
| let typing_mode = self.typing_mode(); | ||
|
|
||
| match typing_mode { | ||
| // We're inferring regions of opaque types, but | ||
| // the type itself is already fully known, no way | ||
| // to leak the hidden type. | ||
| TypingMode::PostTypeckUntilBorrowck { .. } => {} | ||
| // We're inferring the hidden type of opaques, could | ||
| // leak types through it. | ||
| TypingMode::Typeck { .. } => { | ||
| return self.forced_ambiguity(MaybeInfo::AMBIGUOUS); | ||
| } | ||
| // we never add new uses to the opaque type storage | ||
| TypingMode::Coherence | ||
| | TypingMode::PostBorrowck { .. } | ||
| | TypingMode::Reflection | ||
| | TypingMode::PostAnalysis | ||
| | TypingMode::Codegen | ||
| | TypingMode::ErasedNotCoherence(MayBeErased) => { | ||
| unreachable!( | ||
| "we never add new uses to opaque types in typing mode {typing_mode:?}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feel like this could be written in a slightly cleaner way 🤔 we could definitely just And then maybe explicitly destructure if !candidate.result.var_values.is_identity_modulo_regions() {
return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
}
let ExternalConstraintData {
region_constraints: _,
opaque_types,
normalization_nested_goals,
} = candidate.result.external_constraints;
debug_assert!(normalization_nested_goals.is_empty());
if !opaque_types.is_empty() {
blub
}I feel like the way
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is much better. Added |
||
|
|
||
| Ok(candidate) | ||
| } | ||
|
|
||
| // Return `Some` if there is an impl (built-in or user provided) that may | ||
| // hold for the self type of the goal, which for coherence and soundness | ||
| // purposes must disqualify the built-in auto impl assembled by considering | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // External crate for `opaque-hidden-ty-inference.rs`. | ||
| // | ||
| // `define` returns `WaddupGamers<T, closure>`. Its `Unpin` impl only holds if | ||
| // `<T as Leak>::Assoc` is that closure. The test's caller says `Assoc` is its | ||
| // own opaque type instead, so checking `Unpin` would try to set that opaque | ||
| // type to this closure. | ||
| pub struct WaddupGamers<T, U>(Option<T>, U); | ||
|
|
||
| impl<T: Leak<Assoc = U>, U> Unpin for WaddupGamers<T, U> {} | ||
|
|
||
| pub trait Leak { | ||
| type Assoc; | ||
| } | ||
|
|
||
| impl<T> Leak for T { | ||
| type Assoc = T; | ||
| } | ||
|
|
||
| pub fn define<T>() -> impl Sized { | ||
| WaddupGamers(None::<T>, || ()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //@ ignore-compare-mode-next-solver | ||
| //@ compile-flags: -Znext-solver | ||
| //@ aux-build:opaque-auto-trait-leakage.rs | ||
|
|
||
| //! Regression test for https://github.com/rust-lang/rust/issues/134578. | ||
| //! Leaking the auto traits of a foreign opaque must not constrain inference | ||
| //! variables in the caller, as that would leak the hidden type itself. Here | ||
| //! that would constrain `NameMe<T>` to a closure from the auxiliary crate and | ||
| //! ICE in typeck. The hidden type may still show up in the diagnostic. | ||
|
|
||
| #![feature(type_alias_impl_trait)] | ||
| #![allow(unused)] | ||
|
|
||
| extern crate opaque_auto_trait_leakage as dep; | ||
|
|
||
| use dep::*; | ||
|
|
||
| fn require_auto<T: Unpin>(x: T) -> T { | ||
| x | ||
| } | ||
|
|
||
| type NameMe<T> = impl Sized; | ||
|
|
||
| #[define_opaque(NameMe)] | ||
| fn leak<T>() -> NameMe<T> | ||
| where | ||
| T: Leak<Assoc = NameMe<T>>, | ||
| { | ||
| // Proving `impl Sized: Unpin` must not constrain `NameMe<T>` | ||
| // to the foreign closure hidden inside `define`. | ||
| let opaque = require_auto(define::<T>()); | ||
| //~^ ERROR type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}` | ||
| let closure; | ||
| loop {} | ||
| return closure; | ||
| } | ||
|
|
||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| error[E0271]: type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}` | ||
| --> $DIR/opaque-hidden-ty-inference.rs:31:31 | ||
| | | ||
| LL | let opaque = require_auto(define::<T>()); | ||
| | ------------ ^^^^^^^^^^^^^ expected closure, found `!` | ||
| | | | ||
| | required by a bound introduced by this call | ||
| | | ||
| ::: $DIR/auxiliary/opaque-auto-trait-leakage.rs:20:29 | ||
| | | ||
| LL | WaddupGamers(None::<T>, || ()) | ||
| | -- the expected closure | ||
| | | ||
| = note: expected closure `{closure@dep::define<T>::{closure#0}}` | ||
| found type `!` | ||
| = note: required for `WaddupGamers<T, {closure@dep::define<T>::{closure#0}}>` to implement `Unpin` | ||
| note: required because it appears within the type `impl Sized` | ||
| --> $DIR/auxiliary/opaque-auto-trait-leakage.rs:19:23 | ||
| | | ||
| LL | pub fn define<T>() -> impl Sized { | ||
| | ^^^^^^^^^^ | ||
| note: required by a bound in `require_auto` | ||
| --> $DIR/opaque-hidden-ty-inference.rs:18:20 | ||
| | | ||
| LL | fn require_auto<T: Unpin>(x: T) -> T { | ||
| | ^^^^^ required by this bound in `require_auto` | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0271`. |
Uh oh!
There was an error while loading. Please reload this page.