use panicgraph::{CallSite, EdgeKind, FuncKey, Guard, Reified};
use rustc_hir::def_id::DefId;
use rustc_middle::{
mir,
ty::{self, Instance, TypeVisitableExt, TypingEnv},
};
use super::{At, Extractor, Raw, Work};
impl<'tcx> Extractor<'tcx> {
pub(super) fn push_drop(
&self,
raw: &mut Raw<'tcx>,
cx: Work<'tcx>,
at: At,
ty: ty::Ty<'tcx>,
) {
let Some(ty) = self.normalize(cx, ty) else {
self.unresolved(raw, at, "<unresolved drop>".to_owned());
return;
};
if !ty.needs_drop(self.tcx, cx.env) {
return;
}
if ty.has_param() {
self.generic(raw, at, format!("drop glue for {ty}"));
return;
}
let display = format!("drop glue for {ty}");
if let ty::Dynamic(predicates, _) = ty.kind() {
self.push_edge(raw, at, None, display, EdgeKind::Vtable, false);
if let Some(principal) = predicates.principal_def_id() {
self.push_drop_candidates(raw, cx, at, principal);
}
return;
}
let glue = Instance::resolve_drop_glue(self.tcx, ty);
let key = self.symbol_of(glue).map(FuncKey);
self.push_edge(raw, at, key, display, EdgeKind::Drop, false);
raw.successors.push(Work {
inst: glue,
env: cx.env,
});
}
fn push_drop_candidates(
&self,
raw: &mut Raw<'tcx>,
cx: Work<'tcx>,
at: At,
principal: DefId,
) {
for impl_did in self.tcx.all_impls(principal) {
let self_ty = self
.tcx
.impl_trait_ref(impl_did)
.instantiate_identity()
.skip_normalization()
.self_ty();
if self_ty.has_param()
|| !self_ty
.needs_drop(self.tcx, TypingEnv::fully_monomorphized())
{
continue;
}
let glue = Instance::resolve_drop_glue(self.tcx, self_ty);
let Some(key) = self.symbol_of(glue).map(FuncKey) else {
continue;
};
let site = CallSite {
callee: Some(key),
callee_display: format!("drop glue for {self_ty}"),
kind: EdgeKind::Vtable,
loc: self.loc_of(at.span),
guard: Guard::default(),
barrier: false,
terminates: false,
candidate: true,
sig: None,
self_ty: Some(format!("{self_ty}")),
};
raw.add_call(at, site);
raw.successors.push(Work {
inst: glue,
env: cx.env,
});
}
}
pub(super) fn push_dyn_candidates(
&self,
raw: &mut Raw<'tcx>,
cx: Work<'tcx>,
at: At,
virt: Instance<'tcx>,
) {
let method = virt.def_id();
let Some(trait_did) = self.tcx.trait_of_assoc(method) else {
return;
};
if self.tcx.is_fn_trait(trait_did) {
return;
}
for impl_did in self.tcx.all_impls(trait_did) {
let trait_ref = self
.tcx
.impl_trait_ref(impl_did)
.instantiate_identity()
.skip_normalization();
if trait_ref.has_param() {
continue;
}
let args = self.tcx.mk_args_from_iter(
std::iter::once(ty::GenericArg::from(trait_ref.self_ty()))
.chain(virt.args.iter().skip(1)),
);
let Ok(Some(target)) =
Instance::try_resolve(self.tcx, cx.env, method, args)
else {
continue;
};
let Some(key) = self.symbol_of(target).map(FuncKey) else {
continue;
};
let site = CallSite {
callee: Some(key),
callee_display: self.tcx.def_path_str(target.def_id()),
kind: EdgeKind::Vtable,
loc: self.loc_of(at.span),
guard: Guard::default(),
barrier: false,
terminates: false,
candidate: true,
sig: None,
self_ty: Some(format!("{}", trait_ref.self_ty())),
};
raw.add_call(at, site);
raw.successors.push(Work {
inst: target,
env: cx.env,
});
}
}
pub(super) fn note_coerced(
&mut self,
cx: Work<'tcx>,
stmt: &mir::Statement<'tcx>,
mir: &mir::Body<'tcx>,
) {
let mir::StatementKind::Assign(pair) = &stmt.kind else {
return;
};
let mir::Rvalue::Cast(
mir::CastKind::PointerCoercion(
ty::adjustment::PointerCoercion::Unsize,
_,
),
operand,
target,
) = &pair.1
else {
return;
};
let Some(target) = self.normalize(cx, *target) else {
return;
};
let source = operand.ty(&mir.local_decls, self.tcx);
let Some(source) = self.normalize(cx, source) else {
return;
};
if !names_object(target) || names_object(source) {
return;
}
if let Some(pointee) = pointee_of(source) {
self.coerced.insert(format!("{pointee}"));
}
}
pub(super) fn note_reified(
&mut self,
raw: &mut Raw<'tcx>,
cx: Work<'tcx>,
stmt: &mir::Statement<'tcx>,
mir: &mir::Body<'tcx>,
) {
let mir::StatementKind::Assign(pair) = &stmt.kind else {
return;
};
let mir::Rvalue::Cast(
mir::CastKind::PointerCoercion(coercion, _),
operand,
cast_ty,
) = &pair.1
else {
return;
};
let inst = match (coercion, operand) {
(
ty::adjustment::PointerCoercion::ReifyFnPointer(_),
mir::Operand::Constant(konst),
) => self.fn_constant(cx, konst),
(ty::adjustment::PointerCoercion::ClosureFnPointer(_), _) => self
.normalize(cx, operand.ty(&mir.local_decls, self.tcx))
.and_then(|closure| match *closure.kind() {
ty::Closure(did, args) => Some(Instance::resolve_closure(
self.tcx,
did,
args,
ty::ClosureKind::FnOnce,
)),
_ => None,
}),
_ => None,
};
let Some(inst) = inst else {
return;
};
let Some(sig) = self.normalize(cx, *cast_ty).map(|ty| ty.to_string())
else {
return;
};
let Some(key) = self.symbol_of(inst).map(FuncKey) else {
return;
};
if !self.reified_seen.insert((key.clone(), sig.clone())) {
return;
}
raw.successors.push(Work { inst, env: cx.env });
self.reified.push(Reified {
key,
display: self.tcx.def_path_str(inst.def_id()),
sig,
});
}
}
fn names_object(ty: ty::Ty<'_>) -> bool {
ty.walk().any(|part| {
part.as_type()
.is_some_and(|inner| matches!(inner.kind(), ty::Dynamic(..)))
})
}
fn pointee_of(ty: ty::Ty<'_>) -> Option<ty::Ty<'_>> {
match ty.kind() {
ty::Ref(_, inner, _) | ty::RawPtr(inner, _) => Some(*inner),
ty::Adt(def, _) if def.is_box() => Some(ty.expect_boxed_ty()),
ty::Adt(_, args) => {
let inner = args.types().next()?;
match inner.kind() {
ty::Ref(..) | ty::RawPtr(..) => pointee_of(inner),
ty::Adt(def, _) if def.is_box() => pointee_of(inner),
_ => Some(inner),
}
}
_ => None,
}
}