| //! Check whether a type has (potentially) non-trivial drop glue. | |
| use rustc_data_structures::Limit; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_hir::def_id::DefId; | |
| use rustc_hir::find_attr; | |
| use rustc_middle::bug; | |
| use rustc_middle::query::Providers; | |
| use rustc_middle::ty::util::{AlwaysRequiresDrop, needs_drop_components}; | |
| use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt, Unnormalized}; | |
| use tracing::{debug, instrument}; | |
| use crate::diagnostics::NeedsDropOverflow; | |
| type NeedsDropResult<T> = Result<T, AlwaysRequiresDrop>; | |
| fn needs_drop_raw<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, | |
| ) -> bool { | |
| // If we don't know a type doesn't need drop, for example if it's a type | |
| // parameter without a `Copy` bound, then we conservatively return that it | |
| // needs drop. | |
| let adt_has_dtor = | |
| |adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant); | |
| let res = drop_tys_helper( | |
| tcx, | |
| query.value, | |
| query.typing_env, | |
| adt_has_dtor, | |
| DropTysOptions::default(), | |
| ) | |
| .filter(filter_array_elements(tcx, query.typing_env)) | |
| .next() | |
| .is_some(); | |
| debug!("needs_drop_raw({:?}) = {:?}", query, res); | |
| res | |
| } | |
| fn needs_async_drop_raw<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, | |
| ) -> bool { | |
| // If we don't know a type doesn't need async drop, for example if it's a | |
| // type parameter without a `Copy` bound, then we conservatively return that | |
| // it needs async drop. | |
| let adt_has_async_dtor = | |
| |adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant); | |
| let res = drop_tys_helper( | |
| tcx, | |
| query.value, | |
| query.typing_env, | |
| adt_has_async_dtor, | |
| DropTysOptions::default().recurse_into_box_for_async_drop(), | |
| ) | |
| .filter(filter_array_elements_async(tcx, query.typing_env)) | |
| .next() | |
| .is_some(); | |
| debug!("needs_async_drop_raw({:?}) = {:?}", query, res); | |
| res | |
| } | |
| /// HACK: in order to not mistakenly assume that `[PhantomData<T>; N]` requires drop glue | |
| /// we check the element type for drop glue. The correct fix would be looking at the | |
| /// entirety of the code around `needs_drop_components` and this file and come up with | |
| /// logic that is easier to follow while not repeating any checks that may thus diverge. | |
| fn filter_array_elements<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| ) -> impl Fn(&Result<Ty<'tcx>, AlwaysRequiresDrop>) -> bool { | |
| move |ty| match ty { | |
| Ok(ty) => match *ty.kind() { | |
| ty::Array(elem, _) => tcx.needs_drop_raw(typing_env.as_query_input(elem)), | |
| _ => true, | |
| }, | |
| Err(AlwaysRequiresDrop) => true, | |
| } | |
| } | |
| fn filter_array_elements_async<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| ) -> impl Fn(&Result<Ty<'tcx>, AlwaysRequiresDrop>) -> bool { | |
| move |ty| match ty { | |
| Ok(ty) => match *ty.kind() { | |
| ty::Array(elem, _) => tcx.needs_async_drop_raw(typing_env.as_query_input(elem)), | |
| _ => true, | |
| }, | |
| Err(AlwaysRequiresDrop) => true, | |
| } | |
| } | |
| fn has_significant_drop_raw<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| query: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, | |
| ) -> bool { | |
| let res = drop_tys_helper( | |
| tcx, | |
| query.value, | |
| query.typing_env, | |
| adt_consider_insignificant_dtor(tcx), | |
| DropTysOptions::default().only_significant(), | |
| ) | |
| .filter(filter_array_elements(tcx, query.typing_env)) | |
| .next() | |
| .is_some(); | |
| debug!("has_significant_drop_raw({:?}) = {:?}", query, res); | |
| res | |
| } | |
| struct NeedsDropTypes<'tcx, F> { | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| query_ty: Ty<'tcx>, | |
| seen_tys: FxHashSet<Ty<'tcx>>, | |
| /// A stack of types left to process, and the recursion depth when we | |
| /// pushed that type. Each round, we pop something from the stack and check | |
| /// if it needs drop. If the result depends on whether some other types | |
| /// need drop we push them onto the stack. | |
| unchecked_tys: Vec<(Ty<'tcx>, usize)>, | |
| recursion_limit: Limit, | |
| adt_components: F, | |
| /// Set this to true if an exhaustive list of types involved in | |
| /// drop obligation is requested. | |
| // FIXME: Calling this bool `exhaustive` is confusing and possibly a footgun, | |
| // since it does two things: It makes the iterator yield *all* of the types | |
| // that need drop, and it also affects the computation of the drop components | |
| // on `Coroutine`s. The latter is somewhat confusing, and probably should be | |
| // a function of `typing_env`. See the HACK comment below for why this is | |
| // necessary. If this isn't possible, then we probably should turn this into | |
| // a `NeedsDropMode` so that we can have a variant like `CollectAllSignificantDrops`, | |
| // which will more accurately indicate that we want *all* of the *significant* | |
| // drops, which are the two important behavioral changes toggled by this bool. | |
| exhaustive: bool, | |
| } | |
| impl<'tcx, F> NeedsDropTypes<'tcx, F> { | |
| fn new( | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| ty: Ty<'tcx>, | |
| exhaustive: bool, | |
| adt_components: F, | |
| ) -> Self { | |
| let mut seen_tys = FxHashSet::default(); | |
| seen_tys.insert(ty); | |
| Self { | |
| tcx, | |
| typing_env, | |
| seen_tys, | |
| query_ty: ty, | |
| unchecked_tys: vec![(ty, 0)], | |
| recursion_limit: tcx.recursion_limit(), | |
| adt_components, | |
| exhaustive, | |
| } | |
| } | |
| /// Called when `ty` is found to always require drop. | |
| /// If the exhaustive flag is true, then `Ok(ty)` is returned like any other type. | |
| /// Otherwise, `Err(AlwaysRequireDrop)` is returned, which will cause iteration to abort. | |
| fn always_drop_component(&self, ty: Ty<'tcx>) -> NeedsDropResult<Ty<'tcx>> { | |
| if self.exhaustive { Ok(ty) } else { Err(AlwaysRequiresDrop) } | |
| } | |
| } | |
| impl<'tcx, F, I> Iterator for NeedsDropTypes<'tcx, F> | |
| where | |
| F: Fn(ty::AdtDef<'tcx>, GenericArgsRef<'tcx>) -> NeedsDropResult<I>, | |
| I: Iterator<Item = Ty<'tcx>>, | |
| { | |
| type Item = NeedsDropResult<Ty<'tcx>>; | |
| #[instrument(level = "debug", skip(self), ret)] | |
| fn next(&mut self) -> Option<NeedsDropResult<Ty<'tcx>>> { | |
| let tcx = self.tcx; | |
| while let Some((ty, level)) = self.unchecked_tys.pop() { | |
| debug!(?ty, "needs_drop_components: inspect"); | |
| if !self.recursion_limit.value_within_limit(level) { | |
| // Not having a `Span` isn't great. But there's hopefully some other | |
| // recursion limit error as well. | |
| debug!("needs_drop_components: recursion limit exceeded"); | |
| tcx.dcx().emit_err(NeedsDropOverflow { query_ty: self.query_ty }); | |
| return Some(self.always_drop_component(ty)); | |
| } | |
| let components = match needs_drop_components(tcx, ty) { | |
| Err(AlwaysRequiresDrop) => return Some(self.always_drop_component(ty)), | |
| Ok(components) => components, | |
| }; | |
| debug!("needs_drop_components({:?}) = {:?}", ty, components); | |
| let queue_type = move |this: &mut Self, component: Ty<'tcx>| { | |
| if this.seen_tys.insert(component) { | |
| this.unchecked_tys.push((component, level + 1)); | |
| } | |
| }; | |
| for component in components { | |
| match *component.kind() { | |
| // The information required to determine whether a coroutine has drop is | |
| // computed on MIR, while this very method is used to build MIR. | |
| // To avoid cycles, we consider that coroutines always require drop. | |
| // | |
| // HACK: Because we erase regions contained in the coroutine witness, we | |
| // have to conservatively assume that every region captured by the | |
| // coroutine has to be live when dropped. This results in a lot of | |
| // undesirable borrowck errors. During borrowck, we call `needs_drop` | |
| // for the coroutine witness and check whether any of the contained types | |
| // need to be dropped, and only require the captured types to be live | |
| // if they do. | |
| ty::Coroutine(def_id, args) => { | |
| // FIXME: See FIXME on `exhaustive` field above. | |
| if self.exhaustive { | |
| for upvar in args.as_coroutine().upvar_tys() { | |
| queue_type(self, upvar); | |
| } | |
| queue_type(self, args.as_coroutine().resume_ty()); | |
| if let Some(witness) = tcx.mir_coroutine_witnesses(def_id) { | |
| for field_ty in &witness.field_tys { | |
| queue_type( | |
| self, | |
| EarlyBinder::bind(tcx, field_ty.ty) | |
| .instantiate(tcx, args) | |
| .skip_norm_wip(), | |
| ); | |
| } | |
| } | |
| } else { | |
| return Some(self.always_drop_component(ty)); | |
| } | |
| } | |
| ty::CoroutineWitness(..) => { | |
| unreachable!("witness should be handled in parent"); | |
| } | |
| ty::UnsafeBinder(bound_ty) => { | |
| let ty = self.tcx.instantiate_bound_regions_with_erased(bound_ty.into()); | |
| queue_type(self, ty); | |
| } | |
| _ if tcx.type_is_copy_modulo_regions(self.typing_env, component) => {} | |
| ty::Closure(_, args) => { | |
| for upvar in args.as_closure().upvar_tys() { | |
| queue_type(self, upvar); | |
| } | |
| } | |
| ty::CoroutineClosure(_, args) => { | |
| for upvar in args.as_coroutine_closure().upvar_tys() { | |
| queue_type(self, upvar); | |
| } | |
| } | |
| // Check for a `Drop` impl and whether this is a union or | |
| // `ManuallyDrop`. If it's a struct or enum without a `Drop` | |
| // impl then check whether the field types need `Drop`. | |
| ty::Adt(adt_def, args) => { | |
| let tys = match (self.adt_components)(adt_def, args) { | |
| Err(AlwaysRequiresDrop) => { | |
| return Some(self.always_drop_component(ty)); | |
| } | |
| Ok(tys) => tys, | |
| }; | |
| for required_ty in tys { | |
| let required = tcx | |
| .try_normalize_erasing_regions( | |
| self.typing_env, | |
| Unnormalized::new_wip(required_ty), | |
| ) | |
| .unwrap_or(required_ty); | |
| queue_type(self, required); | |
| } | |
| } | |
| ty::Alias(..) | ty::Array(..) | ty::Placeholder(_) | ty::Param(_) => { | |
| if ty == component { | |
| // Return the type to the caller: they may be able | |
| // to normalize further than we can. | |
| return Some(Ok(component)); | |
| } else { | |
| // Store the type for later. We can't return here | |
| // because we would then lose any other components | |
| // of the type. | |
| queue_type(self, component); | |
| } | |
| } | |
| ty::Foreign(_) | ty::Dynamic(..) => { | |
| debug!("needs_drop_components: foreign or dynamic"); | |
| return Some(self.always_drop_component(ty)); | |
| } | |
| ty::Bool | |
| | ty::Char | |
| | ty::Int(_) | |
| | ty::Uint(_) | |
| | ty::Float(_) | |
| | ty::Str | |
| | ty::Slice(_) | |
| | ty::Ref(..) | |
| | ty::RawPtr(..) | |
| | ty::FnDef(..) | |
| | ty::Pat(..) | |
| | ty::FnPtr(..) | |
| | ty::Tuple(_) | |
| | ty::Bound(..) | |
| | ty::Never | |
| | ty::Infer(_) | |
| | ty::Error(_) => { | |
| bug!("unexpected type returned by `needs_drop_components`: {component}") | |
| } | |
| } | |
| } | |
| } | |
| None | |
| } | |
| } | |
| enum DtorType { | |
| /// Type has a `Drop` but it is considered insignificant. | |
| /// Check the query `adt_significant_drop_tys` for understanding | |
| /// "significant" / "insignificant". | |
| Insignificant, | |
| /// Type has a `Drop` implantation. | |
| Significant, | |
| } | |
| #[derive(Copy, Clone, Default)] | |
| struct DropTysOptions { | |
| only_significant: bool, | |
| exhaustive: bool, | |
| async_drop_recurses_into_box: bool, | |
| } | |
| impl DropTysOptions { | |
| fn only_significant(mut self) -> Self { | |
| self.only_significant = true; | |
| self | |
| } | |
| fn exhaustive(mut self) -> Self { | |
| self.exhaustive = true; | |
| self | |
| } | |
| fn recurse_into_box_for_async_drop(mut self) -> Self { | |
| self.async_drop_recurses_into_box = true; | |
| self | |
| } | |
| } | |
| // This is a helper function for `adt_drop_tys` and `adt_significant_drop_tys`. | |
| // Depending on the implantation of `adt_has_dtor`, it is used to check if the | |
| // ADT has a destructor or if the ADT only has a significant destructor. For | |
| // understanding significant destructor look at `adt_significant_drop_tys`. | |
| fn drop_tys_helper<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| ty: Ty<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| adt_has_dtor: impl Fn(ty::AdtDef<'tcx>) -> Option<DtorType>, | |
| options: DropTysOptions, | |
| ) -> impl Iterator<Item = NeedsDropResult<Ty<'tcx>>> { | |
| fn with_query_cache<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| iter: impl IntoIterator<Item = Ty<'tcx>>, | |
| ) -> NeedsDropResult<Vec<Ty<'tcx>>> { | |
| iter.into_iter().try_fold(Vec::new(), |mut vec, subty| { | |
| match subty.kind() { | |
| ty::Adt(adt_id, args) => { | |
| for subty in tcx.adt_drop_tys(adt_id.did())? { | |
| vec.push( | |
| EarlyBinder::bind(tcx, subty).instantiate(tcx, args).skip_norm_wip(), | |
| ); | |
| } | |
| } | |
| _ => vec.push(subty), | |
| }; | |
| Ok(vec) | |
| }) | |
| } | |
| let adt_components = move |adt_def: ty::AdtDef<'tcx>, args: GenericArgsRef<'tcx>| { | |
| if adt_def.is_manually_drop() { | |
| debug!("drop_tys_helper: `{:?}` is manually drop", adt_def); | |
| Ok(Vec::new()) | |
| } else if options.async_drop_recurses_into_box && adt_def.is_box() { | |
| let box_components = match args.as_slice() { | |
| [boxed_ty, allocator_ty] => { | |
| let boxed_ty = boxed_ty.expect_ty(); | |
| let allocator_ty = allocator_ty.expect_ty(); | |
| match boxed_ty.kind() { | |
| // FIXME(async_drop): boxed dyn pointees are deliberately skipped here | |
| // because async drop glue does not yet dispatch through dyn metadata. | |
| // Once that is supported, this should include the boxed pointee too. | |
| ty::Dynamic(..) | ty::Error(_) => vec![allocator_ty], | |
| _ => vec![boxed_ty, allocator_ty], | |
| } | |
| } | |
| _ => { | |
| bug!("drop_tys_helper: `Box` has unexpected generic args: {args:?}"); | |
| } | |
| }; | |
| Ok(box_components) | |
| } else if let Some(dtor_info) = adt_has_dtor(adt_def) { | |
| match dtor_info { | |
| DtorType::Significant => { | |
| debug!("drop_tys_helper: `{:?}` implements `Drop`", adt_def); | |
| Err(AlwaysRequiresDrop) | |
| } | |
| DtorType::Insignificant => { | |
| debug!("drop_tys_helper: `{:?}` drop is insignificant", adt_def); | |
| // Since the destructor is insignificant, we just want to make sure all of | |
| // the passed in type parameters are also insignificant. | |
| // Eg: Vec<T> dtor is insignificant when T=i32 but significant when T=Mutex. | |
| Ok(args.types().collect()) | |
| } | |
| } | |
| } else if adt_def.is_union() { | |
| debug!("drop_tys_helper: `{:?}` is a union", adt_def); | |
| Ok(Vec::new()) | |
| } else { | |
| let field_tys = adt_def.all_fields().map(|field| { | |
| let r = tcx.type_of(field.did).instantiate(tcx, args).skip_norm_wip(); | |
| debug!( | |
| "drop_tys_helper: Instantiate into {:?} with {:?} getting {:?}", | |
| field, args, r | |
| ); | |
| r | |
| }); | |
| if options.only_significant { | |
| // We can't recurse through the query system here because we might induce a cycle | |
| Ok(field_tys.collect()) | |
| } else { | |
| // We can use the query system if we consider all drops significant. In that case, | |
| // ADTs are `needs_drop` exactly if they `impl Drop` or if any of their "transitive" | |
| // fields do. There can be no cycles here, because ADTs cannot contain themselves as | |
| // fields. | |
| with_query_cache(tcx, field_tys) | |
| } | |
| } | |
| .map(|v| v.into_iter()) | |
| }; | |
| NeedsDropTypes::new(tcx, typing_env, ty, options.exhaustive, adt_components) | |
| } | |
| fn adt_consider_insignificant_dtor<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| ) -> impl Fn(ty::AdtDef<'tcx>) -> Option<DtorType> { | |
| move |adt_def: ty::AdtDef<'tcx>| { | |
| if find_attr!(tcx, adt_def.did(), RustcInsignificantDtor) { | |
| // In some cases like `std::collections::HashMap` where the struct is a wrapper around | |
| // a type that is a Drop type, and the wrapped type (eg: `hashbrown::HashMap`) lies | |
| // outside stdlib, we might choose to still annotate the wrapper (std HashMap) with | |
| // `rustc_insignificant_dtor`, even if the type itself doesn't have a `Drop` impl. | |
| Some(DtorType::Insignificant) | |
| } else if adt_def.destructor(tcx).is_some() { | |
| // There is a Drop impl and the type isn't marked insignificant, therefore Drop must be | |
| // significant. | |
| Some(DtorType::Significant) | |
| } else { | |
| // No destructor found nor the type is annotated with `rustc_insignificant_dtor`, we | |
| // treat this as the simple case of Drop impl for type. | |
| None | |
| } | |
| } | |
| } | |
| fn adt_drop_tys<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| def_id: DefId, | |
| ) -> Result<&'tcx ty::List<Ty<'tcx>>, AlwaysRequiresDrop> { | |
| // This is for the "adt_drop_tys" query, that considers all `Drop` impls, therefore all dtors are | |
| // significant. | |
| let adt_has_dtor = | |
| |adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant); | |
| // `tcx.type_of(def_id)` identical to `tcx.make_adt(def, identity_args)` | |
| drop_tys_helper( | |
| tcx, | |
| tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), | |
| ty::TypingEnv::non_body_analysis(tcx, def_id), | |
| adt_has_dtor, | |
| DropTysOptions::default(), | |
| ) | |
| .collect::<Result<Vec<_>, _>>() | |
| .map(|components| tcx.mk_type_list(&components)) | |
| } | |
| fn adt_async_drop_tys<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| def_id: DefId, | |
| ) -> Result<&'tcx ty::List<Ty<'tcx>>, AlwaysRequiresDrop> { | |
| // This is for the "adt_async_drop_tys" query, that considers all `AsyncDrop` impls. | |
| let adt_has_dtor = | |
| |adt_def: ty::AdtDef<'tcx>| adt_def.async_destructor(tcx).map(|_| DtorType::Significant); | |
| // `tcx.type_of(def_id)` identical to `tcx.make_adt(def, identity_args)` | |
| drop_tys_helper( | |
| tcx, | |
| tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), | |
| ty::TypingEnv::non_body_analysis(tcx, def_id), | |
| adt_has_dtor, | |
| DropTysOptions::default().recurse_into_box_for_async_drop(), | |
| ) | |
| .collect::<Result<Vec<_>, _>>() | |
| .map(|components| tcx.mk_type_list(&components)) | |
| } | |
| // If `def_id` refers to a generic ADT, the queries above and below act as if they had been handed | |
| // a `tcx.make_ty(def, identity_args)` and as such it is legal to instantiate the generic parameters | |
| // of the ADT into the outputted `ty`s. | |
| fn adt_significant_drop_tys( | |
| tcx: TyCtxt<'_>, | |
| def_id: DefId, | |
| ) -> Result<&ty::List<Ty<'_>>, AlwaysRequiresDrop> { | |
| drop_tys_helper( | |
| tcx, | |
| tcx.type_of(def_id).instantiate_identity().skip_norm_wip(), // identical to `tcx.make_adt(def, identity_args)` | |
| ty::TypingEnv::non_body_analysis(tcx, def_id), | |
| adt_consider_insignificant_dtor(tcx), | |
| DropTysOptions::default().only_significant(), | |
| ) | |
| .collect::<Result<Vec<_>, _>>() | |
| .map(|components| tcx.mk_type_list(&components)) | |
| } | |
| #[instrument(level = "debug", skip(tcx), ret)] | |
| fn list_significant_drop_tys<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| key: ty::PseudoCanonicalInput<'tcx, Ty<'tcx>>, | |
| ) -> &'tcx ty::List<Ty<'tcx>> { | |
| tcx.mk_type_list( | |
| &drop_tys_helper( | |
| tcx, | |
| key.value, | |
| key.typing_env, | |
| adt_consider_insignificant_dtor(tcx), | |
| DropTysOptions::default().only_significant().exhaustive(), | |
| ) | |
| .filter_map(|res| res.ok()) | |
| .collect::<Vec<_>>(), | |
| ) | |
| } | |
| pub(crate) fn provide(providers: &mut Providers) { | |
| *providers = Providers { | |
| needs_drop_raw, | |
| needs_async_drop_raw, | |
| has_significant_drop_raw, | |
| adt_drop_tys, | |
| adt_async_drop_tys, | |
| adt_significant_drop_tys, | |
| list_significant_drop_tys, | |
| ..*providers | |
| }; | |
| } |