-
-
Notifications
You must be signed in to change notification settings - Fork 14.8k
DispatchFromDyn: require additional checks #149068
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -203,17 +203,15 @@ fn visit_implementation_of_dispatch_from_dyn(checker: &Checker<'_>) -> Result<() | |
| let tcx = checker.tcx; | ||
| let impl_did = checker.impl_def_id; | ||
| let trait_ref = checker.impl_header.trait_ref.instantiate_identity(); | ||
| assert!(tcx.is_lang_item(trait_ref.def_id, LangItem::DispatchFromDyn)); | ||
| let dispatch_from_dyn_trait_did = trait_ref.def_id; | ||
| debug!("visit_implementation_of_dispatch_from_dyn: impl_did={:?}", impl_did); | ||
|
|
||
| let span = tcx.def_span(impl_did); | ||
| let trait_name = "DispatchFromDyn"; | ||
|
|
||
| let source = trait_ref.self_ty(); | ||
| let target = { | ||
| assert!(tcx.is_lang_item(trait_ref.def_id, LangItem::DispatchFromDyn)); | ||
|
|
||
| trait_ref.args.type_at(1) | ||
| }; | ||
| let mut source = trait_ref.self_ty(); | ||
| let mut target = trait_ref.args.type_at(1); | ||
|
|
||
| // Check `CoercePointee` impl is WF -- if not, then there's no reason to report | ||
| // redundant errors for `DispatchFromDyn`. This is best effort, though. | ||
|
|
@@ -242,138 +240,173 @@ fn visit_implementation_of_dispatch_from_dyn(checker: &Checker<'_>) -> Result<() | |
| // trait, they *do* satisfy the repr(transparent) rules, and then we assume that everything else | ||
| // in the compiler (in particular, all the call ABI logic) will treat them as repr(transparent) | ||
| // even if they do not carry that attribute. | ||
| match (source.kind(), target.kind()) { | ||
| (&ty::Pat(_, pat_a), &ty::Pat(_, pat_b)) => { | ||
| if pat_a != pat_b { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceSamePatKind { | ||
| span, | ||
| trait_name, | ||
| pat_a: pat_a.to_string(), | ||
| pat_b: pat_b.to_string(), | ||
| })); | ||
| let mut behind_ref = false; | ||
| loop { | ||
|
Member
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. I don't quite like implementing this as a loop. I think, change this match into a function is better. I would also have arms like (Ref(..), Ref(..)) if behind_ref => { error },
(Ref(..), Ref(..)) if ... => { ... },
...
``
instead of have them all "fallthrough" to the `_` case. |
||
| match (source.kind(), target.kind()) { | ||
| (&ty::Pat(ty_a, pat_a), &ty::Pat(ty_b, pat_b)) => { | ||
| if pat_a != pat_b { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceSamePatKind { | ||
| span, | ||
| trait_name, | ||
| pat_a: pat_a.to_string(), | ||
| pat_b: pat_b.to_string(), | ||
| })); | ||
| } | ||
| source = ty_a; | ||
| target = ty_b; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| (&ty::Ref(r_a, _, mutbl_a), ty::Ref(r_b, _, mutbl_b)) | ||
| if r_a == *r_b && mutbl_a == *mutbl_b => | ||
| { | ||
| Ok(()) | ||
| } | ||
| (&ty::RawPtr(_, a_mutbl), &ty::RawPtr(_, b_mutbl)) if a_mutbl == b_mutbl => Ok(()), | ||
| (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) | ||
| if def_a.is_struct() && def_b.is_struct() => | ||
| { | ||
| if def_a != def_b { | ||
| let source_path = tcx.def_path_str(def_a.did()); | ||
| let target_path = tcx.def_path_str(def_b.did()); | ||
| return Err(tcx.dcx().emit_err(errors::CoerceSameStruct { | ||
| span, | ||
| trait_name, | ||
| note: true, | ||
| source_path, | ||
| target_path, | ||
| })); | ||
| (&ty::Ref(r_a, ty_a, mutbl_a), &ty::Ref(r_b, ty_b, mutbl_b)) | ||
| if !behind_ref && r_a == r_b && mutbl_a == mutbl_b => | ||
| { | ||
| source = ty_a; | ||
| target = ty_b; | ||
| behind_ref = true; | ||
| } | ||
|
|
||
| if def_a.repr().c() || def_a.repr().packed() { | ||
| return Err(tcx.dcx().emit_err(errors::DispatchFromDynRepr { span })); | ||
| (&ty::RawPtr(ty_a, a_mutbl), &ty::RawPtr(ty_b, b_mutbl)) | ||
| if !behind_ref && a_mutbl == b_mutbl => | ||
| { | ||
| source = ty_a; | ||
| target = ty_b; | ||
| behind_ref = true; | ||
|
Member
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. Similarly, test for this. |
||
| } | ||
| (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) | ||
| if !behind_ref && def_a.is_struct() && def_b.is_struct() => | ||
| { | ||
| if def_a != def_b { | ||
| let source_path = tcx.def_path_str(def_a.did()); | ||
| let target_path = tcx.def_path_str(def_b.did()); | ||
| return Err(tcx.dcx().emit_err(errors::CoerceSameStruct { | ||
| span, | ||
| trait_name, | ||
| note: true, | ||
| source_path, | ||
| target_path, | ||
| })); | ||
| } | ||
|
|
||
| let fields = &def_a.non_enum_variant().fields; | ||
|
|
||
| let mut res = Ok(()); | ||
| let coerced_fields = fields | ||
| .iter_enumerated() | ||
| .filter_map(|(i, field)| { | ||
| // Ignore PhantomData fields | ||
| let unnormalized_ty = tcx.type_of(field.did).instantiate_identity(); | ||
| if tcx | ||
| .try_normalize_erasing_regions( | ||
| ty::TypingEnv::non_body_analysis(tcx, def_a.did()), | ||
| unnormalized_ty, | ||
| ) | ||
| .unwrap_or(unnormalized_ty) | ||
| .is_phantom_data() | ||
| { | ||
| return None; | ||
| } | ||
| if def_a.repr().c() || def_a.repr().packed() { | ||
| return Err(tcx.dcx().emit_err(errors::DispatchFromDynRepr { span })); | ||
| } | ||
|
|
||
| let ty_a = field.ty(tcx, args_a); | ||
| let ty_b = field.ty(tcx, args_b); | ||
|
|
||
| // FIXME: We could do normalization here, but is it really worth it? | ||
| if ty_a == ty_b { | ||
| // Allow 1-ZSTs that don't mention type params. | ||
| // | ||
| // Allowing type params here would allow us to possibly transmute | ||
| // between ZSTs, which may be used to create library unsoundness. | ||
| if let Ok(layout) = | ||
| tcx.layout_of(infcx.typing_env(param_env).as_query_input(ty_a)) | ||
| && layout.is_1zst() | ||
| && !ty_a.has_non_region_param() | ||
| let fields = &def_a.non_enum_variant().fields; | ||
|
|
||
| let coerced_fields: Vec<_> = fields | ||
| .iter_enumerated() | ||
| .filter_map(|(i, field)| { | ||
| // Ignore PhantomData fields | ||
| let unnormalized_ty = tcx.type_of(field.did).instantiate_identity(); | ||
| if tcx | ||
| .try_normalize_erasing_regions( | ||
| ty::TypingEnv::non_body_analysis(tcx, def_a.did()), | ||
| unnormalized_ty, | ||
| ) | ||
| .unwrap_or(unnormalized_ty) | ||
| .is_phantom_data() | ||
| { | ||
| // ignore 1-ZST fields | ||
| return None; | ||
| } | ||
|
|
||
| res = Err(tcx.dcx().emit_err(errors::DispatchFromDynZST { | ||
| span, | ||
| name: field.ident(tcx), | ||
| ty: ty_a, | ||
| })); | ||
| let ty_a = field.ty(tcx, args_a); | ||
| let ty_b = field.ty(tcx, args_b); | ||
|
|
||
| // FIXME: We could do normalization here, but is it really worth it? | ||
| if ty_a == ty_b { | ||
| // Allow 1-ZSTs that don't mention type params. | ||
| // | ||
| // Allowing type params here would allow us to possibly transmute | ||
| // between ZSTs, which may be used to create library unsoundness. | ||
| if let Ok(layout) = | ||
| tcx.layout_of(infcx.typing_env(param_env).as_query_input(ty_a)) | ||
| && layout.is_1zst() | ||
| && !ty_a.has_non_region_param() | ||
| { | ||
| // ignore 1-ZST fields | ||
| return None; | ||
| } | ||
|
|
||
| None | ||
| } else { | ||
| Some((i, ty_a, ty_b, tcx.def_span(field.did))) | ||
| Some(Err(tcx.dcx().emit_err(errors::DispatchFromDynZST { | ||
| span, | ||
| name: field.ident(tcx), | ||
| ty: ty_a, | ||
| }))) | ||
| } else { | ||
| Some(Ok((i, ty_a, ty_b, tcx.def_span(field.did)))) | ||
| } | ||
| }) | ||
| .collect::<Result<_, _>>()?; | ||
|
|
||
| if coerced_fields.is_empty() { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceNoField { | ||
| span, | ||
| trait_name, | ||
| note: true, | ||
| })); | ||
| } | ||
| if let &[(_, ty_a, ty_b, field_span)] = &coerced_fields[..] { | ||
| let ocx = ObligationCtxt::new_with_diagnostics(&infcx); | ||
| ocx.register_obligation(Obligation::new( | ||
| tcx, | ||
| cause.clone(), | ||
| param_env, | ||
| ty::TraitRef::new(tcx, dispatch_from_dyn_trait_did, [ty_a, ty_b]), | ||
| )); | ||
| let errors = ocx.evaluate_obligations_error_on_ambiguity(); | ||
| if !errors.is_empty() { | ||
| if is_from_coerce_pointee_derive(tcx, span) { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceFieldValidity { | ||
| span, | ||
| trait_name, | ||
| ty: trait_ref.self_ty(), | ||
| field_span, | ||
| field_ty: ty_a, | ||
| })); | ||
| } else { | ||
| return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); | ||
| } | ||
| } | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| res?; | ||
|
|
||
| if coerced_fields.is_empty() { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceNoField { | ||
| // Finally, resolve all regions. | ||
| ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; | ||
|
|
||
| return Ok(()); | ||
| } | ||
| return Err(tcx.dcx().emit_err(errors::CoerceMulti { | ||
| span, | ||
| trait_name, | ||
| note: true, | ||
| number: coerced_fields.len(), | ||
| fields: coerced_fields.iter().map(|(_, _, _, s)| *s).collect::<Vec<_>>().into(), | ||
| })); | ||
| } else if let &[(_, ty_a, ty_b, field_span)] = &coerced_fields[..] { | ||
| } | ||
| (ty::Param(_), ty::Param(_)) => { | ||
|
Member
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. I guess, the test here is just the I guess, this technically allows |
||
| let ocx = ObligationCtxt::new_with_diagnostics(&infcx); | ||
| ocx.register_obligation(Obligation::new( | ||
| tcx, | ||
| cause.clone(), | ||
| param_env, | ||
| ty::TraitRef::new(tcx, trait_ref.def_id, [ty_a, ty_b]), | ||
| ty::TraitRef::new( | ||
| tcx, | ||
| tcx.require_lang_item(LangItem::Unsize, span), | ||
| [source, target], | ||
| ), | ||
| )); | ||
| let errors = ocx.evaluate_obligations_error_on_ambiguity(); | ||
| if !errors.is_empty() { | ||
| if is_from_coerce_pointee_derive(tcx, span) { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceFieldValidity { | ||
| span, | ||
| trait_name, | ||
| ty: trait_ref.self_ty(), | ||
| field_span, | ||
| field_ty: ty_a, | ||
| })); | ||
| } else { | ||
| return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); | ||
| } | ||
| return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); | ||
| } | ||
|
|
||
| // Finally, resolve all regions. | ||
| ocx.resolve_regions_and_report_errors(impl_did, param_env, [])?; | ||
|
|
||
| Ok(()) | ||
| } else { | ||
| return Err(tcx.dcx().emit_err(errors::CoerceMulti { | ||
| span, | ||
| trait_name, | ||
| number: coerced_fields.len(), | ||
| fields: coerced_fields.iter().map(|(_, _, _, s)| *s).collect::<Vec<_>>().into(), | ||
| })); | ||
| return Ok(()); | ||
| } | ||
| _ => { | ||
| if behind_ref { | ||
| return Err(tcx.dcx().emit_err(errors::DispatchFromDynMultiRefs { span })); | ||
| } else { | ||
|
Comment on lines
+401
to
+403
Member
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. Hmm, not sure I like this implementation. But let me comment above. |
||
| return Err(tcx | ||
| .dcx() | ||
| .emit_err(errors::CoerceUnsizedNonStruct { span, trait_name })); | ||
| } | ||
| } | ||
| } | ||
| _ => Err(tcx.dcx().emit_err(errors::CoerceUnsizedNonStruct { span, trait_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.
Why does this text not show up in the test error output?
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 forgot about a
#[note]attribute on the error type. Now it should work.