use panicgraph::{Category, Termination};
use rustc_hir::{def::DefKind, def_id::DefId};
use rustc_middle::{middle::codegen_fn_attrs::CodegenFnAttrFlags, ty::TyCtxt};
use crate::util::Map;
#[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: &[(&str, &str, Sink)] = &[
("core", "panicking::panic", unwind(Category::Explicit)),
("core", "panicking::panic_fmt", unwind(Category::Explicit)),
("core", "panicking::panic_str", unwind(Category::Explicit)),
(
"core",
"panicking::panic_explicit",
unwind(Category::Explicit),
),
(
"core",
"panicking::panic_display",
unwind(Category::Explicit),
),
(
"core",
"panicking::assert_failed_inner",
unwind(Category::Explicit),
),
(
"core",
"panicking::panic_nounwind",
abort(Category::Explicit),
),
(
"core",
"panicking::panic_nounwind_fmt",
abort(Category::Explicit),
),
(
"core",
"panicking::panic_cannot_unwind",
abort(Category::Explicit),
),
(
"core",
"panicking::panic_bounds_check",
unwind(Category::Index),
),
("core", "option::unwrap_failed", unwind(Category::Unwrap)),
("core", "option::expect_failed", unwind(Category::Unwrap)),
("core", "result::unwrap_failed", unwind(Category::Unwrap)),
(
"core",
"slice::index::slice_index_fail",
unwind(Category::Index),
),
(
"core",
"slice::index::slice_start_index_len_fail",
unwind(Category::Index),
),
(
"core",
"slice::index::slice_end_index_len_fail",
unwind(Category::Index),
),
(
"core",
"slice::index::slice_index_order_fail",
unwind(Category::Index),
),
(
"core",
"str::slice_error_fail",
unwind(Category::StrBoundary),
),
(
"core",
"cell::panic_already_borrowed",
unwind(Category::Borrow),
),
(
"core",
"cell::panic_already_mutably_borrowed",
unwind(Category::Borrow),
),
(
"alloc",
"raw_vec::capacity_overflow",
unwind(Category::CapacityOverflow),
),
(
"alloc",
"raw_vec::handle_error",
unwind(Category::CapacityOverflow),
),
(
"alloc",
"raw_vec::handle_reserve",
unwind(Category::CapacityOverflow),
),
(
"alloc",
"alloc::handle_alloc_error",
abort(Category::AllocFailure),
),
(
"std",
"alloc::handle_alloc_error",
abort(Category::AllocFailure),
),
];
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 (k, p, sink) in EXACT {
if *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()
}
}