sigmd 0.1.0

Windows API signature metadata
Documentation
//! Function building.

use sigmd::model::{Function, Parameter, ParameterFlags, Type};

use super::{BuildContext, Error, Score, clang, parameter::build_parameter, sal, ty::build_type};

/// Per-TU function ready for cross-TU dedup arbitration.
#[derive(Debug)]
pub struct ScoredFunction {
    /// Parsed function.
    pub function: Function,

    /// Arbitration weight for dedup.
    pub score: Score,
}

/// Builds a [`ScoredFunction`] from a `FunctionDecl` or `CXXMethod` cursor.
pub fn build_function(
    cursor: clang::Entity<'_>,
    ctx: &BuildContext,
) -> Result<ScoredFunction, Error> {
    let name = match cursor.get_name() {
        Some(name) => name,
        None => {
            return Err(Error::UndefinedProperty {
                property: "name",
                entity: format!("{:?}", cursor.get_kind()),
            });
        }
    };

    let return_ty = match cursor.get_result_type() {
        Some(return_ty) => build_type(return_ty, ctx),
        None => {
            return Err(Error::UndefinedProperty {
                property: "return type",
                entity: format!("{:?}", cursor.get_kind()),
            });
        }
    };

    // Synthetic `This` for methods.
    let mut parameters = Vec::new();
    let mut parameter_invalid = Vec::new();
    let mut sal_per_parameter = Vec::new();

    if cursor.get_kind() == clang::EntityKind::Method {
        parameters.push(
            Parameter::builder()
                .name("This")
                .ty(Type::void_pointer())
                .build(),
        );
        parameter_invalid.push(false);
        sal_per_parameter.push(Vec::new());
    }

    let mut function_annotations = Vec::new();

    for child in cursor.get_children() {
        match child.get_kind() {
            clang::EntityKind::ParmDecl => {
                let bp = build_parameter(child, ctx)?;
                sal_per_parameter.push(bp.annotations);
                parameter_invalid.push(bp.is_invalid);
                parameters.push(bp.parameter);
            }
            clang::EntityKind::AnnotateAttr => {
                if let Some(name) = child.get_name() {
                    function_annotations.push(name);
                }
            }
            _ => {}
        }
    }

    let annotations_per_parameter = sal_per_parameter
        .iter()
        .map(|raw| sal::decode(raw))
        .collect::<Vec<_>>();
    let buffers = sal::analyze(&parameters, &annotations_per_parameter, ctx);

    let has_override = function_annotations
        .iter()
        .any(|annotation| annotation.starts_with("__OVERRIDE"));
    let is_invalid = cursor.is_invalid_declaration();

    let function = Function::builder()
        .name(name)
        .parameters(parameters)
        .buffers(buffers)
        .return_ty(return_ty)
        .build();

    let score = function_score(&function, &parameter_invalid, is_invalid, has_override);

    Ok(ScoredFunction { function, score })
}

/// Computes the function score.
///
/// score = sum over parameters of:
///     1
///     + 10  if HAS_IN_ATTRIBUTE
///     + 10  if HAS_OUT_ATTRIBUTE
///     + 100 if !is_invalid (per-parameter)
/// + 100_000   if !is_invalid (function-level)
/// + 1_000_000 if has_override
fn function_score(
    function: &Function,
    parameter_invalid: &[bool],
    function_invalid: bool,
    has_override: bool,
) -> Score {
    let mut score = 0;
    for (i, param) in function.parameters.iter().enumerate() {
        score += 1;

        if param.flags.contains(ParameterFlags::HAS_IN_ATTRIBUTE) {
            score += 10;
        }

        if param.flags.contains(ParameterFlags::HAS_OUT_ATTRIBUTE) {
            score += 10;
        }

        let invalid = parameter_invalid.get(i).copied().unwrap_or(false);
        if !invalid {
            score += 100;
        }
    }

    if !function_invalid {
        score += 100_000;
    }

    if has_override {
        score += 1_000_000;
    }

    Score(score)
}

#[cfg(test)]
mod tests {
    use sigmd::model::{Function, Parameter, ParameterFlags, Type, TypeKind};

    use super::*;

    #[test]
    fn function_score_weights_match_spec() {
        let func = Function::builder()
            .name("X")
            .parameters(vec![
                Parameter::builder()
                    .name("p1")
                    .flags(ParameterFlags::HAS_IN_ATTRIBUTE)
                    .ty(Type::builder().name("int").kind(TypeKind::I32).build())
                    .build(),
                Parameter::builder()
                    .name("p2")
                    .flags(ParameterFlags::HAS_OUT_ATTRIBUTE)
                    .ty(Type::builder()
                        .indirections(1)
                        .name("int")
                        .kind(TypeKind::I32)
                        .build())
                    .build(),
            ])
            .return_ty(Type::builder().name("void").kind(TypeKind::Void).build())
            .build();

        // 2 params, both valid: (1 + 10 + 100) + (1 + 10 + 100) = 222
        // function valid: + 100_000
        // not override: + 0
        // total: 100_222
        let s = function_score(&func, &[false, false], false, false);
        assert_eq!(s.0, 100_222);

        // Same with override: + 1_000_000 => 1_100_222
        let s = function_score(&func, &[false, false], false, true);
        assert_eq!(s.0, 1_100_222);

        // Same with function-level invalid: drop 100_000 => 222
        let s = function_score(&func, &[false, false], true, false);
        assert_eq!(s.0, 222);
    }
}