Skip to content

Fix/issue 152607 supertrait assoc type bound#156593

Open
spirali wants to merge 4 commits into
rust-lang:mainfrom
spirali:fix/issue-152607-supertrait-assoc-type-bound
Open

Fix/issue 152607 supertrait assoc type bound#156593
spirali wants to merge 4 commits into
rust-lang:mainfrom
spirali:fix/issue-152607-supertrait-assoc-type-bound

Conversation

@spirali

@spirali spirali commented May 15, 2026

Copy link
Copy Markdown
Contributor

This tries to fix #152607.
This PR is based on #153518.

  • I have added revisions for tests.
  • The original fix does not cover the dyn case, so I have tried to fix it on my own, but I am not sure about the correctness of this fix.

r? lcnr

akhilesharora and others added 3 commits May 14, 2026 17:00
…ormedness

When forming a trait object like `dyn Sub<Assoc = String>` where
`trait Sub: Super<Assoc: Copy>`, the compiler was not verifying that
`String: Copy`. This allowed unsound code to compile, potentially
leading to use-after-free vulnerabilities.

The root cause was that both the old and new trait solvers used
`explicit_super_predicates_of` which only yields `Self: SuperTrait`
predicates, but not the associated type bounds from those supertraits.
For example, `trait Sub: Super<Assoc: Copy>` expands to both
`Self: Super` and `<Self as Super>::Assoc: Copy`, but only the first
was being checked.

This change switches to `explicit_implied_predicates_of` in both:
- The old solver's `confirm_object_candidate`
- The new solver's `predicates_for_object_candidate`

This ensures that associated type bounds from supertraits are properly
verified when constructing trait objects.
@rustbot

rustbot commented May 15, 2026

Copy link
Copy Markdown
Collaborator

changes to the core type system

cc @lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels May 15, 2026
@rust-log-analyzer

This comment has been minimized.

Comment on lines +1001 to +1018
for (pred, _) in tcx
.explicit_implied_predicates_of(principal_def_id)
.iter_instantiated_copied(tcx, args)
.map(Unnormalized::skip_norm_wip)
{
if !pred.has_escaping_bound_vars()
&& !matches!(
pred.kind().skip_binder(),
ty::ClauseKind::Trait(tp) if tp.self_ty() == t
)
{
self.out.push(traits::Obligation::with_depth(
tcx,
self.cause(ObligationCauseCode::WellFormed(None)),
self.recursion_depth,
self.param_env,
pred,
));

@traviscross traviscross May 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider, e.g.:

trait Tr<X>: 'static {}
struct S<'a, X>(&'a dyn Tr<X>);

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what exactly do you mean? You refer to the impact of this on implied bounds?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested that filtering out ty::ClauseKind::TypeOutlives fixes this particular problem. I am not sure if we do not hit something else by this. If this is the correct fix, we should probably also filter ty::ClauseKind::RegionOutlives(..).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what exactly do you mean?

As @spirali inferred and addressed, this branch had regressed that case.

.explicit_implied_predicates_of(principal_def_id)
.iter_instantiated_copied(tcx, args)
.map(Unnormalized::skip_norm_wip)
{

@lcnr lcnr Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should already return these as part of the nominal_obligations and then filter them out, do we not?

I am not 100% sure about this, but would expect so. I don't have a good sense of whether we should filter them out, I feel like it depends on what we want to do with trait objects long term. Their current state is quite a mess sadly

This is not required for soundness, is it? as we do check these bounds when proving dyn Tr: Tr

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that I understand. I have originally tried to implement it in the nominal_obligations cycle, but then I do not know how to distinguish the case:

trait Trait<T: Sized> {}

fn foo(_: &dyn Trait<[u32]>) {}

AFAIK it should compile as we are just mentioning the type and not constructing it.

@lcnr lcnr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the late review, the last weeks have been quite busy :x

View changes since this review

@rust-log-analyzer

Copy link
Copy Markdown
Collaborator

The job aarch64-gnu-llvm-21-1 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
Saved the actual stderr to `/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/traits/next-solver/more-object-bound/more-object-bound.stderr`
diff of stderr:

3    |
4 LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
5    |                 ^^^^^^^^^^^^^^^^^^^^^^^ types differ
+ 
+ error[E0271]: type mismatch resolving `A == B`
+   --> $DIR/more-object-bound.rs:12:5
6    |
-    = note: required because it appears within the type `dyn Trait<A = A, B = B>`
- note: required by a bound in `foo`
-   --> $DIR/more-object-bound.rs:18:8
+ LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
+    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
+ 
+ error[E0271]: type mismatch resolving `A == B`
+   --> $DIR/more-object-bound.rs:12:5
10    |
- LL | fn foo<A, B, T: ?Sized>(x: T::A) -> B
-    |    --- required by a bound in this function
- LL | where
- LL |     T: Trait<B = B>,
-    |        ^^^^^^^^^^^^ required by this bound in `foo`
+ LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
+    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
16 
- error: aborting due to 1 previous error
+ error: aborting due to 3 previous errors
18 
---
-   --> /checkout/tests/ui/traits/next-solver/more-object-bound.rs:12:5
+ 
+ error[E0271]: type mismatch resolving `A == B`
+   --> $DIR/more-object-bound.rs:12:5
+ LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
+    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
+ 
+ error[E0271]: type mismatch resolving `A == B`
+   --> $DIR/more-object-bound.rs:12:5
+ LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
+    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
+ error: aborting due to 3 previous errors


The actual stderr differed from the expected stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args traits/next-solver/more-object-bound.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/traits/next-solver/more-object-bound.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2" "--target=aarch64-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/test/ui/traits/next-solver/more-object-bound" "-A" "unused" "-W" "unused_attributes" "-A" "internal_features" "-A" "incomplete_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/aarch64-unknown-linux-gnu/native/rust-test-helpers" "-Znext-solver"
stdout: none
--- stderr -------------------------------
error[E0271]: type mismatch resolving `A == B`
##[error]  --> /checkout/tests/ui/traits/next-solver/more-object-bound.rs:12:17
   |
LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
   |                 ^^^^^^^^^^^^^^^^^^^^^^^ types differ

error[E0271]: type mismatch resolving `A == B`
##[error]  --> /checkout/tests/ui/traits/next-solver/more-object-bound.rs:12:5
   |
LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ

error[E0271]: type mismatch resolving `A == B`
##[error]  --> /checkout/tests/ui/traits/next-solver/more-object-bound.rs:12:5
   |
LL |     foo::<A, B, dyn Trait<A = A, B = B>>(x)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0271`.

@lcnr

lcnr commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Sorry for not getting to this more quickly..

I don't think we have a good sense of what the WF requirements of trait objects should be

I don't know about the example by TC, but feel like that example probably should error if we check requirements for trait objects.

trait Tr<X>: 'static {}
struct S<X>(*const dyn Tr<X>);

wf.rs is currently used both to compute implied bounds and to compute stuff we have to check, so adding more obligations here probably also gives us more implied bounds. I think your PR makes the following compile right now?

use std::any::Any;
trait Super {
    type Assoc;
}

trait Trait: Super<Assoc: 'static> {}

fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
    Box::new(t)
}

Or well, if you filter out outlives bounds, you could do

use std::any::Any;
trait Super {
    type Assoc;
}

trait IsStatic: 'static {}
trait Trait: Super<Assoc: IsStatic> {}

fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
    Box::new(t)
}

if the second one compiles with your change, can you add them as tests?

I feel like it's safer for now to not change the well-formedness requirements for trait objects. It is sufficient to check that all their bounds hold when checking dyn Trait: Trait. I don't have a clear idea of what I want for trait objects long-term, but I think for now this is significantly easier.

@spirali

spirali commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Both examples do not compile. As far as I understand, to allow this case, it would need to implement RFC 2089 to propagate implied bounds.

Error message for the first one:

error[E0310]: the parameter type `T` may not live long enough
 --> mytest/test3.rs:9:5
  |
8 | fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
  |                                                      ------- this `dyn Trait` has an implicit `'static` lifetime bound
9 |     Box::new(t)
  |     ^^^^^^^^^^^
  |     |
  |     the parameter type `T` must be valid for the static lifetime...
  |     ...so that the type `T` will meet its required lifetime bounds
  |
help: consider adding an explicit lifetime bound
  |
8 | fn foo<T: 'static>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
  |         +++++++++

Error message for the second one:

error[E0277]: the trait bound `T: IsStatic` is not satisfied
 --> mytest/test3.rs:9:19
  |
9 | fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
  |                   ^^^^^^^^^^^^^^^^^^^^ the trait `IsStatic` is not implemented for `T`
  |
help: consider restricting type parameter `T` with trait `IsStatic`
  |
9 | fn foo<T: IsStatic>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
  |         ++++++++++

error[E0310]: the parameter type `T` may not live long enough
  --> mytest/test3.rs:10:5
   |
 9 | fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
   |                                                      ------- this `dyn Trait` has an implicit `'static` lifetime bound
10 |     Box::new(t)
   |     ^^^^^^^^^^^
   |     |
   |     the parameter type `T` must be valid for the static lifetime...
   |     ...so that the type `T` will meet its required lifetime bounds
   |

@lcnr

lcnr commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

I really expected the second one to compile 🤔 ah, the second one fails because we do not imply the trait bound. so while we imply T: 'static by elaborating T: IsStatic, we don't imply T: IsStatic.

Does the following compile with your change? I think returning T: IsStatic from wf.rs for dyn Trait<Assoc = T> should be elaborated to T: 'static which then allows us to use the impl to prove T: IsStatic, even though that one is not added as an implied bound

use std::any::Any;
trait Super {
    type Assoc;
}

trait IsStatic: 'static {}
impl<T: 'static> IsStatic for T {}

trait Trait: Super<Assoc: IsStatic> {}

fn foo<T>(x: *mut dyn Trait<Assoc = T>, t: T) -> Box<dyn Any> {
    Box::new(t)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Associated type bound from supertrait isn't checked for trait object

6 participants