-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Check vtable projections for validity in miri #130727
base: master
Are you sure you want to change the base?
Conversation
Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri The Miri subtree was changed cc @rust-lang/miri Some changes occurred to the CTFE / Miri interpreter cc @rust-lang/miri |
This comment has been minimized.
This comment has been minimized.
0d6923d
to
ffa70dd
Compare
Some changes occurred in compiler/rustc_codegen_cranelift cc @bjorn3 Some changes occurred in compiler/rustc_codegen_gcc |
There's technically an alternative here: we could, perhaps, keep the Then for each projection pred in That doesn't seem as nice, though; I like just storing the full set of predicates in the vtable allocation lol. |
@@ -259,7 +258,7 @@ pub enum GlobalAlloc<'tcx> { | |||
/// The alloc ID is used as a function pointer. | |||
Function { instance: Instance<'tcx> }, | |||
/// This alloc ID points to a symbolic (not-reified) vtable. | |||
VTable(Ty<'tcx>, Option<ty::PolyExistentialTraitRef<'tcx>>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codegen fully identifies a vtable using just the Option<ty::PolyExistentialTraitRef<'tcx>>
:
rust/compiler/rustc_codegen_ssa/src/meth.rs
Line 118 in bdacdfe
pub(crate) fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>( |
So how can it be that Miri would require more than this? Is it just that currently it happens to be the case that vtables only depend on the principal (i.e., never depend on projections or auto traits) but we want to reserve the right to change this later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Codegen fully identifies a vtable using a Option<PolyExistentialTraitRef>
and a self Ty
.
Miri doesn't require the full set of predicates, either, technically, but computing the set of projections that correspond to a vtable requires also using the self type to compute what the projections would be. We could also pass that in to the validity, since it's stored in the allocation, but that would be pretty expensive to test a bunch of projection goals every time we want to validate vtable equality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record, that corresponds roughly to my alternative here:
I'm fine with making this UB. Codegen does rely on these vtables being the same (dropping auto traits in a cast compiles to a NOP), so it is crucial that this does not affect vtable layout... but Miri anyway doesn't have this cast short-cut, and we probably don't want to turn this into a hard guarantee.
Ah, so that explains why codegen can get away with just knowing the principal trait ref when generating the vtable -- it also knows the type, and it gets to assume that the type and the impl satisfy the remaining predicates. This alternative proposal would be equivalent for associated types, right? But for auto traits it would allow transmutes that remove auto traits, or those that add them when the underlying type actually satisfies the auto trait, but reject the case where the auto trait is not satisfied by the underlying type? I think I am fine with having maximal UB here, i.e. requiring the auto trait list to exactly match, which would require storing the full list of predicates. |
/// The vtable that was actually allocated into global memory. | ||
allocated_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, | ||
/// The vtable that was expected at the point in MIR that it was accessed. | ||
expected_vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"allocated" sounds odd here. IMO the previous distinction makes more sense -- the "expected" info comes from the type, and that is compared with the actual info stored in the vtable pointer. There is no "expected vtable", there's just an expected list of predicates. IOW, List<ty::PolyExistentialPredicate<'tcx>>
isn't a vtable, only GlobalAlloc::Vtable
is a vtable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I mostly had a problem with calling it a "trait". It's now not a trait, it's ... poly_existential_predicates which is a mouthful. I understand vtable is a misnomer here, but do you have a better suggestion for a name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "allocated" also here makes total sense, it's the vtable that was allocated in the first place. "expected" here is what we expected the vtable to end up as; that's the unsoundness.
It's like calling the type of an allocation its "allocated type" and the type you end up making an invalid read on some place as its "expected type". Again, I'm totally happy to rename this, but I'd rather not call it "trait" when it's no longer an (existential) trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe vtable → dyn type? After all this list is exactly all the things that go into a dyn Trait
type.
Then I'd use expected_dyn_type
and vtable_dyn_type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya that's fine 👍
let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder(); | ||
self.visit_def_id(def_id, "", &""); | ||
self.visit(args); | ||
} | ||
|
||
// FIXME: are we just skipping the auto traits here? | ||
// What is this reachability *for*? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a very detailed comment at the top of this file explaining what it is for.
@@ -18,19 +18,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |||
pub fn get_vtable_ptr( | |||
&self, | |||
ty: Ty<'tcx>, | |||
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, | |||
vtable: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't call this a "vtable". This is confusing the "information required to compute a vtable" with the resulting output, the vtable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a better name than trait_ref
, since that's no longer a valid name either?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a validity test, so it should go in fail/validity
. It should also have dyn
in its name like all the other dyn trait tests. wrong-dyn-trait-assoc-type.rs
would be a good name, IMO.
For the record, I think I'd like to split out validating vtable auto traits from this PR. I wouldn't be surprised if there are crates in the wild that are currently using transmutes to temporaily smuggle |
Currently, miri does not catch when we transmute
dyn Trait<Assoc = A>
todyn Trait<Assoc = B>
. This PR implements such a check, and fixes rust-lang/miri#3905.To do this, we modify
GlobalAlloc::VTable
to contain the whole list ofPolyExistentialPredicate
, and then modifycheck_vtable_for_type
to validate thePolyExistentialProjection
s of the vtable, along with the principal trait that was already being validated.cc @RalfJung
r? @lcnr or types
I also tweaked the diagnostics a bit.
Open question: We don't validate the auto traits. You can transmute
dyn Foo
intodyn Foo + Send
. Should we check that? We currently have a test that exercises this as not being UB:rust/src/tools/miri/tests/pass/dyn-upcast.rs
Lines 14 to 20 in 6c6d210
I'm not actually sure if we ever decided that's actually UB or not 🤔
We could perhaps still check that the underlying type of the object (i.e. the concrete type that was unsized) implements the auto traits, to catch UB like: