sigmd 0.1.0

Windows API signature metadata
Documentation
//! Single-TU parsing via the `clang` wrapper.

mod clang;
mod error;
mod function;
mod interface;
mod parameter;
mod sal;
mod ty;
mod walk;

use std::{collections::HashMap, path::PathBuf};

use sigmd::Architecture;

pub use self::{error::Error, function::ScoredFunction, interface::ScoredInterface};

/// Hook from a type name to a custom kind tag.
pub type CustomKindFn = Box<dyn Fn(&str) -> Option<u8> + Send + Sync + 'static>;

/// Per-TU build state shared by the parsing helpers.
#[derive(Default)]
pub struct BuildContext {
    /// Byte size of each named type declaration, gathered via
    /// `clang_Type_getSizeOf` before the main walk.
    pub sizeofs: HashMap<String, u64>,

    /// User hook that maps a type name to a custom kind tag, surfaced
    /// as `TypeKind::Custom` in the output model.
    pub custom_type_fn: Option<CustomKindFn>,
}

impl BuildContext {
    /// Returns the custom kind tag for `name`, if any.
    pub fn parse_custom_type(&self, name: impl AsRef<str>) -> Option<u8> {
        match &self.custom_type_fn {
            Some(custom_type_fn) => custom_type_fn(name.as_ref()),
            None => None,
        }
    }
}

/// Output of parsing one translation unit.
#[derive(Debug)]
pub struct ScoredMetadata {
    /// Scored functions extracted from the TU.
    pub functions: Vec<ScoredFunction>,

    /// Scored interfaces extracted from the TU.
    pub interfaces: Vec<ScoredInterface>,
}

/// Dedup arbitration weight.
///
/// Higher wins on conflict. Ties go to first-seen.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Score(pub u32);

/// Per-TU parser.
///
/// One per worker thread to satisfy libclang's thread-affinity.
pub struct Compiler {
    index: clang::Index,
}

#[bon::bon]
impl Compiler {
    /// Creates a new `Compiler` backed by a fresh libclang index.
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            index: clang::Index::new()?,
        })
    }

    /// Parses a single translation unit and returns its `ScoredMetadata`.
    #[builder(finish_fn = compile)]
    pub fn translation_unit(
        &self,
        path: impl Into<PathBuf>,
        architecture: Architecture,
        include: impl IntoIterator<Item = impl Into<PathBuf>>,
        inject: impl IntoIterator<Item = impl Into<PathBuf>>,
        #[builder(with = |f: impl Fn(&str) -> Option<u8> + Send + Sync + 'static| Box::new(f) as _)]
        custom_type_fn: Option<CustomKindFn>,
    ) -> Result<ScoredMetadata, Error> {
        let target = match architecture {
            Architecture::X86 => "--target=i686-pc-windows-msvc",
            Architecture::X64 => "--target=x86_64-pc-windows-msvc",
        };

        let mut parser = self.index.parser(path);

        #[rustfmt::skip]
        parser.args([
            target,
            "-x", "c++",
            "-fms-extensions",
            "-fms-compatibility",
            "-nostdinc++",
            "-nostdsysteminc",
            "-nobuiltininc",
        ]);

        for item in include {
            let item = item.into();
            let include_path = match item.to_str() {
                Some(include_path) => include_path,
                None => {
                    tracing::warn!(
                        path = %item.display(),
                        "path is not valid UTF-8; skipping"
                    );

                    continue;
                }
            };

            parser.args(["-isystem", include_path]);
        }

        for item in inject {
            let item = item.into();
            let include_path = match item.to_str() {
                Some(include_path) => include_path,
                None => {
                    tracing::warn!(
                        path = %item.display(),
                        "path is not valid UTF-8; skipping"
                    );

                    continue;
                }
            };

            parser.args(["-include", include_path]);
        }

        let tu = parser.parse()?;

        let mut ctx = BuildContext {
            sizeofs: HashMap::new(),
            custom_type_fn,
        };
        walk::collect_sizeofs(tu.get_entity(), &mut ctx);

        let mut scored = ScoredMetadata {
            functions: Vec::new(),
            interfaces: Vec::new(),
        };
        walk::visit(tu.get_entity(), &ctx, &mut scored);

        Ok(scored)
    }
}