dotscope 0.8.0

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

use crate::{
    metadata::{
        method::MethodMap,
        tables::{
            ImportScopeMap, LocalConstantMap, LocalScope, LocalScopeRc, LocalVariableMap,
            MetadataTable, TableId, TableInfoRef, TableRow,
        },
        token::Token,
    },
    Result,
};
use std::sync::Arc;

/// Raw binary representation of a `LocalScope` table entry
///
/// This structure matches the exact binary layout of `LocalScope` table
/// entries in the metadata tables stream. All table references remain as unresolved
/// indices that must be resolved through the appropriate tables during the conversion
/// to the owned [`LocalScope`] variant.
///
/// # Binary Format
///
/// Each `LocalScope` table entry consists of:
/// - Method: Simple index into `MethodDef` table
/// - `ImportScope`: Simple index into `ImportScope` table
/// - `VariableList`: Simple index into `LocalVariable` table
/// - `ConstantList`: Simple index into `LocalConstant` table
/// - `StartOffset`: 4-byte unsigned integer (IL offset)
/// - `Length`: 4-byte unsigned integer (scope length in bytes)
#[derive(Debug, Clone)]
pub struct LocalScopeRaw {
    /// Row identifier (1-based index in the table)
    pub rid: u32,

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

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

    /// Simple index into `MethodDef` table
    ///
    /// Identifies the method that contains this local scope. This is always
    /// a valid method reference as local scopes must belong to a method.
    pub method: u32,

    /// Simple index into `ImportScope` table
    ///
    /// References the import scope that provides the namespace context for
    /// this local scope. May be 0 if no specific import context is required.
    pub import_scope: u32,

    /// Simple index into `LocalVariable` table
    ///
    /// Points to the first local variable that belongs to this scope.
    /// Variables are stored consecutively, so this serves as a range start.
    /// May be 0 if this scope contains no variables.
    pub variable_list: u32,

    /// Simple index into `LocalConstant` table
    ///
    /// Points to the first local constant that belongs to this scope.
    /// Constants are stored consecutively, so this serves as a range start.
    /// May be 0 if this scope contains no constants.
    pub constant_list: u32,

    /// IL instruction offset where this scope begins
    ///
    /// Specifies the byte offset within the method's IL code where
    /// the variables and constants in this scope become active.
    pub start_offset: u32,

    /// Length of this scope in IL instruction bytes
    ///
    /// Specifies how many bytes of IL code this scope covers.
    /// The scope extends from `start_offset` to (`start_offset` + `length`).
    pub length: u32,
}

impl LocalScopeRaw {
    /// Converts this raw `LocalScope` entry to an owned [`LocalScope`] instance
    ///
    /// This method resolves the raw `LocalScope` entry to create a complete `LocalScope`
    /// object by resolving all table references and building the variable and constant lists
    /// using range determination based on the next scope's starting indices.
    ///
    /// # Parameters
    /// - `methods`: Map of resolved methods for method reference resolution
    /// - `import_scopes`: Map of resolved import scopes for import scope resolution
    /// - `variables`: Map of resolved local variables for building variable lists
    /// - `constants`: Map of resolved local constants for building constant lists
    /// - `scope_table`: The raw `LocalScope` table for looking up next scope indices
    ///
    /// # Returns
    /// Returns `Ok(LocalScopeRc)` with the resolved scope data, or an error if
    /// any references are invalid or point to malformed data.
    ///
    /// # Errors
    /// Returns an error if any references are invalid or point to malformed data.
    pub fn to_owned(
        &self,
        methods: &MethodMap,
        import_scopes: &ImportScopeMap,
        variables: &LocalVariableMap,
        constants: &LocalConstantMap,
        scope_table: &MetadataTable<LocalScopeRaw>,
    ) -> Result<LocalScopeRc> {
        let method_token = Token::new(
            0x0600_0000_u32
                .checked_add(self.method)
                .ok_or_else(|| malformed_error!("Method token overflow: {}", self.method))?,
        );
        let method = methods
            .get(&method_token)
            .ok_or_else(|| malformed_error!("Invalid method index {} in LocalScope", self.method))?
            .value()
            .clone();

        let import_scope =
            if self.import_scope == 0 {
                None
            } else {
                let import_token =
                    Token::new(0x3500_0000_u32.checked_add(self.import_scope).ok_or_else(
                        || malformed_error!("ImportScope token overflow: {}", self.import_scope),
                    )?);
                Some(
                    import_scopes
                        .get(&import_token)
                        .ok_or_else(|| {
                            malformed_error!(
                                "Invalid import scope index {} in LocalScope",
                                self.import_scope
                            )
                        })?
                        .value()
                        .clone(),
                )
            };

        let next_rid = self.rid.checked_add(1).ok_or_else(|| {
            malformed_error!(
                "LocalScope rid overflow when computing next rid: {}",
                self.rid
            )
        })?;

        let variables = if self.variable_list == 0 {
            Arc::new(boxcar::Vec::new())
        } else {
            let start = self.variable_list;

            let end = if let Some(next_scope) = scope_table.get(next_rid) {
                if next_scope.variable_list != 0 {
                    next_scope.variable_list
                } else {
                    let len = u32::try_from(variables.len()).map_err(|_| {
                        malformed_error!("LocalVariable count exceeds u32: {}", variables.len())
                    })?;
                    len.checked_add(1).ok_or_else(|| {
                        malformed_error!("LocalVariable end index overflow: {}", len)
                    })?
                }
            } else {
                let len = u32::try_from(variables.len()).map_err(|_| {
                    malformed_error!("LocalVariable count exceeds u32: {}", variables.len())
                })?;
                len.checked_add(1)
                    .ok_or_else(|| malformed_error!("LocalVariable end index overflow: {}", len))?
            };

            let list = Arc::new(boxcar::Vec::new());
            for i in start..end {
                let var_token_value = 0x3300_0000_u32
                    .checked_add(i)
                    .ok_or_else(|| malformed_error!("LocalVariable token overflow: {}", i))?;
                let var_token = Token::new(var_token_value);
                if let Some(var_entry) = variables.get(&var_token) {
                    list.push(var_entry.value().clone());
                }
            }
            list
        };

        let constants = if self.constant_list == 0 {
            Arc::new(boxcar::Vec::new())
        } else {
            let start = self.constant_list;

            let end = if let Some(next_scope) = scope_table.get(next_rid) {
                if next_scope.constant_list != 0 {
                    next_scope.constant_list
                } else {
                    let len = u32::try_from(constants.len()).map_err(|_| {
                        malformed_error!("LocalConstant count exceeds u32: {}", constants.len())
                    })?;
                    len.checked_add(1).ok_or_else(|| {
                        malformed_error!("LocalConstant end index overflow: {}", len)
                    })?
                }
            } else {
                let len = u32::try_from(constants.len()).map_err(|_| {
                    malformed_error!("LocalConstant count exceeds u32: {}", constants.len())
                })?;
                len.checked_add(1)
                    .ok_or_else(|| malformed_error!("LocalConstant end index overflow: {}", len))?
            };

            let list = Arc::new(boxcar::Vec::new());
            for i in start..end {
                let const_token_value = 0x3400_0000_u32
                    .checked_add(i)
                    .ok_or_else(|| malformed_error!("LocalConstant token overflow: {}", i))?;
                let const_token = Token::new(const_token_value);
                if let Some(const_entry) = constants.get(&const_token) {
                    list.push(const_entry.value().clone());
                }
            }
            list
        };

        let local_scope = LocalScope {
            rid: self.rid,
            token: self.token,
            offset: self.offset,
            method,
            import_scope,
            variables,
            constants,
            start_offset: self.start_offset,
            length: self.length,
        };

        Ok(Arc::new(local_scope))
    }
}

impl TableRow for LocalScopeRaw {
    /// Calculate the byte size of a LocalScope table row
    ///
    /// Returns the total size of one row in the LocalScope table, including:
    /// - method: 2 or 4 bytes (MethodDef table index)
    /// - import_scope: 2 or 4 bytes (ImportScope table index)
    /// - variable_list: 2 or 4 bytes (LocalVariable table index)
    /// - constant_list: 2 or 4 bytes (LocalConstant table index)
    /// - start_offset: 4 bytes
    /// - length: 4 bytes
    ///
    /// The index sizes depend on the metadata table requirements.
    #[rustfmt::skip]
    fn row_size(sizes: &TableInfoRef) -> u32 {
        u32::from(
            /* method */        sizes.table_index_bytes(TableId::MethodDef)
            /* import_scope */  .saturating_add(sizes.table_index_bytes(TableId::ImportScope))
            /* variable_list */ .saturating_add(sizes.table_index_bytes(TableId::LocalVariable))
            /* constant_list */ .saturating_add(sizes.table_index_bytes(TableId::LocalConstant))
            /* start_offset */  .saturating_add(4)
            /* length */        .saturating_add(4)

        )
    }
}