dotscope 0.6.0

A high-performance, cross-platform framework for analyzing and reverse engineering .NET PE executables
Documentation
//! Raw `StateMachineMethod` table representation for Portable PDB format
//!
//! This module provides the [`StateMachineMethodRaw`] struct that represents
//! the binary format of `StateMachineMethod` table entries as they appear in
//! the metadata tables stream. This is the low-level representation used during
//! the initial parsing phase, containing unresolved method indices.

use crate::{
    metadata::{
        method::MethodMap,
        tables::{StateMachineMethod, StateMachineMethodRc, TableId, TableInfoRef, TableRow},
        token::Token,
    },
    Error::TypeNotFound,
    Result,
};
use std::sync::Arc;

/// Raw binary representation of a `StateMachineMethod` table entry
///
/// This structure matches the exact binary layout of `StateMachineMethod` table
/// entries in the metadata tables stream. Both fields contain unresolved indices
/// into the `MethodDef` table that must be resolved during conversion to the
/// owned [`StateMachineMethod`] variant.
///
/// # Binary Format
///
/// Each `StateMachineMethod` table entry consists of:
/// - **`MoveNextMethod`** (4 bytes): `MethodDef` table index of the compiler-generated `MoveNext` method
/// - **`KickoffMethod`** (4 bytes): `MethodDef` table index of the original user method
///
/// # State Machine Context
///
/// When compilers generate state machines for async/await or yield return patterns:
/// 1. The original method becomes the "kickoff" method that initializes the state machine
/// 2. A new `MoveNext` method contains the actual implementation logic
/// 3. This table provides the bidirectional mapping between these methods
///
/// # Constraints
///
/// - Table must be sorted by `MoveNextMethod` column
/// - No duplicate `MoveNextMethod` values allowed
/// - No duplicate `KickoffMethod` values allowed
/// - Both indices must reference valid `MethodDef` entries
///
/// # References
///
/// - [Portable PDB Format - StateMachineMethod Table](https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#statemachinemethod-table-0x36)
#[derive(Debug, Clone)]
pub struct StateMachineMethodRaw {
    /// Row identifier (1-based index in the table)
    pub rid: u32,

    /// Metadata token for this `StateMachineMethod` entry
    pub token: Token,

    /// Byte offset of this row in the original metadata stream
    pub offset: usize,

    /// Index into `MethodDef` table for the compiler-generated `MoveNext` method
    ///
    /// References the state machine's `MoveNext` method that contains the actual
    /// implementation logic. This method is generated by the compiler and contains
    /// the state machine's execution logic.
    pub move_next_method: u32,

    /// Index into `MethodDef` table for the original user-written method
    ///
    /// References the kickoff method that was originally written by the developer.
    /// This method initializes and starts the state machine when called.
    pub kickoff_method: u32,
}

impl StateMachineMethodRaw {
    /// Converts this raw `StateMachineMethod` entry to an owned [`StateMachineMethod`] instance
    ///
    /// This method resolves the raw `StateMachineMethod` entry to create a complete `StateMachineMethod`
    /// object by resolving the `MethodDef` table indices to actual method references from the method map.
    /// Both method references are resolved using the provided method map.
    ///
    /// # Parameters
    /// - `method_map`: Reference to the method map containing resolved method references
    ///
    /// # Returns
    /// Returns `Ok(StateMachineMethodRc)` with the resolved state machine method mapping data,
    /// or an error if either method reference cannot be resolved.
    ///
    /// # Errors
    /// Returns [`TypeNotFound`] if either the `move_next_method` or `kickoff_method` cannot be resolved from the map.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// # use dotscope::metadata::tables::statemachinemethod::StateMachineMethodRaw;
    /// # use dotscope::metadata::token::Token;
    /// # fn example(method_map: &MethodMap) -> dotscope::Result<()> {
    /// let mapping_raw = StateMachineMethodRaw {
    ///     rid: 1,
    ///     token: Token::new(0x36000001),
    ///     offset: 0,
    ///     move_next_method: 123,  // MethodDef table index
    ///     kickoff_method: 45,     // MethodDef table index
    /// };
    ///
    /// let mapping = mapping_raw.to_owned(method_map)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn to_owned(&self, method_map: &MethodMap) -> Result<StateMachineMethodRc> {
        let move_next_token = Token::new(0x0600_0000 | self.move_next_method);
        let kickoff_token = Token::new(0x0600_0000 | self.kickoff_method);

        let move_next_method = method_map
            .get(&move_next_token)
            .ok_or(TypeNotFound(move_next_token))?
            .value()
            .clone();

        let kickoff_method = method_map
            .get(&kickoff_token)
            .ok_or(TypeNotFound(kickoff_token))?
            .value()
            .clone();

        Ok(Arc::new(StateMachineMethod {
            rid: self.rid,
            token: self.token,
            offset: self.offset,
            move_next_method,
            kickoff_method,
        }))
    }
}

impl TableRow for StateMachineMethodRaw {
    /// Calculate the row size for `StateMachineMethod` table entries
    ///
    /// Returns the total byte size of a single `StateMachineMethod` table row based on the
    /// table configuration. The size varies depending on the size of table indexes in the metadata.
    ///
    /// # Size Breakdown
    /// - `move_next_method`: 2 or 4 bytes (table index into `MethodDef` table)
    /// - `kickoff_method`: 2 or 4 bytes (table index into `MethodDef` table)
    ///
    /// Total: 4-8 bytes depending on table index size configuration
    #[rustfmt::skip]
    fn row_size(sizes: &TableInfoRef) -> u32 {
        u32::from(
            sizes.table_index_bytes(TableId::MethodDef) +   // move_next_method (MethodDef table index)
            sizes.table_index_bytes(TableId::MethodDef)     // kickoff_method (MethodDef table index)
        )
    }
}