mod index;
mod variables;
use anyhow::Context as _;
pub use index::{function_index, FunctionData, FunctionIndex, FunctionIndexEntry};
use itertools::Itertools;
use rudy_types::{Layout, PrimitiveLayout, ReferenceLayout};
pub use variables::{resolve_function_variables, Variable};
use crate::{
die::{
utils::{get_string_attr, pretty_print_die_entry},
UnitRef,
},
file::RawDie,
function::variables::variable,
parser::{
children::{for_each_child, try_for_each_child},
combinators::all,
primitives::{attr, is_member_tag, optional_attr, resolve_type_shallow},
Parser,
},
types::{DieLayout, DieTypeDefinition},
Die, DwarfDb,
};
type Result<T> = std::result::Result<T, super::Error>;
pub enum FunctionDeclarationType {
Closure,
ClassMethodDeclaration,
ClassMethodImplementation,
Function {
#[allow(dead_code)]
inlined: bool,
},
InlinedFunctionImplementation,
}
#[allow(dead_code)]
pub fn get_declaration_type<'db>(
_db: &'db dyn DwarfDb,
die: &RawDie<'db>,
unit_ref: &UnitRef<'db>,
) -> FunctionDeclarationType {
if die.attr(gimli::DW_AT_declaration).ok().flatten().is_some() {
return FunctionDeclarationType::ClassMethodDeclaration;
}
if let Some(gimli::AttributeValue::UnitRef(_)) = die
.attr(gimli::DW_AT_specification)
.ok()
.flatten()
.map(|v| v.value())
{
return FunctionDeclarationType::ClassMethodImplementation;
}
if let Some(gimli::AttributeValue::DebugInfoRef(_)) = die
.attr(gimli::DW_AT_specification)
.ok()
.flatten()
.map(|v| v.value())
{
return FunctionDeclarationType::ClassMethodImplementation;
}
if let Some(gimli::AttributeValue::UnitRef(_)) = die
.attr(gimli::DW_AT_abstract_origin)
.ok()
.flatten()
.map(|v| v.value())
{
return FunctionDeclarationType::InlinedFunctionImplementation;
}
if let Ok(Some(name)) = get_string_attr(die, gimli::DW_AT_name, unit_ref) {
if name.starts_with("{closure#") {
return FunctionDeclarationType::Closure;
}
} else {
tracing::error!(
"No name attribute for function at {:#010x}. What is this? {}",
unit_ref.header.offset().as_debug_info_offset().unwrap().0 + die.offset().0,
pretty_print_die_entry(die, unit_ref)
);
}
let inlined = matches!(
die.attr(gimli::DW_AT_inline)
.ok()
.flatten()
.map(|v| v.value()),
Some(gimli::AttributeValue::Inline(gimli::DW_INL_inlined)),
);
FunctionDeclarationType::Function { inlined }
}
#[salsa::tracked(debug)]
pub struct FunctionSignature<'db> {
pub name: String,
pub params: Vec<Variable<'db>>,
pub return_type: Option<DieTypeDefinition<'db>>,
pub self_type: Option<SelfType>,
pub callable: bool,
pub debug_location: String,
}
impl<'db> FunctionSignature<'db> {
pub fn print_sig(&self, db: &'db dyn DwarfDb) -> String {
let params = self
.params(db)
.iter()
.map(|p| {
format!(
"{}: {}",
p.name(db).as_deref().unwrap_or("_"),
p.ty(db).display_name()
)
})
.join(", ");
if let Some(ret) = self.return_type(db) {
format!("fn({params}) -> {}", ret.display_name())
} else {
format!("fn({params})")
}
}
}
fn function_parameter<'db>() -> impl Parser<'db, Variable<'db>> {
is_member_tag(gimli::DW_TAG_formal_parameter).then(variable())
}
fn function_declaration<'db>(
) -> impl Parser<'db, (String, Option<DieTypeDefinition<'db>>, Vec<Variable<'db>>)> {
all((
attr::<String>(gimli::DW_AT_name),
optional_attr::<Die<'db>>(gimli::DW_AT_type).then(resolve_type_shallow()),
try_for_each_child(
is_member_tag(gimli::DW_TAG_formal_parameter)
.filter()
.then(function_parameter()),
),
))
}
fn function_specification<'db>() -> impl Parser<'db, Vec<Variable<'db>>> {
for_each_child(function_parameter())
}
#[salsa::tracked]
pub fn resolve_function_signature<'db>(
db: &'db dyn DwarfDb,
function_index_entry: FunctionIndexEntry<'db>,
) -> Result<FunctionSignature<'db>> {
let FunctionData {
declaration_die,
specification_die,
alternate_locations,
..
} = function_index_entry.data(db);
let (name, return_type, parameters) = function_declaration()
.parse(db, *declaration_die)
.context("parsing function declaration")?;
let parameters = if let Some(specification_die) = specification_die {
function_specification()
.parse(db, *specification_die)
.context("parsing function specification")?
} else {
parameters
};
let self_type = if let Some(first_param) = parameters.first() {
let first_param_name = first_param.name(db);
if matches!(
first_param_name.as_deref(),
Some("self" | "&self" | "&mut self")
) {
Some(SelfType::from_param_type(
first_param.ty(db).layout.as_ref(),
))
} else {
None
}
} else {
None
};
let mut debug_location = format!("Declaration: {}", declaration_die.location(db));
if let Some(spec) = specification_die {
debug_location.push_str(&format!("\nSpecification: {}", spec.location(db)));
}
for location in alternate_locations.iter() {
debug_location.push_str(&format!("\nAlternate: {}", location.location(db)));
}
Ok(FunctionSignature::new(
db,
name,
parameters,
return_type,
self_type,
true, debug_location,
))
}
#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SelfType {
Owned,
Borrowed,
BorrowedMut,
}
impl SelfType {
pub fn from_param_type(param_type: &DieLayout<'_>) -> Self {
match param_type {
Layout::Primitive(PrimitiveLayout::Reference(ReferenceLayout {
mutable: true,
..
})) => Self::BorrowedMut,
Layout::Primitive(PrimitiveLayout::Reference(ReferenceLayout {
mutable: false,
..
})) => Self::Borrowed,
_ => Self::Owned,
}
}
}