xbp-analysis 10.57.0

Language-agnostic static analysis (error-handling / Aspirator-style) for XBP
Documentation
//! Abstract error-handling concepts (not Rust-specific).
//!
//! Language adapters map concrete constructs into these concepts.
//! Examples: Rust `Result`/`?`/`unwrap` vs Java exceptions vs Go multi-value errors.

use serde::{Deserialize, Serialize};

use super::types::SourceLocation;

/// Kind of abstract operation / signal in the error model.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConceptKind {
    FallibleOperation,
    ErrorSignal,
    ErrorHandler,
    Propagation,
    Recovery,
    Retry,
    Termination,
    ResourceCleanup,
    StateMutation,
    TransactionBoundary,
    LoggingOnly,
    Placeholder,
    UnobservedTask,
    PartialCommit,
    ContextLoss,
    EmptyHandler,
    IgnoredFailure,
}

/// A single abstract concept instance extracted from source.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConceptInstance {
    pub kind: ConceptKind,
    pub location: SourceLocation,
    /// Free-form classifier from the adapter (e.g. `unwrap`, `panic_macro`, `tokio_spawn`).
    pub tag: String,
    /// Optional name of the symbol/operation.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub symbol: Option<String>,
    /// Whether the adapter believes the operation is idempotent (retries).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub idempotent: Option<bool>,
    /// Whether a retry has an explicit bound / backoff.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bounded: Option<bool>,
    /// Adjacent logging without propagation.
    #[serde(default)]
    pub logs_only: bool,
    /// Original error dropped / mapped to unit success.
    #[serde(default)]
    pub drops_context: bool,
    /// Critical path marker (e.g. error handler with TODO).
    #[serde(default)]
    pub critical_path: bool,
    /// Human-readable surface form for evidence.
    pub surface: String,
}

impl ConceptInstance {
    pub fn new(
        kind: ConceptKind,
        location: SourceLocation,
        tag: impl Into<String>,
        surface: impl Into<String>,
    ) -> Self {
        Self {
            kind,
            location,
            tag: tag.into(),
            symbol: None,
            idempotent: None,
            bounded: None,
            logs_only: false,
            drops_context: false,
            critical_path: false,
            surface: surface.into(),
        }
    }

    pub fn with_symbol(mut self, s: impl Into<String>) -> Self {
        self.symbol = Some(s.into());
        self
    }

    pub fn with_idempotent(mut self, v: bool) -> Self {
        self.idempotent = Some(v);
        self
    }

    pub fn with_bounded(mut self, v: bool) -> Self {
        self.bounded = Some(v);
        self
    }

    pub fn with_logs_only(mut self, v: bool) -> Self {
        self.logs_only = v;
        self
    }

    pub fn with_drops_context(mut self, v: bool) -> Self {
        self.drops_context = v;
        self
    }

    pub fn with_critical_path(mut self, v: bool) -> Self {
        self.critical_path = v;
        self
    }
}