use panicgraph::{Category, Termination, util::Map};
use rustc_hir::{def::DefKind, def_id::DefId};
use rustc_middle::{middle::codegen_fn_attrs::CodegenFnAttrFlags, ty::TyCtxt};
#[derive(Debug, Clone, Copy)]
pub struct Sink {
pub category: Category,
pub termination: Termination,
}
const fn unwind(category: Category) -> Sink {
Sink {
category,
termination: Termination::Unwind,
}
}
const fn abort(category: Category) -> Sink {
Sink {
category,
termination: Termination::Abort,
}
}
const EXACT: &[(Sink, &[(&str, &str)])] = &[
(
unwind(Category::Explicit),
&[
("core", "panicking::panic"),
("core", "panicking::panic_fmt"),
("core", "panicking::panic_str"),
("core", "panicking::panic_explicit"),
("core", "panicking::panic_display"),
("core", "panicking::assert_failed_inner"),
],
),
(
abort(Category::Explicit),
&[
("core", "panicking::panic_nounwind"),
("core", "panicking::panic_nounwind_fmt"),
("core", "panicking::panic_cannot_unwind"),
],
),
(
unwind(Category::Index),
&[
("core", "panicking::panic_bounds_check"),
("core", "slice::index::slice_index_fail"),
("core", "slice::index::slice_start_index_len_fail"),
("core", "slice::index::slice_end_index_len_fail"),
("core", "slice::index::slice_index_order_fail"),
],
),
(
unwind(Category::Unwrap),
&[
("core", "option::unwrap_failed"),
("core", "option::expect_failed"),
("core", "result::unwrap_failed"),
],
),
(
unwind(Category::StrBoundary),
&[("core", "str::slice_error_fail")],
),
(
unwind(Category::Borrow),
&[
("core", "cell::panic_already_borrowed"),
("core", "cell::panic_already_mutably_borrowed"),
],
),
(
unwind(Category::CapacityOverflow),
&[
("alloc", "raw_vec::capacity_overflow"),
("alloc", "raw_vec::handle_error"),
("alloc", "raw_vec::handle_reserve"),
],
),
(
abort(Category::AllocFailure),
&[
("alloc", "alloc::handle_alloc_error"),
("std", "alloc::handle_alloc_error"),
],
),
];
pub struct SinkTable {
cache: Map<DefId, Option<Sink>>,
}
impl SinkTable {
#[must_use]
pub fn new() -> Self {
Self {
cache: Map::default(),
}
}
pub fn get(&mut self, tcx: TyCtxt<'_>, did: DefId) -> Option<Sink> {
if let Some(hit) = self.cache.get(&did) {
return *hit;
}
let sink = Self::classify(tcx, did);
self.cache.insert(did, sink);
sink
}
fn classify(tcx: TyCtxt<'_>, did: DefId) -> Option<Sink> {
let krate = tcx.crate_name(did.krate);
let krate = krate.as_str();
let path = Self::def_path(tcx, did);
for (sink, entries) in EXACT {
if entries.iter().any(|(k, p)| *k == krate && *p == path) {
return Some(*sink);
}
}
if krate == "core" && path.starts_with("panicking::") {
return Some(unwind(Category::Explicit));
}
if let Some(sink) =
Self::by_leaf_name(path.rsplit("::").next().unwrap_or(&path))
{
return Some(sink);
}
Self::opaque_divergence(tcx, did, krate)
}
fn opaque_divergence(
tcx: TyCtxt<'_>,
did: DefId,
krate: &str,
) -> Option<Sink> {
if !matches!(krate, "core" | "alloc") {
return None;
}
if !matches!(tcx.def_kind(did), DefKind::Fn | DefKind::AssocFn) {
return None;
}
if tcx.is_mir_available(did) {
return None;
}
if !tcx
.fn_sig(did)
.skip_binder()
.skip_binder()
.output()
.is_never()
{
return None;
}
let aborts = tcx
.codegen_fn_attrs(did)
.flags
.contains(CodegenFnAttrFlags::NEVER_UNWIND);
Some(if aborts {
abort(Category::Explicit)
} else {
unwind(Category::Explicit)
})
}
fn by_leaf_name(leaf: &str) -> Option<Sink> {
match leaf {
"capacity_overflow" => Some(unwind(Category::CapacityOverflow)),
"precondition_check" => Some(abort(Category::UbCheck)),
"handle_alloc_error" | "alloc_err" | "oom" => {
Some(abort(Category::AllocFailure))
}
"panic_arc_overflow" | "panic_rc_overflow" => {
Some(unwind(Category::RefCountOverflow))
}
_ => None,
}
}
fn def_path(tcx: TyCtxt<'_>, did: DefId) -> String {
let mut out = String::new();
for seg in &tcx.def_path(did).data {
let Some(name) = seg.data.get_opt_name() else {
continue;
};
if !out.is_empty() {
out.push_str("::");
}
out.push_str(name.as_str());
}
out
}
}
impl Default for SinkTable {
fn default() -> Self {
Self::new()
}
}