diff --git a/Cargo.lock b/Cargo.lock index 67c04f6..9d5bf55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,7 +310,8 @@ dependencies = [ "flate2", "glob", "hex", - "indexmap", + "indent", + "indexmap 2.0.2", "itertools", "log", "memchr", @@ -334,12 +335,22 @@ dependencies = [ "sha-1", "smallvec", "supports-color 2.1.0", + "syntect", "tracing", "tracing-attributes", "tracing-subscriber", "xxhash-rust", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.6" @@ -428,6 +439,12 @@ dependencies = [ "miniz_oxide 0.7.1", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.6" @@ -459,6 +476,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.1" @@ -489,6 +512,22 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "indent" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1a0777d972970f204fdf8ef319f1f4f8459131636d7e3c96c5d59570d0fa6" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.0.2" @@ -496,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.1", ] [[package]] @@ -543,6 +582,21 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.10" @@ -681,8 +735,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "crc32fast", - "hashbrown", - "indexmap", + "hashbrown 0.14.1", + "indexmap 2.0.2", "memchr", ] @@ -692,6 +746,28 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags 1.3.2", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "overload" version = "0.1.1" @@ -720,7 +796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 2.0.2", ] [[package]] @@ -729,6 +805,32 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "plist" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa" +dependencies = [ + "base64", + "indexmap 1.9.3", + "line-wrap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppc750cl" version = "0.2.0" @@ -770,6 +872,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.33" @@ -883,6 +994,21 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -937,7 +1063,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap", + "indexmap 2.0.2", "itoa", "ryu", "serde", @@ -1018,6 +1144,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syntect" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax 0.7.5", + "serde", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -1057,6 +1204,35 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "toml" version = "0.5.11" @@ -1179,6 +1355,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1195,6 +1381,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1329,3 +1524,12 @@ name = "xxhash-rust" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9828b178da53440fa9c766a3d2f73f7cf5d0ac1fe3980c1e5018d899fd19e07b" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index b1e9405..4cdf9ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ flagset = { version = "0.4.4", features = ["serde"] } flate2 = "1.0.27" glob = "0.3.1" hex = "0.4.3" +indent = "0.1.1" indexmap = "2.0.2" itertools = "0.11.0" log = "0.4.20" @@ -59,6 +60,7 @@ serde_yaml = "0.9.25" sha-1 = "0.10.1" smallvec = "1.11.1" supports-color = "2.1.0" +syntect = "5.1.0" tracing = "0.1.37" tracing-attributes = "0.1.26" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/src/cmd/dwarf.rs b/src/cmd/dwarf.rs index e932bdd..8ea1ef3 100644 --- a/src/cmd/dwarf.rs +++ b/src/cmd/dwarf.rs @@ -2,16 +2,21 @@ use std::{ collections::{btree_map, BTreeMap}, io::{stdout, Cursor, Read, Write}, path::PathBuf, + str::from_utf8, }; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use argp::FromArgs; use object::{elf, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section}; +use syntect::{ + highlighting::{Color, HighlightIterator, HighlightState, Highlighter, Theme, ThemeSet}, + parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}, +}; use crate::util::{ dwarf::{ - process_address, process_type, process_variable_location, read_debug_section, type_string, - ud_type, ud_type_def, ud_type_string, AttributeKind, TagKind, + process_root_tag, read_debug_section, should_skip_tag, tag_type_string, AttributeKind, + TagKind, }, file::{buf_writer, map_file}, }; @@ -40,6 +45,9 @@ pub struct DumpArgs { #[argp(option, short = 'o')] /// Output file. (Or directory, for archive) out: Option, + #[argp(switch)] + /// Disable color output. + no_color: bool, } pub fn run(args: Args) -> Result<()> { @@ -49,6 +57,12 @@ pub fn run(args: Args) -> Result<()> { } fn dump(args: DumpArgs) -> Result<()> { + let theme_set = ThemeSet::load_defaults(); + let theme = theme_set.themes.get("Solarized (dark)").context("Failed to load theme")?; + let syntax_set = SyntaxSet::load_defaults_newlines(); + let syntax = + syntax_set.find_syntax_by_extension("cpp").context("Failed to find syntax")?.clone(); + let file = map_file(&args.in_file)?; let buf = file.as_slice(); if buf.starts_with(b"!\n") { @@ -80,9 +94,13 @@ fn dump(args: DumpArgs) -> Result<()> { let mut file = buf_writer(file_path)?; dump_debug_section(&mut file, &obj_file, debug_section)?; file.flush()?; - } else { - println!("\nFile {}:", name); + } else if args.no_color { + println!("\n// File {}:", name); dump_debug_section(&mut stdout(), &obj_file, debug_section)?; + } else { + let mut writer = HighlightWriter::new(syntax_set.clone(), syntax.clone(), theme); + writeln!(writer, "\n// File {}:", name)?; + dump_debug_section(&mut writer, &obj_file, debug_section)?; } } } else { @@ -94,8 +112,11 @@ fn dump(args: DumpArgs) -> Result<()> { let mut file = buf_writer(out_path)?; dump_debug_section(&mut file, &obj_file, debug_section)?; file.flush()?; - } else { + } else if args.no_color { dump_debug_section(&mut stdout(), &obj_file, debug_section)?; + } else { + let mut writer = HighlightWriter::new(syntax_set, syntax, theme); + dump_debug_section(&mut writer, &obj_file, debug_section)?; } } Ok(()) @@ -145,140 +166,37 @@ where .string_attribute(AttributeKind::Name) .ok_or_else(|| anyhow!("CompileUnit without name {:?}", tag))?; if units.contains(unit) { - log::warn!("Duplicate unit '{}'", unit); + // log::warn!("Duplicate unit '{}'", unit); } else { units.push(unit.clone()); } + writeln!(w, "\n// Compile unit: {}", unit)?; let children = tag.children(&tags); let mut typedefs = BTreeMap::>::new(); for child in children { - match child.kind { - TagKind::GlobalSubroutine | TagKind::Subroutine => { - let _is_prototyped = - child.string_attribute(AttributeKind::Prototyped).is_some(); - if let (Some(_hi), Some(_lo)) = ( - child.address_attribute(AttributeKind::HighPc), - child.address_attribute(AttributeKind::LowPc), - ) {} - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Subroutine without name"))?; - let udt = ud_type(&tags, child)?; - let ts = ud_type_string(&tags, &typedefs, &udt)?; - writeln!(w, "{} {}{} {{", ts.prefix, name, ts.suffix)?; - for tag in child.children(&tags) { - match tag.kind { - TagKind::LocalVariable => {} - _ => continue, - } - let type_attr = tag.type_attribute().ok_or_else(|| { - anyhow!("LocalVariable without type attr") - })?; - let var_type = process_type(type_attr)?; - let ts = type_string(&tags, &typedefs, &var_type)?; - let name = tag - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("LocalVariable without name"))?; - write!(w, "\t{} {}{};", ts.prefix, name, ts.suffix)?; - if let Some(location) = - tag.block_attribute(AttributeKind::Location) - { - if !location.is_empty() { - write!( - w, - " // {}", - process_variable_location(location)? - )?; - } - } - writeln!(w)?; - } - writeln!(w, "}}")?; - } - TagKind::Typedef => { - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Typedef without name"))?; - let attr = child - .type_attribute() - .ok_or_else(|| anyhow!("Typedef without type attribute"))?; - let t = process_type(attr)?; - let ts = type_string(&tags, &typedefs, &t)?; - writeln!(w, "typedef {} {}{};", ts.prefix, name, ts.suffix)?; + let tag_type = process_root_tag(&tags, child)?; + if should_skip_tag(&tag_type) { + continue; + } + writeln!(w, "{}", tag_type_string(&tags, &typedefs, &tag_type)?)?; - // TODO fundamental typedefs? - if let Some(ud_type_ref) = - child.reference_attribute(AttributeKind::UserDefType) - { - match typedefs.entry(ud_type_ref) { - btree_map::Entry::Vacant(e) => { - e.insert(vec![child.key]); - } - btree_map::Entry::Occupied(e) => { - e.into_mut().push(child.key); - } + if let TagKind::Typedef = child.kind { + // TODO fundamental typedefs? + if let Some(ud_type_ref) = + child.reference_attribute(AttributeKind::UserDefType) + { + match typedefs.entry(ud_type_ref) { + btree_map::Entry::Vacant(e) => { + e.insert(vec![child.key]); + } + btree_map::Entry::Occupied(e) => { + e.into_mut().push(child.key); } } } - TagKind::GlobalVariable | TagKind::LocalVariable => { - let name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Variable without name"))?; - let address = if let Some(location) = - child.block_attribute(AttributeKind::Location) - { - Some(process_address(location)?) - } else { - None - }; - if let Some(type_attr) = child.type_attribute() { - let var_type = process_type(type_attr)?; - // log::info!("{:?}", var_type); - // if let TypeKind::UserDefined(key) = var_type.kind { - // let ud_tag = tags - // .get(&key) - // .ok_or_else(|| anyhow!("Invalid UD type ref"))?; - // let ud_type = ud_type(&tags, ud_tag)?; - // log::info!("{:?}", ud_type); - // } - let ts = type_string(&tags, &typedefs, &var_type)?; - let st = if child.kind == TagKind::LocalVariable { - "static " - } else { - "" - }; - let address_str = match address { - Some(addr) => format!(" : {:#010X}", addr), - None => String::new(), - }; - let size = var_type.size(&tags)?; - writeln!( - w, - "{}{} {}{}{}; // size: {:#X}", - st, ts.prefix, name, ts.suffix, address_str, size, - )?; - } - } - TagKind::StructureType - | TagKind::ArrayType - | TagKind::EnumerationType - | TagKind::UnionType - | TagKind::ClassType - | TagKind::SubroutineType => { - let udt = ud_type(&tags, child)?; - if child.string_attribute(AttributeKind::Name).is_some() { - writeln!(w, "{}", ud_type_def(&tags, &typedefs, &udt)?)?; - } else { - // log::warn!("No name for tag: {:?}", child); - } - } - _ => { - log::warn!("Unhandled CompileUnit child {:?}", child.kind); - } } } - // println!("Children: {:?}", children.iter().map(|c| c.kind).collect::>()); } _ => { log::warn!("Expected CompileUnit, got {:?}", tag.kind); @@ -298,3 +216,81 @@ where // } Ok(()) } + +struct HighlightWriter<'a> { + line: String, + highlighter: Highlighter<'a>, + parse_state: ParseState, + highlight_state: HighlightState, + syntax_set: SyntaxSet, +} + +impl<'a> HighlightWriter<'a> { + pub fn new( + syntax_set: SyntaxSet, + syntax: SyntaxReference, + theme: &'a Theme, + ) -> HighlightWriter<'a> { + let highlighter = Highlighter::new(theme); + let highlight_state = HighlightState::new(&highlighter, ScopeStack::new()); + HighlightWriter { + line: String::new(), + highlighter, + syntax_set, + parse_state: ParseState::new(&syntax), + highlight_state, + } + } +} + +#[inline] +fn blend_fg_color(fg: Color, bg: Color) -> Color { + if fg.a == 0xff { + return fg; + } + let ratio = fg.a as u32; + let r = (fg.r as u32 * ratio + bg.r as u32 * (255 - ratio)) / 255; + let g = (fg.g as u32 * ratio + bg.g as u32 * (255 - ratio)) / 255; + let b = (fg.b as u32 * ratio + bg.b as u32 * (255 - ratio)) / 255; + Color { r: r as u8, g: g as u8, b: b as u8, a: 255 } +} + +impl Write for HighlightWriter<'_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let str = from_utf8(buf).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + for s in str.split_inclusive('\n') { + self.line.push_str(s); + if self.line.ends_with('\n') { + self.flush()?; + } + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + if self.line.is_empty() { + return Ok(()); + } + let ops = self + .parse_state + .parse_line(&self.line, &self.syntax_set) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let iter = HighlightIterator::new( + &mut self.highlight_state, + &ops[..], + &self.line, + &self.highlighter, + ); + for (style, text) in iter { + print!( + "\x1b[48;2;{};{};{}m", + style.background.r, style.background.g, style.background.b + ); + let fg = blend_fg_color(style.foreground, style.background); + print!("\x1b[38;2;{};{};{}m{}", fg.r, fg.g, fg.b, text); + } + print!("\x1b[0m"); + self.line.clear(); + Ok(()) + } +} diff --git a/src/util/dwarf.rs b/src/util/dwarf.rs index bfe346e..fb21f21 100644 --- a/src/util/dwarf.rs +++ b/src/util/dwarf.rs @@ -7,6 +7,7 @@ use std::{ }; use anyhow::{anyhow, bail, ensure, Context, Result}; +use indent::indent_all_by; use num_enum::{IntoPrimitive, TryFromPrimitive}; use crate::{ @@ -56,6 +57,7 @@ pub enum TagKind { #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] #[repr(u16)] pub enum FundType { + WideChar = 0x0000, // Likely an MW bug Char = 0x0001, SignedChar = 0x0002, UnsignedChar = 0x0003, @@ -82,13 +84,17 @@ pub enum FundType { LongLong = 0x8008, SignedLongLong = 0x8108, UnsignedLongLong = 0x8208, + Vec2x32Float = 0xac00, } impl FundType { - fn size(self) -> Result { + pub fn size(self) -> Result { Ok(match self { FundType::Char | FundType::SignedChar | FundType::UnsignedChar | FundType::Boolean => 1, - FundType::Short | FundType::SignedShort | FundType::UnsignedShort => 2, + FundType::WideChar + | FundType::Short + | FundType::SignedShort + | FundType::UnsignedShort => 2, FundType::Integer | FundType::SignedInteger | FundType::UnsignedInteger => 4, FundType::Long | FundType::SignedLong @@ -98,7 +104,8 @@ impl FundType { FundType::DblPrecFloat | FundType::LongLong | FundType::SignedLongLong - | FundType::UnsignedLongLong => 8, + | FundType::UnsignedLongLong + | FundType::Vec2x32Float => 8, FundType::Void => 0, FundType::ExtPrecFloat | FundType::Complex @@ -107,6 +114,38 @@ impl FundType { | FundType::Label => bail!("Unhandled fundamental type {self:?}"), }) } + + pub fn name(self) -> Result<&'static str> { + Ok(match self { + FundType::WideChar => "wchar_t", + FundType::Char => "char", + FundType::SignedChar => "signed char", + FundType::UnsignedChar => "unsigned char", + FundType::Short => "short", + FundType::SignedShort => "signed short", + FundType::UnsignedShort => "unsigned short", + FundType::Integer => "int", + FundType::SignedInteger => "signed int", + FundType::UnsignedInteger => "unsigned int", + FundType::Long => "long", + FundType::SignedLong => "signed long", + FundType::UnsignedLong => "unsigned long", + FundType::Pointer => "void *", + FundType::Float => "float", + FundType::DblPrecFloat => "double", + FundType::ExtPrecFloat => "long double", + FundType::Void => "void", + FundType::Boolean => "bool", + FundType::Complex + | FundType::DblPrecComplex + | FundType::ExtPrecComplex + | FundType::Label => bail!("Unhandled fundamental type {self:?}"), + FundType::LongLong => "long long", + FundType::SignedLongLong => "signed long long", + FundType::UnsignedLongLong => "unsigned long long", + FundType::Vec2x32Float => "__vec2x32float__", + }) + } } #[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)] @@ -144,6 +183,9 @@ pub enum LocationOp { Deref4 = 0x06, Add = 0x07, // User types + MwFpReg = 0x80, + MwFpDReg = 0x81, + MwDRef8 = 0x82, } const FORM_MASK: u16 = 0xF; @@ -224,9 +266,10 @@ pub enum AttributeKind { LoUser = 0x2000, HiUser = 0x3ff0, // User types - Unknown200 = 0x2000 | (FormKind::String as u16), - MwVariableRef = 0x2020 | (FormKind::Ref as u16), - Unknown234 = 0x2340 | (FormKind::Block2 as u16), + MwMangled = 0x2000 | (FormKind::String as u16), + MwGlobalRef = 0x2020 | (FormKind::Ref as u16), + MwGlobalRefByName = 0x2030 | (FormKind::String as u16), + MwDwarf2Location = 0x2340 | (FormKind::Block2 as u16), Unknown800 = 0x8000 | (FormKind::Data4 as u16), Unknown801 = 0x8010 | (FormKind::Data4 as u16), MwPrologueEnd = 0x8040 | (FormKind::Addr as u16), @@ -498,7 +541,6 @@ pub struct ArrayType { #[derive(Debug, Clone)] pub struct BitData { - pub byte_size: u32, pub bit_size: u32, pub bit_offset: u16, } @@ -509,19 +551,45 @@ pub struct StructureMember { pub kind: Type, pub offset: u32, pub bit: Option, + pub visibility: Visibility, + pub byte_size: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StructureKind { + Struct, + Class, } #[derive(Debug, Clone)] pub struct StructureType { + pub kind: StructureKind, pub name: Option, pub byte_size: u32, pub members: Vec, + pub bases: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Visibility { + Private, + Protected, + Public, +} + +#[derive(Debug, Clone)] +pub struct StructureBase { + pub name: String, + pub base_type: Type, + pub offset: u32, + pub visibility: Visibility, + pub virtual_base: bool, } #[derive(Debug, Clone)] pub struct EnumerationMember { pub name: String, - pub value: u32, + pub value: i32, } #[derive(Debug, Clone)] @@ -531,31 +599,44 @@ pub struct EnumerationType { pub members: Vec, } -#[derive(Debug, Clone)] -pub struct UnionMember { - pub name: String, - pub kind: Type, -} - #[derive(Debug, Clone)] pub struct UnionType { pub name: Option, pub byte_size: u32, - pub members: Vec, + pub members: Vec, } #[derive(Debug, Clone)] pub struct SubroutineParameter { pub name: Option, pub kind: Type, + pub location: Option, +} + +#[derive(Debug, Clone)] +pub struct SubroutineVariable { + pub name: Option, + pub kind: Type, + pub location: Option, } #[derive(Debug, Clone)] pub struct SubroutineType { + pub name: Option, + pub mangled_name: Option, pub return_type: Type, pub parameters: Vec, pub var_args: bool, pub prototyped: bool, + pub references: Vec, + pub member_of: Option, + pub variables: Vec, +} + +#[derive(Debug, Clone)] +pub struct PtrToMemberType { + pub kind: Type, + pub containing_type: u32, } #[derive(Debug, Clone)] @@ -565,15 +646,39 @@ pub enum UserDefinedType { Enumeration(EnumerationType), Union(UnionType), Subroutine(SubroutineType), + PtrToMember(PtrToMemberType), +} + +#[derive(Debug, Clone)] +pub struct VariableTag { + pub name: Option, + pub mangled_name: Option, + pub kind: Type, + pub address: Option, + pub local: bool, +} + +#[derive(Debug, Clone)] +pub struct TypedefTag { + pub name: String, + pub kind: Type, +} + +#[derive(Debug, Clone)] +pub enum TagType { + Variable(VariableTag), + Typedef(TypedefTag), + UserDefined(UserDefinedType), } impl UserDefinedType { pub fn is_definition(&self) -> bool { match self { - UserDefinedType::Array(_) | UserDefinedType::Subroutine(_) => false, + UserDefinedType::Array(_) | UserDefinedType::PtrToMember(_) => false, UserDefinedType::Structure(t) => t.name.is_some(), UserDefinedType::Enumeration(t) => t.name.is_some(), UserDefinedType::Union(t) => t.name.is_some(), + UserDefinedType::Subroutine(t) => t.name.is_some(), } } @@ -590,6 +695,7 @@ impl UserDefinedType { UserDefinedType::Enumeration(t) => t.byte_size, UserDefinedType::Union(t) => t.byte_size, UserDefinedType::Subroutine(_) => 0, + UserDefinedType::PtrToMember(_) => 4, }) } } @@ -630,7 +736,11 @@ pub fn apply_modifiers(mut str: TypeString, modifiers: &[Modifier]) -> Result { if !has_pointer && !str.suffix.is_empty() { - str.prefix.push_str(" (*"); + if str.member.is_empty() { + str.prefix.push_str(" (*"); + } else { + write!(str.prefix, " ({}*", str.member)?; + } str.suffix.insert(0, ')'); } else { str.prefix.push_str(" *"); @@ -665,22 +775,35 @@ pub fn apply_modifiers(mut str: TypeString, modifiers: &[Modifier]) -> Result) -> std::fmt::Result { - write!(f, "{}{}", self.prefix, self.suffix) + if self.member.is_empty() { + write!(f, "{}{}", self.prefix, self.suffix)?; + } else { + // TODO member print likely wrong + write!(f, "{} {}{}", self.prefix, self.member, self.suffix)?; + } + Ok(()) } } -pub fn type_string(tags: &TagMap, typedefs: &TypedefMap, t: &Type) -> Result { +pub fn type_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &Type, + include_anonymous_def: bool, +) -> Result { let str = match t.kind { TypeKind::Fundamental(ft) => { - TypeString { prefix: fund_type_string(ft)?.to_string(), suffix: String::new() } + TypeString { prefix: ft.name()?.to_string(), ..Default::default() } } TypeKind::UserDefined(key) => { if let Some(&td_key) = typedefs.get(&key).and_then(|v| v.first()) { @@ -689,119 +812,367 @@ pub fn type_string(tags: &TagMap, typedefs: &TypedefMap, t: &Type) -> Result Result { + let mut out = type_string(tags, typedefs, t.element_type.as_ref(), include_anonymous_def)?; + for dim in &t.dimensions { + ensure!( + matches!( + dim.index_type.kind, + TypeKind::Fundamental(FundType::Long | FundType::Integer) + ), + "Unsupported array index type '{}'", + type_string(tags, typedefs, &dim.index_type, true)? + ); + match dim.size { + None => out.suffix.insert_str(0, "[]"), + Some(size) => out.suffix = format!("[{}]{}", size, out.suffix), + }; + } + Ok(out) +} + +fn structure_type_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &StructureType, + include_keyword: bool, + include_anonymous_def: bool, +) -> Result { + let prefix = if let Some(name) = t.name.as_ref() { + if include_keyword { + match t.kind { + StructureKind::Struct => format!("struct {}", name), + StructureKind::Class => format!("class {}", name), + } + } else { + name.clone() + } + } else if include_anonymous_def { + struct_def_string(tags, typedefs, t)? + } else if include_keyword { + match t.kind { + StructureKind::Struct => "struct [anonymous]".to_string(), + StructureKind::Class => "class [anonymous]".to_string(), + } + } else { + match t.kind { + StructureKind::Struct => "[anonymous struct]".to_string(), + StructureKind::Class => "[anonymous class]".to_string(), + } + }; + Ok(TypeString { prefix, ..Default::default() }) +} + +fn enumeration_type_string( + _tags: &TagMap, + _typedefs: &TypedefMap, + t: &EnumerationType, + include_keyword: bool, + include_anonymous_def: bool, +) -> Result { + let prefix = if let Some(name) = t.name.as_ref() { + if include_keyword { + format!("enum {}", name) + } else { + name.clone() + } + } else if include_anonymous_def { + enum_def_string(t)? + } else if include_keyword { + "enum [anonymous]".to_string() + } else { + "[anonymous enum]".to_string() + }; + Ok(TypeString { prefix, ..Default::default() }) +} + +fn union_type_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &UnionType, + include_keyword: bool, + include_anonymous_def: bool, +) -> Result { + let prefix = if let Some(name) = t.name.as_ref() { + if include_keyword { + format!("union {}", name) + } else { + name.clone() + } + } else if include_anonymous_def { + union_def_string(tags, typedefs, t)? + } else if include_keyword { + "union [anonymous]".to_string() + } else { + "[anonymous union]".to_string() + }; + Ok(TypeString { prefix, ..Default::default() }) +} + pub fn ud_type_string( tags: &TagMap, typedefs: &TypedefMap, t: &UserDefinedType, + include_keyword: bool, + include_anonymous_def: bool, ) -> Result { Ok(match t { - UserDefinedType::Array(t) => { - let mut out = type_string(tags, typedefs, t.element_type.as_ref())?; - for dim in &t.dimensions { - ensure!( - matches!( - dim.index_type.kind, - TypeKind::Fundamental(FundType::Long | FundType::Integer) - ), - "Unsupported array index type '{}'", - type_string(tags, typedefs, &dim.index_type)? - ); - match dim.size { - None => out.suffix.insert_str(0, "[]"), - Some(size) => out.suffix = format!("[{}]{}", size, out.suffix), - }; - } - out - } + UserDefinedType::Array(t) => array_type_string(tags, typedefs, t, include_anonymous_def)?, UserDefinedType::Structure(t) => { - let struct_str = if let Some(name) = t.name.as_ref() { - format!("struct {}", name) - } else { - struct_def_string(tags, typedefs, t)? - }; - TypeString { prefix: struct_str, suffix: String::new() } + structure_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)? } UserDefinedType::Enumeration(t) => { - let struct_str = if let Some(name) = t.name.as_ref() { - format!("enum {}", name) - } else { - enum_def_string(t)? - }; - TypeString { prefix: struct_str, suffix: String::new() } + enumeration_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)? } UserDefinedType::Union(t) => { - let struct_str = if let Some(name) = t.name.as_ref() { - format!("union {}", name) - } else { - union_def_string(tags, typedefs, t)? - }; - TypeString { prefix: struct_str, suffix: String::new() } - } - UserDefinedType::Subroutine(t) => { - let mut out = type_string(tags, typedefs, &t.return_type)?; - let mut parameters = String::new(); - if t.parameters.is_empty() && t.prototyped { - parameters = "void".to_string() - } else { - for (idx, parameter) in t.parameters.iter().enumerate() { - if idx > 0 { - write!(parameters, ", ")?; - } - let ts = type_string(tags, typedefs, ¶meter.kind)?; - if let Some(name) = ¶meter.name { - write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?; - } else { - write!(parameters, "{}{}", ts.prefix, ts.suffix)?; - } - } - } - out.suffix = format!("({}){}", parameters, out.suffix); - out + union_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)? } + UserDefinedType::Subroutine(t) => subroutine_type_string(tags, typedefs, t)?, + UserDefinedType::PtrToMember(t) => ptr_to_member_type_string(tags, typedefs, t)?, + }) +} + +fn ptr_to_member_type_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &PtrToMemberType, +) -> Result { + let ts = type_string(tags, typedefs, &t.kind, true)?; + let containing_type = tags + .get(&t.containing_type) + .ok_or_else(|| anyhow!("Failed to locate containing type {}", t.containing_type))?; + let containing_ts = + ud_type_string(tags, typedefs, &ud_type(tags, containing_type)?, false, false)?; + Ok(TypeString { + prefix: format!("{} ({}::*", ts.prefix, containing_ts.prefix), + suffix: format!("{}){}", containing_ts.suffix, ts.suffix), + ..Default::default() }) } pub fn ud_type_def(tags: &TagMap, typedefs: &TypedefMap, t: &UserDefinedType) -> Result { match t { - UserDefinedType::Array(_) | UserDefinedType::Subroutine(_) => { - Err(anyhow!("Can't define non-definition type")) + UserDefinedType::Array(t) => { + let ts = array_type_string(tags, typedefs, t, false)?; + Ok(format!("// Array: {}{}", ts.prefix, ts.suffix)) } + UserDefinedType::Subroutine(t) => Ok(subroutine_def_string(tags, typedefs, t)?), UserDefinedType::Structure(t) => Ok(struct_def_string(tags, typedefs, t)?), UserDefinedType::Enumeration(t) => Ok(enum_def_string(t)?), UserDefinedType::Union(t) => Ok(union_def_string(tags, typedefs, t)?), + UserDefinedType::PtrToMember(t) => { + let ts = ptr_to_member_type_string(tags, typedefs, t)?; + Ok(format!("// PtrToMember: {}{}", ts.prefix, ts.suffix)) + } } } +pub fn subroutine_type_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &SubroutineType, +) -> Result { + let mut out = type_string(tags, typedefs, &t.return_type, true)?; + let mut parameters = String::new(); + if t.parameters.is_empty() { + if t.var_args { + parameters = "...".to_string(); + } else if t.prototyped { + parameters = "void".to_string(); + } + } else { + for (idx, parameter) in t.parameters.iter().enumerate() { + if idx > 0 { + write!(parameters, ", ")?; + } + let ts = type_string(tags, typedefs, ¶meter.kind, true)?; + if let Some(name) = ¶meter.name { + write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?; + } else { + write!(parameters, "{}{}", ts.prefix, ts.suffix)?; + } + if let Some(location) = ¶meter.location { + write!(parameters, " /* {} */", location)?; + } + } + if t.var_args { + write!(parameters, ", ...")?; + } + } + out.suffix = format!("({}){}", parameters, out.suffix); + if let Some(member_of) = t.member_of { + let tag = tags + .get(&member_of) + .ok_or_else(|| anyhow!("Failed to locate member_of tag {}", member_of))?; + let base_name = tag + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("member_of tag {} has no name attribute", member_of))?; + out.member = format!("{}::", base_name); + } + Ok(out) +} + +pub fn subroutine_def_string( + tags: &TagMap, + typedefs: &TypedefMap, + t: &SubroutineType, +) -> Result { + let rt = type_string(tags, typedefs, &t.return_type, true)?; + let mut out = rt.prefix; + out.push(' '); + + let mut name_written = false; + if let Some(member_of) = t.member_of { + let tag = tags + .get(&member_of) + .ok_or_else(|| anyhow!("Failed to locate member_of tag {}", member_of))?; + let base_name = tag + .string_attribute(AttributeKind::Name) + .ok_or_else(|| anyhow!("member_of tag {} has no name attribute", member_of))?; + write!(out, "{}::", base_name)?; + + // Handle constructors and destructors + if let Some(name) = t.name.as_ref() { + if name == "__dt" { + write!(out, "~{}", base_name)?; + name_written = true; + } else if name == "__ct" { + write!(out, "{}", base_name)?; + name_written = true; + } + } + } + if !name_written { + if let Some(name) = t.name.as_ref() { + out.push_str(name); + } + } + let mut parameters = String::new(); + if t.parameters.is_empty() { + if t.var_args { + parameters = "...".to_string(); + } else if t.prototyped { + parameters = "void".to_string(); + } + } else { + for (idx, parameter) in t.parameters.iter().enumerate() { + if idx > 0 { + write!(parameters, ", ")?; + } + let ts = type_string(tags, typedefs, ¶meter.kind, true)?; + if let Some(name) = ¶meter.name { + write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?; + } else { + write!(parameters, "{}{}", ts.prefix, ts.suffix)?; + } + if let Some(location) = ¶meter.location { + write!(parameters, " /* {} */", location)?; + } + } + if t.var_args { + write!(out, ", ...")?; + } + } + write!(out, "({}){} {{", parameters, rt.suffix)?; + + if !t.variables.is_empty() { + writeln!(out, "\n // Local variables")?; + let mut var_out = String::new(); + for variable in &t.variables { + let ts = type_string(tags, typedefs, &variable.kind, true)?; + write!( + var_out, + "{} {}{};", + ts.prefix, + variable.name.as_deref().unwrap_or_default(), + ts.suffix + )?; + if let Some(location) = &variable.location { + write!(var_out, " // {}", location)?; + } + writeln!(var_out)?; + } + write!(out, "{}", indent_all_by(4, var_out))?; + } + + if !t.references.is_empty() { + writeln!(out, "\n // References")?; + for &reference in &t.references { + let tag = tags + .get(&reference) + .ok_or_else(|| anyhow!("Failed to locate reference tag {}", reference))?; + if tag.kind == TagKind::Padding { + writeln!(out, " // -> ??? ({})", reference)?; + continue; + } + let variable = process_variable_tag(tags, tag)?; + writeln!(out, " // -> {}", variable_string(tags, typedefs, &variable, false)?)?; + } + } + + writeln!(out, "}}")?; + Ok(out) +} + pub fn struct_def_string( tags: &TagMap, typedefs: &TypedefMap, t: &StructureType, ) -> Result { - let mut out = match t.name.as_ref() { - Some(name) => format!("struct {} {{\n", name), - None => "struct {\n".to_string(), + let mut out = match t.kind { + StructureKind::Struct => "class".to_string(), + StructureKind::Class => "struct".to_string(), }; - writeln!(out, "\t// total size: {:#X}", t.byte_size)?; - for member in &t.members { - let ts = type_string(tags, typedefs, &member.kind)?; - write!(out, "\t{} {}{}", ts.prefix, member.name, ts.suffix)?; - if let Some(bit) = &member.bit { - write!(out, " : {}", bit.bit_size)?; - } - writeln!(out, "; // offset {:#X}, size {:#X}", member.offset, member.kind.size(tags)?)?; + if let Some(name) = t.name.as_ref() { + write!(out, " {}", name)?; } + let mut wrote_base = false; + for base in &t.bases { + if !wrote_base { + out.push_str(" : "); + wrote_base = true; + } else { + out.push_str(", "); + } + match base.visibility { + Visibility::Private => out.push_str("private "), + Visibility::Protected => out.push_str("protected "), + Visibility::Public => out.push_str("public "), + } + if base.virtual_base { + out.push_str("virtual "); + } + out.push_str(&base.name); + } + writeln!(out, " {{\n // total size: {:#X}", t.byte_size)?; + let mut var_out = String::new(); + for member in &t.members { + let ts = type_string(tags, typedefs, &member.kind, true)?; + write!(var_out, "{} {}{}", ts.prefix, member.name, ts.suffix)?; + if let Some(bit) = &member.bit { + write!(var_out, " : {}", bit.bit_size)?; + } + let size = if let Some(size) = member.byte_size { size } else { member.kind.size(tags)? }; + writeln!(var_out, "; // offset {:#X}, size {:#X}", member.offset, size)?; + } + write!(out, "{}", indent_all_by(4, var_out))?; write!(out, "}}")?; Ok(out) } @@ -811,8 +1182,8 @@ pub fn enum_def_string(t: &EnumerationType) -> Result { Some(name) => format!("enum {} {{\n", name), None => "enum {\n".to_string(), }; - for member in t.members.iter().rev() { - writeln!(out, "\t{} = {},", member.name, member.value)?; + for member in t.members.iter() { + writeln!(out, " {} = {},", member.name, member.value)?; } write!(out, "}}")?; Ok(out) @@ -823,44 +1194,19 @@ pub fn union_def_string(tags: &TagMap, typedefs: &TypedefMap, t: &UnionType) -> Some(name) => format!("union {} {{\n", name), None => "union {\n".to_string(), }; - for member in t.members.iter().rev() { - let ts = type_string(tags, typedefs, &member.kind)?; - writeln!(out, "\t{} {}{};", ts.prefix, member.name, ts.suffix)?; + let mut var_out = String::new(); + for member in t.members.iter() { + let ts = type_string(tags, typedefs, &member.kind, true)?; + write!(var_out, "{} {}{};", ts.prefix, member.name, ts.suffix)?; + let size = if let Some(size) = member.byte_size { size } else { member.kind.size(tags)? }; + write!(var_out, " // offset {:#X}, size {:#X}", member.offset, size)?; + writeln!(var_out)?; } + write!(out, "{}", indent_all_by(4, var_out))?; write!(out, "}}")?; Ok(out) } -pub fn fund_type_string(ft: FundType) -> Result<&'static str> { - Ok(match ft { - FundType::Char => "char", - FundType::SignedChar => "signed char", - FundType::UnsignedChar => "unsigned char", - FundType::Short => "short", - FundType::SignedShort => "signed short", - FundType::UnsignedShort => "unsigned short", - FundType::Integer => "int", - FundType::SignedInteger => "signed int", - FundType::UnsignedInteger => "unsigned int", - FundType::Long => "long", - FundType::SignedLong => "signed long", - FundType::UnsignedLong => "unsigned long", - FundType::Pointer => "void *", - FundType::Float => "float", - FundType::DblPrecFloat => "double", - FundType::ExtPrecFloat => "long double", - FundType::Void => "void", - FundType::Boolean => "bool", - FundType::Complex - | FundType::DblPrecComplex - | FundType::ExtPrecComplex - | FundType::Label => bail!("Unhandled fundamental type {ft:?}"), - FundType::LongLong => "long long", - FundType::SignedLongLong => "signed long long", - FundType::UnsignedLongLong => "unsigned long long", - }) -} - pub fn process_offset(block: &[u8]) -> Result { if block.len() == 6 && block[0] == LocationOp::Const as u8 && block[5] == LocationOp::Add as u8 { @@ -904,7 +1250,9 @@ pub const fn register_name(reg: u32) -> &'static str { } pub fn process_variable_location(block: &[u8]) -> Result { - if block.len() == 5 && block[0] == LocationOp::Register as u8 { + if block.len() == 5 + && (block[0] == LocationOp::Register as u8 || block[0] == LocationOp::BaseRegister as u8) + { Ok(register_name(u32::from_be_bytes(*array_ref!(block, 1, 4))).to_string()) } else if block.len() == 5 && block[0] == LocationOp::Address as u8 { Ok(format!("@ {:#010X}", u32::from_be_bytes(*array_ref!(block, 1, 4)))) @@ -923,208 +1271,488 @@ pub fn process_variable_location(block: &[u8]) -> Result { } } -pub fn ud_type(tags: &TagMap, tag: &Tag) -> Result { - match tag.kind { - TagKind::ArrayType => { - let mut data = tag - .block_attribute(AttributeKind::SubscrData) - .ok_or_else(|| anyhow!("ArrayType without SubscrData"))?; +fn process_inheritance_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::Inheritance, "{:?} is not an Inheritance tag", tag.kind); - let mut element_type = None; - let mut dimensions = Vec::new(); - while !data.is_empty() { - let format = SubscriptFormat::try_from( - data.first().cloned().ok_or_else(|| anyhow!("Empty SubscrData"))?, - ) - .context("Unknown array subscript format")?; - data = &data[1..]; - match format { - SubscriptFormat::FundTypeConstConst => { - let index_type = - FundType::try_from(u16::from_be_bytes(data[..2].try_into()?)) - .context("Invalid fundamental type ID")?; - let low_bound = u32::from_be_bytes(data[2..6].try_into()?); - ensure!(low_bound == 0, "Invalid array low bound {low_bound}, expected 0"); - let high_bound = u32::from_be_bytes(data[6..10].try_into()?); - data = &data[10..]; - dimensions.push(ArrayDimension { - index_type: Type { - kind: TypeKind::Fundamental(index_type), - modifiers: vec![], - }, - // u32::MAX will wrap to 0, meaning unbounded - size: NonZeroU32::new(high_bound.wrapping_add(1)), - }); - } - SubscriptFormat::ElementType => { - let mut cursor = Cursor::new(data); - let type_attr = read_attribute(&mut cursor)?; - element_type = Some(process_type(&type_attr)?); - data = &data[cursor.position() as usize..]; - } - _ => bail!("Unhandled subscript format type {:?}", format), - } + let mut name = None; + let mut base_type = None; + let mut offset = None; + let mut visibility = None; + let mut virtual_base = false; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => base_type = Some(process_type(attr)?), + (AttributeKind::Location, AttributeValue::Block(block)) => { + offset = Some(process_offset(block)?) } - if let Some(element_type) = element_type { - Ok(UserDefinedType::Array(ArrayType { - element_type: Box::from(element_type), - dimensions, - })) - } else { - Err(anyhow!("Array type without element type")) + (AttributeKind::Private, _) => visibility = Some(Visibility::Private), + (AttributeKind::Protected, _) => visibility = Some(Visibility::Protected), + (AttributeKind::Public, _) => visibility = Some(Visibility::Public), + (AttributeKind::Virtual, _) => virtual_base = true, + _ => { + bail!("Unhandled Inheritance attribute {:?}", attr); } } - TagKind::StructureType => { - let byte_size = tag.data4_attribute(AttributeKind::ByteSize).unwrap_or_default(); - //.ok_or_else(|| { - // anyhow!("StructureType without ByteSize: {:?}", tag) - // })? - let name = tag.string_attribute(AttributeKind::Name).cloned(); - let mut members = Vec::new(); - for child in tag.children(tags) { - ensure!( - child.kind == TagKind::Member, - "Unhandled StructureType child {:?}", - child.kind - ); + } - let member_name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Structure member without name: {:?}", child))?; - let member_type = process_type( - child - .type_attribute() - .ok_or_else(|| anyhow!("Structure member without type: {:?}", child))?, - )?; - if let Some(member_of) = child.reference_attribute(AttributeKind::Member) { - ensure!( - member_of == tag.key, - "Structure member mismatch: {} != {}", - member_of, - tag.key - ); - } + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled Inheritance child {:?}", child.kind); + } - let location = child - .block_attribute(AttributeKind::Location) - .ok_or_else(|| anyhow!("Structure member without location: {:?}", child))?; - let offset = process_offset(location)?; + let name = name.ok_or_else(|| anyhow!("Inheritance without name: {:?}", tag))?; + let base_type = base_type.ok_or_else(|| anyhow!("Inheritance without base type: {:?}", tag))?; + let offset = offset.ok_or_else(|| anyhow!("Inheritance without offset: {:?}", tag))?; + let visibility = + visibility.ok_or_else(|| anyhow!("Inheritance without visibility: {:?}", tag))?; + Ok(StructureBase { name, base_type, offset, visibility, virtual_base }) +} - let bit = match ( - child.data4_attribute(AttributeKind::ByteSize), - child.data4_attribute(AttributeKind::BitSize), - child.data2_attribute(AttributeKind::BitOffset), - ) { - (Some(byte_size), Some(bit_size), Some(bit_offset)) => { - Some(BitData { byte_size, bit_size, bit_offset }) - } - (None, None, None) => None, - _ => bail!("Mismatched bit attributes in structure member: {child:?}"), - }; - members.push(StructureMember { - name: member_name.clone(), - kind: member_type, - offset, - bit, +fn process_structure_member_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::Member, "{:?} is not a Member tag", tag.kind); + + let mut name = None; + let mut member_type = None; + let mut offset = None; + let mut byte_size = None; + let mut bit_size = None; + let mut bit_offset = None; + let mut visibility = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => member_type = Some(process_type(attr)?), + (AttributeKind::Location, AttributeValue::Block(block)) => { + offset = Some(process_offset(block)?) + } + (AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value), + (AttributeKind::BitSize, &AttributeValue::Data4(value)) => bit_size = Some(value), + (AttributeKind::BitOffset, &AttributeValue::Data2(value)) => bit_offset = Some(value), + (AttributeKind::Private, _) => visibility = Some(Visibility::Private), + (AttributeKind::Protected, _) => visibility = Some(Visibility::Protected), + (AttributeKind::Public, _) => visibility = Some(Visibility::Public), + _ => { + bail!("Unhandled Member attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled Member child {:?}", child.kind); + } + + let name = name.ok_or_else(|| anyhow!("Member without name: {:?}", tag))?; + let member_type = member_type.ok_or_else(|| anyhow!("Member without type: {:?}", tag))?; + let offset = offset.ok_or_else(|| anyhow!("Member without offset: {:?}", tag))?; + let bit = match (bit_size, bit_offset) { + (Some(bit_size), Some(bit_offset)) => Some(BitData { bit_size, bit_offset }), + (None, None) => None, + _ => bail!("Mismatched bit attributes in Member: {tag:?}"), + }; + let visibility = visibility.unwrap_or(Visibility::Public); + Ok(StructureMember { + name: name.clone(), + kind: member_type, + offset, + bit, + visibility, + byte_size, + }) +} + +fn process_structure_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!( + matches!(tag.kind, TagKind::StructureType | TagKind::ClassType), + "{:?} is not a Structure type tag", + tag.kind + ); + + let mut name = None; + let mut byte_size = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + (AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value), + _ => { + bail!("Unhandled structure attribute {:?}", attr); + } + } + } + + let mut members = Vec::new(); + let mut bases = Vec::new(); + for child in tag.children(tags) { + match child.kind { + TagKind::Inheritance => bases.push(process_inheritance_tag(tags, child)?), + TagKind::Member => members.push(process_structure_member_tag(tags, child)?), + TagKind::Typedef => { + // TODO? + // info!("Structure {:?} Typedef: {:?}", name, child); + } + kind => bail!("Unhandled StructureType child {:?}", kind), + } + } + + let byte_size = + byte_size.ok_or_else(|| anyhow!("StructureType without ByteSize: {:?}", tag))?; + Ok(StructureType { + kind: if tag.kind == TagKind::ClassType { + StructureKind::Class + } else { + StructureKind::Struct + }, + name, + byte_size, + members, + bases, + }) +} + +fn process_array_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::ArrayType, "{:?} is not an ArrayType tag", tag.kind); + + let mut subscr_data = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::SubscrData, AttributeValue::Block(data)) => { + subscr_data = + Some(process_array_subscript_data(data).with_context(|| { + format!("Failed to process SubscrData for tag: {:?}", tag) + })?) + } + _ => { + bail!("Unhandled ArrayType attribute {:?}", attr) + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled ArrayType child {:?}", child.kind); + } + + let (element_type, dimensions) = + subscr_data.ok_or_else(|| anyhow!("ArrayType without SubscrData: {:?}", tag))?; + Ok(ArrayType { element_type: Box::from(element_type), dimensions }) +} + +fn process_array_subscript_data(data: &[u8]) -> Result<(Type, Vec)> { + let mut element_type = None; + let mut dimensions = Vec::new(); + let mut data = data; + while !data.is_empty() { + let format = SubscriptFormat::try_from( + data.first().cloned().ok_or_else(|| anyhow!("Empty SubscrData"))?, + ) + .context("Unknown array subscript format")?; + data = &data[1..]; + match format { + SubscriptFormat::FundTypeConstConst => { + let index_type = FundType::try_from(u16::from_be_bytes(data[..2].try_into()?)) + .context("Invalid fundamental type ID")?; + let low_bound = u32::from_be_bytes(data[2..6].try_into()?); + ensure!(low_bound == 0, "Invalid array low bound {low_bound}, expected 0"); + let high_bound = u32::from_be_bytes(data[6..10].try_into()?); + data = &data[10..]; + dimensions.push(ArrayDimension { + index_type: Type { kind: TypeKind::Fundamental(index_type), modifiers: vec![] }, + // u32::MAX will wrap to 0, meaning unbounded + size: NonZeroU32::new(high_bound.wrapping_add(1)), }); } - Ok(UserDefinedType::Structure(StructureType { name, byte_size, members })) + SubscriptFormat::ElementType => { + let mut cursor = Cursor::new(data); + let type_attr = read_attribute(&mut cursor)?; + element_type = Some(process_type(&type_attr)?); + data = &data[cursor.position() as usize..]; + } + _ => bail!("Unhandled subscript format type {:?}", format), + } + } + let element_type = element_type.ok_or_else(|| anyhow!("ArrayType without ElementType"))?; + Ok((element_type, dimensions)) +} + +fn process_enumeration_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::EnumerationType, "{:?} is not an EnumerationType tag", tag.kind); + + let mut name = None; + let mut byte_size = None; + let mut members = Vec::new(); + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + (AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value), + (AttributeKind::ElementList, AttributeValue::Block(data)) => { + let mut cursor = Cursor::new(data); + while cursor.position() < data.len() as u64 { + let value = i32::from_reader(&mut cursor, Endian::Big)?; + let name = read_string(&mut cursor)?; + members.push(EnumerationMember { name, value }); + } + } + _ => { + bail!("Unhandled EnumerationType attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled EnumerationType child {:?}", child.kind); + } + + let byte_size = + byte_size.ok_or_else(|| anyhow!("EnumerationType without ByteSize: {:?}", tag))?; + Ok(EnumerationType { name, byte_size, members }) +} + +fn process_union_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::UnionType, "{:?} is not a UnionType tag", tag.kind); + + let mut name = None; + let mut byte_size = None; + let mut members = Vec::new(); + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + (AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value), + _ => { + bail!("Unhandled UnionType attribute {:?}", attr); + } + } + } + + for child in tag.children(tags) { + match child.kind { + TagKind::Member => members.push(process_structure_member_tag(tags, child)?), + kind => bail!("Unhandled UnionType child {:?}", kind), + } + } + + let byte_size = byte_size.ok_or_else(|| anyhow!("UnionType without ByteSize: {:?}", tag))?; + Ok(UnionType { name, byte_size, members }) +} + +fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!( + matches!( + tag.kind, + TagKind::SubroutineType | TagKind::GlobalSubroutine | TagKind::Subroutine + ), + "{:?} is not a Subroutine tag", + tag.kind + ); + + let mut name = None; + let mut mangled_name = None; + let mut return_type = None; + let mut prototyped = false; + let mut parameters = Vec::new(); + let mut var_args = false; + let mut references = Vec::new(); + let mut member_of = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + (AttributeKind::MwMangled, AttributeValue::String(s)) => mangled_name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => return_type = Some(process_type(attr)?), + (AttributeKind::Prototyped, _) => prototyped = true, + (AttributeKind::LowPc, _) | (AttributeKind::HighPc, _) => { + // TODO? + } + (AttributeKind::MwGlobalRef, &AttributeValue::Reference(key)) => { + references.push(key); + } + (AttributeKind::ReturnAddr, AttributeValue::Block(_block)) => { + // let location = process_variable_location(block)?; + // info!("ReturnAddr: {}", location); + } + (AttributeKind::Member, &AttributeValue::Reference(key)) => { + member_of = Some(key); + } + _ => { + bail!("Unhandled SubroutineType attribute {:?}", attr); + } + } + } + + let mut variables = Vec::new(); + for child in tag.children(tags) { + ensure!(!var_args, "{:?} after UnspecifiedParameters", child.kind); + match child.kind { + TagKind::FormalParameter => { + parameters.push(process_subroutine_parameter_tag(tags, child)?) + } + TagKind::UnspecifiedParameters => var_args = true, + TagKind::LocalVariable => variables.push(process_local_variable_tag(tags, child)?), + kind => bail!("Unhandled SubroutineType child {:?}", kind), + } + } + + let return_type = return_type + .unwrap_or_else(|| Type { kind: TypeKind::Fundamental(FundType::Void), modifiers: vec![] }); + Ok(SubroutineType { + name, + mangled_name, + return_type, + parameters, + var_args, + prototyped, + references, + member_of, + variables, + }) +} + +fn process_subroutine_parameter_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::FormalParameter, "{:?} is not a FormalParameter tag", tag.kind); + + let mut name = None; + let mut kind = None; + let mut location = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => kind = Some(process_type(attr)?), + (AttributeKind::Location, AttributeValue::Block(block)) => { + location = Some(process_variable_location(block)?) + } + (AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => { + // TODO? + // info!("MwDwarf2Location: {:?} in {:?}", block, tag); + } + _ => { + bail!("Unhandled SubroutineParameter attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled SubroutineParameter child {:?}", child.kind); + } + + let kind = kind.ok_or_else(|| anyhow!("SubroutineParameter without type: {:?}", tag))?; + Ok(SubroutineParameter { name, kind, location }) +} + +fn process_local_variable_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::LocalVariable, "{:?} is not a LocalVariable tag", tag.kind); + + let mut name = None; + let mut kind = None; + let mut location = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => kind = Some(process_type(attr)?), + (AttributeKind::Location, AttributeValue::Block(block)) => { + location = Some(process_variable_location(block)?) + } + (AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => { + // TODO? + // info!("MwDwarf2Location: {:?} in {:?}", block, tag); + } + _ => { + bail!("Unhandled LocalVariable attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled LocalVariable child {:?}", child.kind); + } + + let kind = kind.ok_or_else(|| anyhow!("LocalVariable without type: {:?}", tag))?; + Ok(SubroutineVariable { name, kind, location }) +} + +fn process_ptr_to_member_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::PtrToMemberType, "{:?} is not a PtrToMemberType tag", tag.kind); + + let mut kind = None; + let mut containing_type = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => kind = Some(process_type(attr)?), + (AttributeKind::ContainingType, &AttributeValue::Reference(key)) => { + containing_type = Some(key) + } + _ => { + bail!("Unhandled PtrToMemberType attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled PtrToMemberType child {:?}", child.kind); + } + + let kind = kind.ok_or_else(|| anyhow!("PtrToMemberType without type: {:?}", tag))?; + let containing_type = containing_type + .ok_or_else(|| anyhow!("PtrToMemberType without containing type: {:?}", tag))?; + Ok(PtrToMemberType { kind, containing_type }) +} + +pub fn ud_type(tags: &TagMap, tag: &Tag) -> Result { + match tag.kind { + TagKind::ArrayType => Ok(UserDefinedType::Array(process_array_tag(tags, tag)?)), + TagKind::StructureType | TagKind::ClassType => { + Ok(UserDefinedType::Structure(process_structure_tag(tags, tag)?)) } TagKind::EnumerationType => { - let byte_size = tag - .data4_attribute(AttributeKind::ByteSize) - .ok_or_else(|| anyhow!("EnumerationType without ByteSize"))?; - let data = tag - .block_attribute(AttributeKind::ElementList) - .ok_or_else(|| anyhow!("EnumerationType without ElementList"))?; - let name = tag.string_attribute(AttributeKind::Name).cloned(); - let mut members = Vec::new(); - let mut cursor = Cursor::new(data); - while cursor.position() < data.len() as u64 { - let value = u32::from_reader(&mut cursor, Endian::Big)?; - let name = read_string(&mut cursor)?; - members.push(EnumerationMember { name, value }); - } - Ok(UserDefinedType::Enumeration(EnumerationType { name, byte_size, members })) - } - TagKind::UnionType => { - let byte_size = tag - .data4_attribute(AttributeKind::ByteSize) - .ok_or_else(|| anyhow!("UnionType without ByteSize"))?; - let name = tag.string_attribute(AttributeKind::Name).cloned(); - let mut members = Vec::new(); - for child in tag.children(tags) { - ensure!( - child.kind == TagKind::Member, - "Unhandled UnionType child {:?}", - child.kind - ); - - let member_name = child - .string_attribute(AttributeKind::Name) - .ok_or_else(|| anyhow!("Structure member without name: {:?}", child))?; - let member_type = process_type( - child - .type_attribute() - .ok_or_else(|| anyhow!("Structure member without type: {:?}", child))?, - )?; - if let Some(member_of) = child.reference_attribute(AttributeKind::Member) { - ensure!( - member_of == tag.key, - "Structure member mismatch: {member_of} != {}", - tag.key - ); - } - - let location = child - .block_attribute(AttributeKind::Location) - .ok_or_else(|| anyhow!("Structure member without location: {:?}", child))?; - ensure!(process_offset(location)? == 0, "Union member at non-zero offset"); - - members.push(UnionMember { name: member_name.clone(), kind: member_type }); - } - Ok(UserDefinedType::Union(UnionType { name, byte_size, members })) + Ok(UserDefinedType::Enumeration(process_enumeration_tag(tags, tag)?)) } + TagKind::UnionType => Ok(UserDefinedType::Union(process_union_tag(tags, tag)?)), TagKind::SubroutineType | TagKind::GlobalSubroutine | TagKind::Subroutine => { - let return_type = match tag.type_attribute() { - Some(attr) => process_type(attr)?, - None => Type { kind: TypeKind::Fundamental(FundType::Void), modifiers: vec![] }, - }; - let prototyped = tag.string_attribute(AttributeKind::Prototyped).is_some(); - let mut parameters = Vec::new(); - let mut var_args = false; - for child in tag.children(tags) { - if tag.kind != TagKind::SubroutineType - && child.kind != TagKind::FormalParameter - && child.kind != TagKind::UnspecifiedParameters - { - break; - } - ensure!(!var_args, "{:?} after UnspecifiedParameters", child.kind); - match child.kind { - TagKind::FormalParameter => { - let parameter_name = child.string_attribute(AttributeKind::Name).cloned(); - let parameter_type = - process_type(child.type_attribute().ok_or_else(|| { - anyhow!("FormalParameter without type: {:?}", child) - })?)?; - parameters.push(SubroutineParameter { - name: parameter_name, - kind: parameter_type, - }); - } - TagKind::UnspecifiedParameters => { - var_args = true; - } - _ => bail!("Unhandled SubroutineType child {:?}", child.kind), - } - } - Ok(UserDefinedType::Subroutine(SubroutineType { - return_type, - parameters, - var_args, - prototyped, - })) + Ok(UserDefinedType::Subroutine(process_subroutine_tag(tags, tag)?)) + } + TagKind::PtrToMemberType => { + Ok(UserDefinedType::PtrToMember(process_ptr_to_member_tag(tags, tag)?)) } kind => Err(anyhow!("Unhandled user defined type {kind:?}")), } @@ -1139,43 +1767,172 @@ pub fn process_modifiers(block: &[u8]) -> Result> { } pub fn process_type(attr: &Attribute) -> Result { - match attr.kind { - AttributeKind::FundType => { - if let AttributeValue::Data2(type_id) = attr.value { - let fund_type = FundType::try_from(type_id) - .with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?; - Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers: vec![] }) - } else { - Err(anyhow!("Invalid value type for FundType")) - } + match (attr.kind, &attr.value) { + (AttributeKind::FundType, &AttributeValue::Data2(type_id)) => { + let fund_type = FundType::try_from(type_id) + .with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?; + Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers: vec![] }) } - AttributeKind::ModFundType => { - if let AttributeValue::Block(ops) = &attr.value { - let type_id = u16::from_be_bytes(ops[ops.len() - 2..].try_into()?); - let fund_type = FundType::try_from(type_id) - .with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?; - let modifiers = process_modifiers(&ops[..ops.len() - 2])?; - Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers }) - } else { - Err(anyhow!("Invalid value type for ModFundType")) - } + (AttributeKind::ModFundType, AttributeValue::Block(ops)) => { + let type_id = u16::from_be_bytes(ops[ops.len() - 2..].try_into()?); + let fund_type = FundType::try_from(type_id) + .with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?; + let modifiers = process_modifiers(&ops[..ops.len() - 2])?; + Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers }) } - AttributeKind::UserDefType => { - if let AttributeValue::Reference(key) = attr.value { - Ok(Type { kind: TypeKind::UserDefined(key), modifiers: vec![] }) - } else { - Err(anyhow!("Invalid value type for UserDefType")) - } + (AttributeKind::UserDefType, &AttributeValue::Reference(key)) => { + Ok(Type { kind: TypeKind::UserDefined(key), modifiers: vec![] }) } - AttributeKind::ModUDType => { - if let AttributeValue::Block(ops) = &attr.value { - let ud_ref = u32::from_be_bytes(ops[ops.len() - 4..].try_into()?); - let modifiers = process_modifiers(&ops[..ops.len() - 4])?; - Ok(Type { kind: TypeKind::UserDefined(ud_ref), modifiers }) - } else { - Err(anyhow!("Invalid value type for ModUDType")) - } + (AttributeKind::ModUDType, AttributeValue::Block(ops)) => { + let ud_ref = u32::from_be_bytes(ops[ops.len() - 4..].try_into()?); + let modifiers = process_modifiers(&ops[..ops.len() - 4])?; + Ok(Type { kind: TypeKind::UserDefined(ud_ref), modifiers }) } - _ => Err(anyhow!("Invalid type attribute kind {:?}", attr.kind)), + _ => Err(anyhow!("Invalid type attribute {:?}", attr)), } } + +pub fn process_root_tag(tags: &TagMap, tag: &Tag) -> Result { + match tag.kind { + TagKind::Typedef => Ok(TagType::Typedef(process_typedef_tag(tags, tag)?)), + TagKind::GlobalVariable | TagKind::LocalVariable => { + Ok(TagType::Variable(process_variable_tag(tags, tag)?)) + } + TagKind::StructureType + | TagKind::ArrayType + | TagKind::EnumerationType + | TagKind::UnionType + | TagKind::ClassType + | TagKind::SubroutineType + | TagKind::GlobalSubroutine + | TagKind::Subroutine + | TagKind::PtrToMemberType => Ok(TagType::UserDefined(ud_type(tags, tag)?)), + kind => Err(anyhow!("Unhandled root tag type {:?}", kind)), + } +} + +/// Logic to skip uninteresting tags +pub fn should_skip_tag(tag_type: &TagType) -> bool { + match tag_type { + TagType::Variable(_) => false, + TagType::Typedef(_) => false, + TagType::UserDefined(t) => !t.is_definition(), + } +} + +pub fn tag_type_string(tags: &TagMap, typedefs: &TypedefMap, tag_type: &TagType) -> Result { + match tag_type { + TagType::Typedef(t) => typedef_string(tags, typedefs, t), + TagType::Variable(v) => variable_string(tags, typedefs, v, true), + TagType::UserDefined(ud) => { + let ud_str = ud_type_def(tags, typedefs, ud)?; + match ud { + UserDefinedType::Structure(_) + | UserDefinedType::Enumeration(_) + | UserDefinedType::Union(_) => Ok(format!("{};", ud_str)), + _ => Ok(ud_str), + } + } + } +} + +fn typedef_string(tags: &TagMap, typedefs: &TypedefMap, typedef: &TypedefTag) -> Result { + let ts = type_string(tags, typedefs, &typedef.kind, true)?; + Ok(format!("typedef {} {}{};", ts.prefix, typedef.name, ts.suffix)) +} + +fn variable_string( + tags: &TagMap, + typedefs: &TypedefMap, + variable: &VariableTag, + include_extra: bool, +) -> Result { + let ts = type_string(tags, typedefs, &variable.kind, include_extra)?; + let mut out = if variable.local { "static ".to_string() } else { String::new() }; + out.push_str(&ts.prefix); + out.push(' '); + out.push_str(variable.name.as_deref().unwrap_or("[unknown]")); + out.push_str(&ts.suffix); + match &variable.address { + Some(addr) => out.push_str(&format!(" : {:#010X}", addr)), + None => {} + } + out.push(';'); + if include_extra { + let size = variable.kind.size(tags)?; + out.push_str(&format!(" // size: {:#X}", size)); + } + Ok(out) +} + +fn process_typedef_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!(tag.kind == TagKind::Typedef, "{:?} is not a typedef tag", tag.kind); + + let mut name = None; + let mut kind = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => kind = Some(process_type(attr)?), + _ => { + bail!("Unhandled Typedef attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled Typedef child {:?}", child.kind); + } + + let name = name.ok_or_else(|| anyhow!("Typedef without Name: {:?}", tag))?; + let kind = kind.ok_or_else(|| anyhow!("Typedef without Type: {:?}", tag))?; + Ok(TypedefTag { name, kind }) +} + +fn process_variable_tag(tags: &TagMap, tag: &Tag) -> Result { + ensure!( + matches!(tag.kind, TagKind::GlobalVariable | TagKind::LocalVariable), + "{:?} is not a variable tag", + tag.kind + ); + + let mut name = None; + let mut mangled_name = None; + let mut kind = None; + let mut address = None; + for attr in &tag.attributes { + match (attr.kind, &attr.value) { + (AttributeKind::Sibling, _) => {} + (AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()), + (AttributeKind::MwMangled, AttributeValue::String(s)) => mangled_name = Some(s.clone()), + ( + AttributeKind::FundType + | AttributeKind::ModFundType + | AttributeKind::UserDefType + | AttributeKind::ModUDType, + _, + ) => kind = Some(process_type(attr)?), + (AttributeKind::Location, AttributeValue::Block(block)) => { + address = Some(process_address(block)?) + } + _ => { + bail!("Unhandled Variable attribute {:?}", attr); + } + } + } + + if let Some(child) = tag.children(tags).first() { + bail!("Unhandled Variable child {:?}", child.kind); + } + + let kind = kind.ok_or_else(|| anyhow!("Variable without Type: {:?}", tag))?; + let local = tag.kind == TagKind::LocalVariable; + Ok(VariableTag { name, mangled_name, kind, address, local }) +}