use std::collections::{HashSet, VecDeque};
use crate::{
formats::BinaryContext,
metadata::{is_internal_path, is_runtime_path, is_stdlib_path},
structures::{
abitype::AbiType,
arraytype::ArrayTypeExtra,
chantype::ChanTypeExtra,
descriptor,
elemtype::ElemTypeExtra,
functype::FuncTypeExtra,
interfacetype::InterfaceTypeExtra,
method::GoImethod,
method::GoMethod,
moduledata::Moduledata,
name::{
NAME_FLAG_EMBEDDED, NAME_FLAG_EXPORTED, decode_name, decode_name_and_tag,
decode_name_with_flags,
},
structtype::{GoStructField, StructTypeExtra},
uncommon::UncommonType,
util::{align_up, align_up_u64, read_uintptr},
},
};
pub use crate::structures::maptype::{MapFlags, MapLayout, MapTypeExtra};
#[derive(Debug, Clone, Copy)]
pub struct TypeAbi {
pub ps: u8,
pub legacy_names: bool,
pub map_layout: MapLayout,
}
#[derive(Debug, Clone)]
pub struct GoType<'a> {
pub descriptor_va: u64,
pub name: &'a str,
pub kind: TypeKind,
pub size: u64,
pub align: u8,
pub field_align: u8,
pub ptr_bytes: u64,
pub hash: u32,
pub tflag: u8,
pub ptr_to_this: i32,
pub equal_va: u64,
pub gcdata_va: u64,
pub pkg_path: Option<&'a str>,
pub has_uncommon: bool,
pub is_named: bool,
pub is_exported: bool,
pub method_count: u16,
pub exported_method_count: u16,
pub detail: TypeDetail<'a>,
pub methods: Vec<MethodEntry<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TypeRef<'a> {
pub va: u64,
pub name: Option<&'a str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodEntry<'a> {
pub name: &'a str,
pub type_descriptor_offset: i32,
pub type_name: Option<&'a str>,
pub function_text_offset: Option<i32>,
pub interface_text_offset: Option<i32>,
pub is_exported: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructField<'a> {
pub name: &'a str,
pub type_va: u64,
pub type_name: Option<&'a str>,
pub tag: Option<&'a str>,
pub offset: u64,
pub is_embedded: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InterfaceMethod<'a> {
pub name: &'a str,
pub type_descriptor_offset: i32,
pub type_name: Option<&'a str>,
}
#[derive(Debug, Clone)]
pub enum TypeDetail<'a> {
None,
Array {
len: u64,
elem_va: u64,
slice_va: u64,
},
Chan {
dir: u64,
elem_va: u64,
},
Func {
in_count: u16,
out_count: u16,
is_variadic: bool,
inputs: Vec<TypeRef<'a>>,
outputs: Vec<TypeRef<'a>>,
},
Interface {
method_count: u64,
methods: Vec<InterfaceMethod<'a>>,
pkg_path: Option<&'a str>,
},
Map {
key_va: u64,
elem_va: u64,
group_va: u64,
extra: Box<MapTypeExtra>,
},
Pointer {
elem_va: u64,
},
Slice {
elem_va: u64,
},
Struct {
field_count: u64,
fields: Vec<StructField<'a>>,
},
}
impl<'a> TypeDetail<'a> {
pub fn kind_str(&self) -> &'static str {
match self {
Self::None => "none",
Self::Array { .. } => "array",
Self::Chan { .. } => "chan",
Self::Func { .. } => "func",
Self::Interface { .. } => "interface",
Self::Map { .. } => "map",
Self::Pointer { .. } => "pointer",
Self::Slice { .. } => "slice",
Self::Struct { .. } => "struct",
}
}
pub fn array_len(&self) -> Option<u64> {
match self {
Self::Array { len, .. } => Some(*len),
_ => None,
}
}
pub fn chan_dir(&self) -> Option<u64> {
match self {
Self::Chan { dir, .. } => Some(*dir),
_ => None,
}
}
pub fn func_arity(&self) -> Option<(u16, u16)> {
match self {
Self::Func {
in_count,
out_count,
..
} => Some((*in_count, *out_count)),
_ => None,
}
}
pub fn struct_field_count(&self) -> Option<u64> {
match self {
Self::Struct { field_count, .. } => Some(*field_count),
_ => None,
}
}
pub fn interface_method_count(&self) -> Option<u64> {
match self {
Self::Interface { method_count, .. } => Some(*method_count),
_ => None,
}
}
}
impl<'a> GoType<'a> {
pub fn package(&self) -> Option<&'a str> {
let mut s = self.name;
while let Some(rest) = s.strip_prefix('*') {
s = rest;
}
if let Some(rest) = s.strip_prefix("[]") {
s = rest;
while let Some(rest) = s.strip_prefix('*') {
s = rest;
}
}
if s.starts_with('[')
&& let Some(bracket_end) = s.find(']')
&& let Some(after) = bracket_end.checked_add(1).and_then(|i| s.get(i..))
{
s = after;
while let Some(rest) = s.strip_prefix('*') {
s = rest;
}
}
if let Some(rest) = s.strip_prefix("map[") {
s = rest;
}
s.find('.').and_then(|dot| s.get(..dot)).filter(|pkg| {
!pkg.is_empty()
&& pkg
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '/' || c == '_' || c == '-')
})
}
pub fn is_runtime(&self) -> bool {
self.package().is_some_and(is_runtime_path)
}
pub fn is_internal(&self) -> bool {
self.package().is_some_and(is_internal_path)
}
pub fn is_stdlib(&self) -> bool {
self.package().is_some_and(is_stdlib_path)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypeKind {
Invalid,
Bool,
Int,
Int8,
Int16,
Int32,
Int64,
Uint,
Uint8,
Uint16,
Uint32,
Uint64,
Uintptr,
Float32,
Float64,
Complex64,
Complex128,
Array,
Chan,
Func,
Interface,
Map,
Pointer,
Slice,
String,
Struct,
UnsafePointer,
}
impl TypeKind {
pub fn from_raw(raw: u8) -> Self {
match raw & 0x1f {
1 => Self::Bool,
2 => Self::Int,
3 => Self::Int8,
4 => Self::Int16,
5 => Self::Int32,
6 => Self::Int64,
7 => Self::Uint,
8 => Self::Uint8,
9 => Self::Uint16,
10 => Self::Uint32,
11 => Self::Uint64,
12 => Self::Uintptr,
13 => Self::Float32,
14 => Self::Float64,
15 => Self::Complex64,
16 => Self::Complex128,
17 => Self::Array,
18 => Self::Chan,
19 => Self::Func,
20 => Self::Interface,
21 => Self::Map,
22 => Self::Pointer,
23 => Self::Slice,
24 => Self::String,
25 => Self::Struct,
26 => Self::UnsafePointer,
_ => Self::Invalid,
}
}
}
impl std::fmt::Display for TypeKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Invalid => "invalid",
Self::Bool => "bool",
Self::Int => "int",
Self::Int8 => "int8",
Self::Int16 => "int16",
Self::Int32 => "int32",
Self::Int64 => "int64",
Self::Uint => "uint",
Self::Uint8 => "uint8",
Self::Uint16 => "uint16",
Self::Uint32 => "uint32",
Self::Uint64 => "uint64",
Self::Uintptr => "uintptr",
Self::Float32 => "float32",
Self::Float64 => "float64",
Self::Complex64 => "complex64",
Self::Complex128 => "complex128",
Self::Array => "array",
Self::Chan => "chan",
Self::Func => "func",
Self::Interface => "interface",
Self::Map => "map",
Self::Pointer => "pointer",
Self::Slice => "slice",
Self::String => "string",
Self::Struct => "struct",
Self::UnsafePointer => "unsafe.Pointer",
})
}
}
pub struct TypeIter<'a> {
ctx: &'a BinaryContext<'a>,
data: &'a [u8],
types_base_va: u64,
abi: TypeAbi,
strategy: TypeIterStrategy<'a>,
}
enum TypeIterStrategy<'a> {
Typelinks { tl_data: &'a [u8], pos: usize },
Walk {
td: u64,
end_va: u64,
skips_left: u32,
},
Empty,
}
impl<'a> TypeIter<'a> {
pub fn empty(ctx: &'a BinaryContext<'a>) -> Self {
Self {
ctx,
data: ctx.structure_search_data(),
types_base_va: 0,
abi: TypeAbi {
ps: 0,
legacy_names: false,
map_layout: MapLayout::SwissSplitGroup,
},
strategy: TypeIterStrategy::Empty,
}
}
}
impl<'a> Iterator for TypeIter<'a> {
type Item = GoType<'a>;
fn next(&mut self) -> Option<GoType<'a>> {
match &mut self.strategy {
TypeIterStrategy::Empty => None,
TypeIterStrategy::Typelinks { tl_data, pos } => {
while let Some(end) = pos.checked_add(4) {
if end > tl_data.len() {
return None;
}
let bytes = tl_data.get(*pos..end).and_then(|s| s.try_into().ok())?;
*pos = end;
let type_off = i32::from_le_bytes(bytes);
let type_va =
(self.types_base_va as i64).saturating_add(type_off as i64) as u64;
if let Some(file_off) = self.ctx.va_to_file(type_va)
&& let Some(go_type) = parse_type_at(
self.data,
file_off,
type_va,
self.types_base_va,
self.abi,
self.ctx,
)
{
return Some(go_type);
}
}
None
}
TypeIterStrategy::Walk {
td,
end_va,
skips_left,
} => {
let p = self.abi.ps as u64;
if p == 0 {
return None;
}
while *td < *end_va {
*td = align_up_u64(*td, p)?;
if *td >= *end_va {
return None;
}
let here = *td;
let mut skip = || -> Option<()> {
*skips_left = skips_left.checked_sub(1)?;
*td = here.checked_add(p)?;
Some(())
};
let Some(file_off) = self.ctx.va_to_file(here) else {
skip()?;
continue;
};
let remaining = match self.data.get(file_off..) {
Some(d) if d.len() >= AbiType::size(self.abi.ps) => d,
_ => return None,
};
let (Some(abi_type), _) = (AbiType::parse(remaining, self.abi.ps), ()) else {
skip()?;
continue;
};
let desc_size = match descriptor::descriptor_size(
remaining,
&abi_type,
self.abi.ps,
self.abi.map_layout,
) {
Some(s) if s > 0 => s,
_ => {
skip()?;
continue;
}
};
let go_type = build_go_type(
&abi_type,
remaining,
self.data,
self.types_base_va,
self.abi,
self.ctx,
);
*td = here.checked_add(desc_size as u64)?;
if let Some(mut t) = go_type {
t.descriptor_va = here;
return Some(t);
}
}
None
}
}
}
}
pub fn extract_types_iter<'a>(
ctx: &'a BinaryContext<'a>,
md: &Moduledata,
abi: TypeAbi,
) -> TypeIter<'a> {
if !ctx.has_va_mapping() || abi.ps == 0 || md.types == 0 {
return TypeIter::empty(ctx);
}
let data = ctx.structure_search_data();
let sections = ctx.sections();
let iter = |strategy| TypeIter {
ctx,
data,
types_base_va: md.types,
abi,
strategy,
};
if let Some(ref range) = sections.typelink
&& let Some(end) = range.offset.checked_add(range.size)
&& let Some(tl_data) = data.get(range.offset..end)
{
return iter(TypeIterStrategy::Typelinks { tl_data, pos: 0 });
}
if let Some(ref tl_slice) = md.typelinks
&& let Some(tl_file_off) = ctx.va_to_file(tl_slice.ptr)
&& let Some(tl_byte_len) = (tl_slice.len as usize).checked_mul(4)
&& let Some(tl_end) = tl_file_off.checked_add(tl_byte_len)
&& let Some(tl_data) = data.get(tl_file_off..tl_end)
{
return iter(TypeIterStrategy::Typelinks { tl_data, pos: 0 });
}
match typelink_walk_range(md, abi.ps) {
Some((td, end_va)) => iter(TypeIterStrategy::Walk {
td,
end_va,
skips_left: WALK_SKIP_BUDGET,
}),
None => TypeIter::empty(ctx),
}
}
const WALK_SKIP_BUDGET: u32 = 64;
fn typelink_walk_range(md: &Moduledata, ptr_size: u8) -> Option<(u64, u64)> {
let start = md.types.checked_add(u64::from(ptr_size))?;
let end = match md.typedesclen {
Some(len) => md.types.checked_add(len)?,
None => md.etypes,
};
if end > start {
Some((start, end))
} else {
None
}
}
fn parse_type_at<'a>(
data: &'a [u8],
file_off: usize,
type_va: u64,
types_base_va: u64,
abi: TypeAbi,
ctx: &BinaryContext<'a>,
) -> Option<GoType<'a>> {
let remaining = data.get(file_off..)?;
let abi_type = AbiType::parse(remaining, abi.ps)?;
let mut t = build_go_type(&abi_type, remaining, data, types_base_va, abi, ctx)?;
t.descriptor_va = type_va;
Some(t)
}
pub fn type_at_va<'a>(
ctx: &'a BinaryContext<'a>,
va: u64,
types_base_va: u64,
abi: TypeAbi,
) -> Option<GoType<'a>> {
let data = ctx.structure_search_data();
let file_off = ctx.va_to_file(va)?;
let t = parse_type_at(data, file_off, va, types_base_va, abi, ctx)?;
if t.kind == TypeKind::Invalid {
return None;
}
if t.name.bytes().any(|b| b < 0x20) {
return None;
}
Some(t)
}
pub fn extract_all_types<'a>(
ctx: &'a BinaryContext<'a>,
seeds: Vec<GoType<'a>>,
types_base: u64,
etypes: u64,
abi: TypeAbi,
) -> Vec<GoType<'a>> {
const CAP: usize = 2_000_000;
let in_range = |va: u64| va >= types_base && (etypes == 0 || va < etypes);
let mut visited: HashSet<u64> = HashSet::new();
let mut queue: VecDeque<u64> = VecDeque::new();
let mut out: Vec<GoType<'a>> = Vec::new();
for t in &seeds {
if in_range(t.descriptor_va) {
queue.push_back(t.descriptor_va);
}
}
while let Some(va) = queue.pop_front() {
if out.len() >= CAP {
break;
}
if !in_range(va) || !visited.insert(va) {
continue;
}
let t = match type_at_va(ctx, va, types_base, abi) {
Some(t) => t,
None => continue,
};
collect_type_refs(&t, types_base, |r| {
if in_range(r) && !visited.contains(&r) {
queue.push_back(r);
}
});
out.push(t);
}
out
}
fn collect_type_refs(t: &GoType<'_>, types_base: u64, mut push: impl FnMut(u64)) {
let off_to_va = |off: i32| (types_base as i64).saturating_add(off as i64) as u64;
if t.ptr_to_this != 0 {
push(off_to_va(t.ptr_to_this));
}
match &t.detail {
TypeDetail::Array { elem_va, .. }
| TypeDetail::Chan { elem_va, .. }
| TypeDetail::Pointer { elem_va }
| TypeDetail::Slice { elem_va } => push(*elem_va),
TypeDetail::Map {
key_va,
elem_va,
group_va,
..
} => {
push(*key_va);
push(*elem_va);
push(*group_va);
}
TypeDetail::Struct { fields, .. } => {
for f in fields {
push(f.type_va);
}
}
TypeDetail::Func {
inputs, outputs, ..
} => {
for r in inputs.iter().chain(outputs.iter()) {
push(r.va);
}
}
TypeDetail::Interface { methods, .. } => {
for m in methods {
push(off_to_va(m.type_descriptor_offset));
}
}
TypeDetail::None => {}
}
for m in &t.methods {
if m.type_descriptor_offset != 0 {
push(off_to_va(m.type_descriptor_offset));
}
}
}
fn resolve_name_at_va<'a>(
va: u64,
full_data: &'a [u8],
legacy: bool,
ctx: &BinaryContext<'a>,
) -> Option<&'a str> {
if va == 0 {
return None;
}
ctx.va_to_file(va)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name(d, legacy))
.filter(|s| !s.is_empty())
}
fn resolve_type_name<'a>(
type_va: u64,
full_data: &'a [u8],
types_base_va: u64,
ps: u8,
legacy: bool,
ctx: &BinaryContext<'a>,
) -> Option<&'a str> {
if type_va == 0 {
return None;
}
let off = ctx.va_to_file(type_va)?;
let abi = AbiType::parse(full_data.get(off..)?, ps)?;
let name_va = (types_base_va as i64).saturating_add(abi.str_off as i64) as u64;
let name = ctx
.va_to_file(name_va)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name(d, legacy))?;
if name.is_empty() { None } else { Some(name) }
}
fn build_go_type<'a>(
abi_type: &AbiType,
type_data: &'a [u8],
full_data: &'a [u8],
types_base_va: u64,
abi: TypeAbi,
ctx: &BinaryContext<'a>,
) -> Option<GoType<'a>> {
let TypeAbi {
ps,
legacy_names: legacy,
map_layout,
} = abi;
let kind = TypeKind::from_raw(abi_type.kind());
let name_va = (types_base_va as i64).saturating_add(abi_type.str_off as i64) as u64;
let name: &'a str = ctx
.va_to_file(name_va)
.and_then(|off| full_data.get(off..))
.and_then(|d| decode_name(d, legacy))
.unwrap_or("");
if name.is_empty() && kind == TypeKind::Invalid {
return None;
}
let is_exported = name.chars().next().is_some_and(|c| {
let c = if c == '*' {
name.chars().nth(1).unwrap_or('a')
} else {
c
};
c.is_ascii_uppercase()
});
let base_sz = AbiType::size(ps);
let detail = match kind {
TypeKind::Array => type_data
.get(base_sz..)
.and_then(|d| ArrayTypeExtra::parse(d, ps))
.map(|a| TypeDetail::Array {
len: a.len,
elem_va: a.elem,
slice_va: a.slice,
})
.unwrap_or(TypeDetail::None),
TypeKind::Chan => type_data
.get(base_sz..)
.and_then(|d| ChanTypeExtra::parse(d, ps))
.map(|c| TypeDetail::Chan {
dir: c.dir,
elem_va: c.elem,
})
.unwrap_or(TypeDetail::None),
TypeKind::Func => type_data
.get(base_sz..)
.and_then(FuncTypeExtra::parse)
.map(|f| {
let (inputs, outputs) = read_func_params(
type_data,
base_sz,
f.in_count,
f.num_out(),
abi_type.has_uncommon(),
ps,
legacy,
full_data,
types_base_va,
ctx,
);
TypeDetail::Func {
in_count: f.in_count,
out_count: f.num_out(),
is_variadic: f.is_variadic(),
inputs,
outputs,
}
})
.unwrap_or(TypeDetail::None),
TypeKind::Interface => type_data
.get(base_sz..)
.and_then(|d| InterfaceTypeExtra::parse(d, ps))
.map(|i| {
let methods =
resolve_interface_methods(&i, full_data, types_base_va, ps, legacy, ctx);
let pkg_path = resolve_name_at_va(i.pkg_path, full_data, legacy, ctx);
TypeDetail::Interface {
method_count: i.methods.len,
methods,
pkg_path,
}
})
.unwrap_or(TypeDetail::None),
TypeKind::Map => type_data
.get(base_sz..)
.and_then(|d| MapTypeExtra::parse(d, ps, map_layout))
.map(|m| TypeDetail::Map {
key_va: m.key,
elem_va: m.elem,
group_va: m.group,
extra: Box::new(m),
})
.unwrap_or(TypeDetail::None),
TypeKind::Pointer => type_data
.get(base_sz..)
.and_then(|d| ElemTypeExtra::parse(d, ps))
.map(|e| TypeDetail::Pointer { elem_va: e.elem })
.unwrap_or(TypeDetail::None),
TypeKind::Slice => type_data
.get(base_sz..)
.and_then(|d| ElemTypeExtra::parse(d, ps))
.map(|e| TypeDetail::Slice { elem_va: e.elem })
.unwrap_or(TypeDetail::None),
TypeKind::Struct => type_data
.get(base_sz..)
.and_then(|d| StructTypeExtra::parse(d, ps))
.map(|s| {
let fields = resolve_struct_fields(&s, full_data, types_base_va, ps, legacy, ctx);
TypeDetail::Struct {
field_count: s.fields.len,
fields,
}
})
.unwrap_or(TypeDetail::None),
_ => TypeDetail::None,
};
let (method_count, exported_method_count, methods, pkg_path) = if abi_type.has_uncommon() {
let extra = match kind {
TypeKind::Array => ArrayTypeExtra::size(ps),
TypeKind::Chan => ChanTypeExtra::size(ps),
TypeKind::Func => align_up(base_sz.saturating_add(FuncTypeExtra::SIZE), ps as usize)?
.saturating_sub(base_sz),
TypeKind::Interface => InterfaceTypeExtra::size(ps),
TypeKind::Map => {
MapTypeExtra::size(ps, map_layout.resolve_for(type_data.get(base_sz..)?, ps))
}
TypeKind::Pointer | TypeKind::Slice => ElemTypeExtra::size(ps),
TypeKind::Struct => StructTypeExtra::size(ps),
_ => 0,
};
let concrete_sz = base_sz.saturating_add(extra);
match type_data.get(concrete_sz..).and_then(UncommonType::parse) {
Some(u) => {
let methods = resolve_concrete_methods(
&u,
type_data,
concrete_sz,
full_data,
types_base_va,
ps,
legacy,
ctx,
);
let pkg_path = if u.pkg_path != 0 {
let name_va = (types_base_va as i64).saturating_add(u.pkg_path as i64) as u64;
ctx.va_to_file(name_va)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name(d, legacy))
.filter(|s| !s.is_empty())
} else {
None
};
(u.mcount, u.xcount, methods, pkg_path)
}
None => (0, 0, Vec::new(), None),
}
} else {
(0, 0, Vec::new(), None)
};
Some(GoType {
descriptor_va: 0, name,
kind,
size: abi_type.size_,
align: abi_type.align_,
field_align: abi_type.field_align_,
ptr_bytes: abi_type.ptr_bytes,
hash: abi_type.hash,
tflag: abi_type.tflag,
ptr_to_this: abi_type.ptr_to_this,
equal_va: abi_type.equal,
gcdata_va: abi_type.gcdata,
pkg_path,
has_uncommon: abi_type.has_uncommon(),
is_named: abi_type.is_named(),
is_exported,
method_count,
exported_method_count,
detail,
methods,
})
}
#[allow(clippy::too_many_arguments)]
fn read_func_params<'a>(
type_data: &'a [u8],
base_sz: usize,
in_count: u16,
out_count: u16,
has_uncommon: bool,
ps: u8,
legacy: bool,
full_data: &'a [u8],
types_base_va: u64,
ctx: &BinaryContext<'a>,
) -> (Vec<TypeRef<'a>>, Vec<TypeRef<'a>>) {
let p = ps as usize;
if p == 0 {
return (Vec::new(), Vec::new());
}
let uncommon_sz = if has_uncommon { UncommonType::SIZE } else { 0 };
let params_off = match base_sz
.checked_add(FuncTypeExtra::SIZE)
.and_then(|x| align_up(x, p))
.and_then(|x| x.checked_add(uncommon_sz))
{
Some(o) => o,
None => return (Vec::new(), Vec::new()),
};
let read_one = |idx: usize| -> Option<TypeRef<'a>> {
let pos = params_off.checked_add(idx.checked_mul(p)?)?;
let va = read_uintptr(type_data, pos, ps)?;
let name = resolve_type_name(va, full_data, types_base_va, ps, legacy, ctx);
Some(TypeRef { va, name })
};
let mut inputs = Vec::with_capacity(in_count as usize);
for i in 0..(in_count as usize) {
match read_one(i) {
Some(v) => inputs.push(v),
None => break,
}
}
let mut outputs = Vec::with_capacity(out_count as usize);
for i in 0..(out_count as usize) {
let idx = match (in_count as usize).checked_add(i) {
Some(v) => v,
None => break,
};
match read_one(idx) {
Some(v) => outputs.push(v),
None => break,
}
}
(inputs, outputs)
}
#[allow(clippy::too_many_arguments)]
fn resolve_concrete_methods<'a>(
uncommon: &UncommonType,
type_data: &'a [u8],
uncommon_off_in_type: usize,
full_data: &'a [u8],
types_base_va: u64,
ps: u8,
legacy: bool,
ctx: &BinaryContext<'a>,
) -> Vec<MethodEntry<'a>> {
let mcount = uncommon.mcount as usize;
if mcount == 0 {
return Vec::new();
}
let methods_start = match uncommon_off_in_type.checked_add(uncommon.moff as usize) {
Some(s) => s,
None => return Vec::new(),
};
let mut out = Vec::new();
for i in 0..mcount {
let off = match i
.checked_mul(GoMethod::SIZE)
.and_then(|delta| methods_start.checked_add(delta))
{
Some(o) => o,
None => break,
};
let m = match type_data.get(off..).and_then(GoMethod::parse) {
Some(m) => m,
None => break,
};
let name_va = (types_base_va as i64).saturating_add(m.name as i64) as u64;
let (name, flags): (&'a str, u8) = match ctx
.va_to_file(name_va)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name_with_flags(d, legacy))
{
Some((n, f)) if !n.is_empty() => (n, f),
_ => break,
};
let is_exported = (flags & NAME_FLAG_EXPORTED) != 0
|| name.chars().next().is_some_and(|c| c.is_ascii_uppercase());
let function_text_offset = if m.tfn > 0 { Some(m.tfn) } else { None };
let interface_text_offset = if m.ifn > 0 { Some(m.ifn) } else { None };
let type_va = (types_base_va as i64).saturating_add(m.mtyp as i64) as u64;
let type_name = resolve_type_name(type_va, full_data, types_base_va, ps, legacy, ctx);
out.push(MethodEntry {
name,
type_descriptor_offset: m.mtyp,
type_name,
function_text_offset,
interface_text_offset,
is_exported,
});
}
out
}
fn resolve_interface_methods<'a>(
iface: &InterfaceTypeExtra,
full_data: &'a [u8],
types_base_va: u64,
ps: u8,
legacy: bool,
ctx: &BinaryContext<'a>,
) -> Vec<InterfaceMethod<'a>> {
let count = iface.methods.len as usize;
if count == 0 {
return Vec::new();
}
let array_off = match ctx.va_to_file(iface.methods.ptr) {
Some(o) => o,
None => return Vec::new(),
};
let bytes = match full_data.get(array_off..) {
Some(b) => b,
None => return Vec::new(),
};
let mut out = Vec::new();
for i in 0..count {
let off = match i.checked_mul(GoImethod::SIZE) {
Some(o) => o,
None => break,
};
let im = match bytes.get(off..).and_then(GoImethod::parse) {
Some(im) => im,
None => break,
};
let name_va = (types_base_va as i64).saturating_add(im.name as i64) as u64;
let name: &'a str = match ctx
.va_to_file(name_va)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name(d, legacy))
{
Some(n) if !n.is_empty() => n,
_ => break,
};
let type_va = (types_base_va as i64).saturating_add(im.typ as i64) as u64;
let type_name = resolve_type_name(type_va, full_data, types_base_va, ps, legacy, ctx);
out.push(InterfaceMethod {
name,
type_descriptor_offset: im.typ,
type_name,
});
}
out
}
fn resolve_struct_fields<'a>(
extra: &StructTypeExtra,
full_data: &'a [u8],
types_base_va: u64,
ps: u8,
legacy: bool,
ctx: &BinaryContext<'a>,
) -> Vec<StructField<'a>> {
let count = extra.fields.len as usize;
if count == 0 {
return Vec::new();
}
let array_off = match ctx.va_to_file(extra.fields.ptr) {
Some(o) => o,
None => return Vec::new(),
};
let bytes = match full_data.get(array_off..) {
Some(b) => b,
None => return Vec::new(),
};
let stride = GoStructField::size(ps);
let mut out = Vec::new();
for i in 0..count {
let off = match i.checked_mul(stride) {
Some(o) => o,
None => break,
};
let f = match bytes.get(off..).and_then(|d| GoStructField::parse(d, ps)) {
Some(f) => f,
None => break,
};
let (name, flags, tag): (&'a str, u8, Option<&'a str>) = ctx
.va_to_file(f.name)
.and_then(|o| full_data.get(o..))
.and_then(|d| decode_name_and_tag(d, legacy))
.unwrap_or(("", 0, None));
let is_embedded = (flags & NAME_FLAG_EMBEDDED) != 0;
let type_name = resolve_type_name(f.typ, full_data, types_base_va, ps, legacy, ctx);
out.push(StructField {
name,
type_va: f.typ,
type_name,
tag,
offset: f.offset,
is_embedded,
});
}
out
}