Skip to main content

threatflux_binary_analysis/analysis/
control_flow.rs

1//! Control flow analysis for binary programs
2//!
3//! This module provides functionality to analyze control flow in binary programs,
4//! including basic block identification, control flow graph construction, and
5//! complexity metrics calculation.
6
7use crate::{
8    BinaryError, BinaryFile, Result,
9    disasm::{Disassembler, DisassemblyConfig, section_file_data},
10    types::{
11        Architecture, BasicBlock, ComplexityMetrics, ControlFlow as FlowType, ControlFlowGraph,
12        Function, Instruction,
13    },
14};
15use std::collections::{HashMap, HashSet};
16
17// Note: petgraph integration planned for future advanced CFG analysis
18// #[cfg(feature = "control-flow")]
19// use petgraph::{Directed, Graph};
20
21/// Control flow analyzer
22#[derive(Clone)]
23pub struct ControlFlowAnalyzer {
24    /// Architecture being analyzed
25    #[allow(dead_code)]
26    architecture: Architecture,
27    /// Analysis configuration
28    config: AnalysisConfig,
29}
30
31/// Configuration for control flow analysis
32#[derive(Debug, Clone)]
33pub struct AnalysisConfig {
34    /// Maximum number of instructions accepted per function.
35    pub max_instructions: usize,
36    /// Maximum number of discovered functions accepted for one analysis.
37    pub max_functions: usize,
38    /// Maximum number of instructions accepted across all discovered functions.
39    pub max_total_instructions: usize,
40    /// Maximum number of advanced loops retained across one analysis.
41    pub max_loops: usize,
42    /// Maximum aggregate number of basic-block memberships retained in loops.
43    ///
44    /// A block that belongs to two loops consumes two memberships.
45    pub max_total_loop_body_blocks: usize,
46    /// Enable loop detection
47    pub detect_loops: bool,
48    /// Enable complexity metrics calculation
49    pub calculate_metrics: bool,
50    /// Enable cognitive complexity calculation
51    pub enable_cognitive_complexity: bool,
52    /// Enable advanced loop analysis
53    pub enable_advanced_loops: bool,
54}
55
56impl Default for AnalysisConfig {
57    fn default() -> Self {
58        Self {
59            max_instructions: 10000,
60            max_functions: 10_000,
61            max_total_instructions: 1_000_000,
62            max_loops: 10_000,
63            max_total_loop_body_blocks: 1_000_000,
64            detect_loops: true,
65            calculate_metrics: true,
66            enable_cognitive_complexity: true,
67            enable_advanced_loops: true,
68        }
69    }
70}
71
72#[derive(Clone, Copy)]
73struct LoopBudget {
74    remaining_loops: usize,
75    remaining_body_blocks: usize,
76    configured_max_loops: usize,
77    configured_max_body_blocks: usize,
78}
79
80impl LoopBudget {
81    fn full(config: &AnalysisConfig) -> Self {
82        Self {
83            remaining_loops: config.max_loops,
84            remaining_body_blocks: config.max_total_loop_body_blocks,
85            configured_max_loops: config.max_loops,
86            configured_max_body_blocks: config.max_total_loop_body_blocks,
87        }
88    }
89}
90
91enum FunctionAnalysisFailure {
92    Recoverable(BinaryError),
93    ResourceLimit(BinaryError),
94}
95
96impl FunctionAnalysisFailure {
97    fn into_error(self) -> BinaryError {
98        match self {
99            Self::Recoverable(error) | Self::ResourceLimit(error) => error,
100        }
101    }
102}
103
104struct FunctionAnalysis {
105    graph: ControlFlowGraph,
106    loop_count: usize,
107    loop_body_blocks: usize,
108}
109
110struct DominatorIndex {
111    entry: Vec<usize>,
112    exit: Vec<usize>,
113    nesting_depth: Vec<u32>,
114}
115
116impl DominatorIndex {
117    fn dominates(&self, dominator: usize, block: usize) -> bool {
118        self.entry.get(dominator).is_some_and(|&dominator_entry| {
119            self.entry.get(block).is_some_and(|&block_entry| {
120                dominator_entry <= block_entry && self.exit[block] <= self.exit[dominator]
121            })
122        })
123    }
124}
125
126impl ControlFlowAnalyzer {
127    /// Create a new control flow analyzer
128    pub fn new(architecture: Architecture) -> Self {
129        Self {
130            architecture,
131            config: AnalysisConfig::default(),
132        }
133    }
134
135    /// Create analyzer with custom configuration
136    pub fn with_config(architecture: Architecture, config: AnalysisConfig) -> Self {
137        Self {
138            architecture,
139            config,
140        }
141    }
142
143    /// Analyze control flow for all functions in a binary
144    pub fn analyze_binary(&self, binary: &BinaryFile) -> Result<Vec<ControlFlowGraph>> {
145        // Get functions from symbols
146        let functions = self.extract_functions(binary)?;
147        let mut cfgs = Vec::new();
148        cfgs.try_reserve(functions.len())
149            .map_err(|error| Self::allocation_error("control-flow result graphs", error))?;
150        let mut total_instructions = 0_usize;
151        let mut total_loops = 0_usize;
152        let mut total_loop_body_blocks = 0_usize;
153        let mut last_error = None;
154
155        for function in &functions {
156            let remaining_instructions = self
157                .config
158                .max_total_instructions
159                .saturating_sub(total_instructions);
160            let decode_limit = self
161                .config
162                .max_instructions
163                .saturating_add(1)
164                .min(remaining_instructions.saturating_add(1));
165
166            match self.get_function_instructions(binary, function, decode_limit) {
167                Ok(instructions) => {
168                    total_instructions = self.validate_instruction_count(
169                        function,
170                        instructions.len(),
171                        total_instructions,
172                    )?;
173                    let loop_budget = LoopBudget {
174                        remaining_loops: self.config.max_loops.saturating_sub(total_loops),
175                        remaining_body_blocks: self
176                            .config
177                            .max_total_loop_body_blocks
178                            .saturating_sub(total_loop_body_blocks),
179                        configured_max_loops: self.config.max_loops,
180                        configured_max_body_blocks: self.config.max_total_loop_body_blocks,
181                    };
182                    match self.analyze_function_instructions(function, &instructions, loop_budget) {
183                        Ok(analysis) => {
184                            total_loops =
185                                total_loops
186                                    .checked_add(analysis.loop_count)
187                                    .ok_or_else(|| {
188                                        BinaryError::control_flow("Loop count overflowed usize")
189                                    })?;
190                            total_loop_body_blocks = total_loop_body_blocks
191                                .checked_add(analysis.loop_body_blocks)
192                                .ok_or_else(|| {
193                                    BinaryError::control_flow(
194                                        "Loop body membership count overflowed usize",
195                                    )
196                                })?;
197                            cfgs.push(analysis.graph);
198                        }
199                        Err(FunctionAnalysisFailure::Recoverable(error)) => {
200                            last_error = Some(error);
201                        }
202                        Err(FunctionAnalysisFailure::ResourceLimit(error)) => return Err(error),
203                    }
204                }
205                Err(error) => last_error = Some(error),
206            }
207        }
208
209        if cfgs.is_empty()
210            && let Some(error) = last_error
211        {
212            return Err(error);
213        }
214
215        Ok(cfgs)
216    }
217
218    /// Analyze control flow for a specific function
219    pub fn analyze_function(
220        &self,
221        binary: &BinaryFile,
222        function: &Function,
223    ) -> Result<ControlFlowGraph> {
224        let decode_limit = self
225            .config
226            .max_instructions
227            .saturating_add(1)
228            .min(self.config.max_total_instructions.saturating_add(1));
229        let instructions = self.get_function_instructions(binary, function, decode_limit)?;
230        self.validate_instruction_count(function, instructions.len(), 0)?;
231        self.analyze_function_instructions(function, &instructions, LoopBudget::full(&self.config))
232            .map(|analysis| analysis.graph)
233            .map_err(FunctionAnalysisFailure::into_error)
234    }
235
236    fn analyze_function_instructions(
237        &self,
238        function: &Function,
239        instructions: &[Instruction],
240        loop_budget: LoopBudget,
241    ) -> std::result::Result<FunctionAnalysis, FunctionAnalysisFailure> {
242        // Build basic blocks
243        let mut basic_blocks = self
244            .build_basic_blocks(instructions)
245            .map_err(FunctionAnalysisFailure::Recoverable)?;
246
247        // Dominators are required by loop and nesting analysis.
248        let mut analyzer = self.clone();
249        let dominator_index = if self.config.enable_advanced_loops || self.config.calculate_metrics
250        {
251            analyzer
252                .build_dominator_tree(&mut basic_blocks)
253                .map_err(FunctionAnalysisFailure::Recoverable)?;
254            Some(
255                Self::build_dominator_index(&basic_blocks)
256                    .map_err(FunctionAnalysisFailure::Recoverable)?,
257            )
258        } else {
259            None
260        };
261
262        // Calculate complexity metrics
263        let complexity = if self.config.calculate_metrics {
264            let dominator_index = dominator_index.as_ref().ok_or_else(|| {
265                FunctionAnalysisFailure::Recoverable(BinaryError::control_flow(
266                    "Dominator index was not built for complexity analysis",
267                ))
268            })?;
269            self.calculate_complexity(&basic_blocks, dominator_index)
270        } else {
271            ComplexityMetrics::default()
272        };
273
274        // Perform enhanced analysis if enabled
275        let loops = if self.config.enable_advanced_loops {
276            let dominator_index = dominator_index.as_ref().ok_or_else(|| {
277                FunctionAnalysisFailure::Recoverable(BinaryError::control_flow(
278                    "Dominator index was not built for advanced loop analysis",
279                ))
280            })?;
281            analyzer.analyze_loops_from_dominators(
282                &mut basic_blocks,
283                dominator_index,
284                loop_budget,
285            )?
286        } else {
287            Vec::new()
288        };
289        let loop_body_blocks = loops.iter().try_fold(0_usize, |total, loop_info| {
290            total
291                .checked_add(loop_info.body_blocks.len())
292                .ok_or_else(|| {
293                    FunctionAnalysisFailure::ResourceLimit(BinaryError::control_flow(
294                        "Loop body membership count overflowed usize",
295                    ))
296                })
297        })?;
298        let loop_count = loops.len();
299
300        // Classify block types
301        if self.config.enable_advanced_loops {
302            analyzer
303                .classify_block_types(&mut basic_blocks)
304                .map_err(FunctionAnalysisFailure::Recoverable)?;
305        }
306
307        Ok(FunctionAnalysis {
308            graph: ControlFlowGraph {
309                function: function.clone(),
310                basic_blocks,
311                complexity,
312                loops,
313            },
314            loop_count,
315            loop_body_blocks,
316        })
317    }
318
319    /// Extract functions from binary symbols
320    fn extract_functions(&self, binary: &BinaryFile) -> Result<Vec<Function>> {
321        let mut functions = Vec::new();
322        let mut seen_addresses = HashSet::new();
323        let reservation = binary
324            .symbols()
325            .len()
326            .min(self.config.max_functions.saturating_add(1));
327        functions
328            .try_reserve(reservation)
329            .map_err(|error| Self::allocation_error("discovered functions", error))?;
330        seen_addresses
331            .try_reserve(reservation)
332            .map_err(|error| Self::allocation_error("function address set", error))?;
333
334        for symbol in binary.symbols() {
335            if matches!(symbol.symbol_type, crate::types::SymbolType::Function)
336                && symbol.size > 0
337                && seen_addresses.insert(symbol.address)
338            {
339                self.ensure_function_limit(functions.len().saturating_add(1))?;
340                let function = Function {
341                    name: symbol.name.clone(),
342                    start_address: symbol.address,
343                    end_address: symbol.address.checked_add(symbol.size).ok_or_else(|| {
344                        BinaryError::invalid_data(format!(
345                            "Function '{}' address range overflows",
346                            symbol.name
347                        ))
348                    })?,
349                    size: symbol.size,
350                    function_type: crate::types::FunctionType::Normal,
351                    calling_convention: None,
352                    parameters: Vec::new(),
353                    return_type: None,
354                };
355                functions.push(function);
356            }
357        }
358
359        // If no function symbols, try to find functions from entry point
360        if let (true, Some(entry_point)) = (functions.is_empty(), binary.entry_point()) {
361            self.ensure_function_limit(1)?;
362            let function = Function {
363                name: "_start".to_string(),
364                start_address: entry_point,
365                end_address: entry_point.checked_add(1000).ok_or_else(|| {
366                    BinaryError::invalid_data("Entry-point address range overflows")
367                })?, // Estimate when symbols are unavailable
368                size: 1000,
369                function_type: crate::types::FunctionType::Entrypoint,
370                calling_convention: None,
371                parameters: Vec::new(),
372                return_type: None,
373            };
374            functions.push(function);
375        }
376
377        functions.sort_by(|left, right| {
378            left.start_address
379                .cmp(&right.start_address)
380                .then_with(|| left.name.cmp(&right.name))
381        });
382        Ok(functions)
383    }
384
385    /// Get instructions for a function using the disassembly module
386    fn get_function_instructions(
387        &self,
388        binary: &BinaryFile,
389        function: &Function,
390        max_instructions: usize,
391    ) -> Result<Vec<Instruction>> {
392        // Locate the section containing this function
393        for section in binary.sections() {
394            let start = section.address;
395            let Some(end) = start.checked_add(section.size) else {
396                continue;
397            };
398
399            if section.permissions.execute
400                && function.start_address >= start
401                && function.start_address < end
402            {
403                let function_size = usize::try_from(function.size).unwrap_or(usize::MAX);
404                let data = section_file_data(
405                    binary,
406                    section,
407                    function.start_address - start,
408                    Some(function_size),
409                )?;
410                let length = data.len();
411                if length == 0 {
412                    return Ok(Vec::new());
413                }
414
415                let disassembler = Disassembler::with_config(
416                    self.architecture,
417                    DisassemblyConfig {
418                        max_instructions,
419                        ..DisassemblyConfig::default()
420                    },
421                )?;
422                return disassembler.disassemble_at(data, function.start_address, length);
423            }
424        }
425
426        Err(BinaryError::invalid_data(
427            "Function bytes not found in any executable section",
428        ))
429    }
430
431    fn ensure_function_limit(&self, actual: usize) -> Result<()> {
432        if actual > self.config.max_functions {
433            return Err(BinaryError::control_flow(format!(
434                "Discovered function count {actual} exceeds configured max_functions {}",
435                self.config.max_functions
436            )));
437        }
438        Ok(())
439    }
440
441    fn validate_instruction_count(
442        &self,
443        function: &Function,
444        function_instructions: usize,
445        previous_total: usize,
446    ) -> Result<usize> {
447        if function_instructions > self.config.max_instructions {
448            return Err(BinaryError::control_flow(format!(
449                "Function '{}' decoded {function_instructions} instructions, exceeding configured max_instructions {}",
450                function.name, self.config.max_instructions
451            )));
452        }
453
454        let total = previous_total
455            .checked_add(function_instructions)
456            .ok_or_else(|| BinaryError::control_flow("Total instruction count overflowed usize"))?;
457        if total > self.config.max_total_instructions {
458            return Err(BinaryError::control_flow(format!(
459                "Decoded instruction count {total} exceeds configured max_total_instructions {}",
460                self.config.max_total_instructions
461            )));
462        }
463        Ok(total)
464    }
465
466    fn allocation_error(context: &str, error: std::collections::TryReserveError) -> BinaryError {
467        BinaryError::control_flow(format!("Unable to reserve {context}: {error}"))
468    }
469
470    /// Build basic blocks from instructions
471    fn build_basic_blocks(&self, instructions: &[Instruction]) -> Result<Vec<BasicBlock>> {
472        if instructions.is_empty() {
473            return Ok(Vec::new());
474        }
475
476        let mut basic_blocks = Vec::new();
477        let mut block_starts = HashSet::new();
478
479        // First instruction is always a block start
480        block_starts.insert(instructions[0].address);
481
482        // Find all block boundaries
483        for (i, instr) in instructions.iter().enumerate() {
484            match &instr.flow {
485                FlowType::Jump(target) | FlowType::ConditionalJump(target) => {
486                    // A branch target and the instruction following a terminator start blocks.
487                    block_starts.insert(*target);
488                    if i + 1 < instructions.len() {
489                        block_starts.insert(instructions[i + 1].address);
490                    }
491                }
492                FlowType::Return | FlowType::Interrupt if i + 1 < instructions.len() => {
493                    // Instruction after return/interrupt is a block start (if exists)
494                    block_starts.insert(instructions[i + 1].address);
495                }
496                _ => {}
497            }
498        }
499
500        // Build basic blocks
501        let mut current_block_id = 0;
502        let mut current_block_start = 0;
503
504        for (i, instr) in instructions.iter().enumerate() {
505            if block_starts.contains(&instr.address) && i > current_block_start {
506                // End current block
507                let block_instructions = instructions[current_block_start..i].to_vec();
508                let start_addr = instructions[current_block_start].address;
509                let end_addr = instructions[i - 1]
510                    .address
511                    .checked_add(instructions[i - 1].size as u64)
512                    .ok_or_else(|| {
513                        BinaryError::invalid_data("Instruction address range overflows")
514                    })?;
515
516                basic_blocks.push(BasicBlock {
517                    id: current_block_id,
518                    start_address: start_addr,
519                    end_address: end_addr,
520                    instructions: block_instructions,
521                    successors: Vec::new(),   // Will be filled later
522                    predecessors: Vec::new(), // Will be filled later
523                    block_type: crate::types::BlockType::Normal, // Will be classified later
524                    dominator: None,          // Will be computed later
525                    dominance_frontier: Vec::new(), // Will be computed later
526                });
527
528                current_block_id += 1;
529                current_block_start = i;
530            }
531        }
532
533        // Add the last block
534        if current_block_start < instructions.len() {
535            let block_instructions = instructions[current_block_start..].to_vec();
536            let start_addr = instructions[current_block_start].address;
537            let last_instruction = &instructions[instructions.len() - 1];
538            let end_addr = last_instruction
539                .address
540                .checked_add(last_instruction.size as u64)
541                .ok_or_else(|| BinaryError::invalid_data("Instruction address range overflows"))?;
542
543            basic_blocks.push(BasicBlock {
544                id: current_block_id,
545                start_address: start_addr,
546                end_address: end_addr,
547                instructions: block_instructions,
548                successors: Vec::new(),
549                predecessors: Vec::new(),
550                block_type: crate::types::BlockType::Normal, // Will be classified later
551                dominator: None,                             // Will be computed later
552                dominance_frontier: Vec::new(),              // Will be computed later
553            });
554        }
555
556        // Build successor/predecessor relationships
557        self.build_cfg_edges(&mut basic_blocks)?;
558
559        Ok(basic_blocks)
560    }
561
562    /// Build control flow graph edges between basic blocks
563    fn build_cfg_edges(&self, basic_blocks: &mut [BasicBlock]) -> Result<()> {
564        let mut addr_to_block: HashMap<u64, usize> = HashMap::new();
565
566        // Build address to block ID mapping
567        for (i, block) in basic_blocks.iter().enumerate() {
568            addr_to_block.insert(block.start_address, i);
569        }
570
571        // Build edges
572        for i in 0..basic_blocks.len() {
573            let block = &basic_blocks[i];
574            if let Some(last_instr) = block.instructions.last() {
575                match &last_instr.flow {
576                    FlowType::Sequential => {
577                        // Fall through to next block
578                        if i + 1 < basic_blocks.len() {
579                            Self::add_cfg_edge(basic_blocks, i, i + 1);
580                        }
581                    }
582                    FlowType::Jump(target) => {
583                        // Unconditional jump
584                        if let Some(&target_block) = addr_to_block.get(target) {
585                            Self::add_cfg_edge(basic_blocks, i, target_block);
586                        }
587                    }
588                    FlowType::ConditionalJump(target) => {
589                        // Conditional jump - two successors
590                        if let Some(&target_block) = addr_to_block.get(target) {
591                            Self::add_cfg_edge(basic_blocks, i, target_block);
592                        }
593                        // Fall through
594                        if i + 1 < basic_blocks.len() {
595                            Self::add_cfg_edge(basic_blocks, i, i + 1);
596                        }
597                    }
598                    FlowType::Call(_target) => {
599                        // Function call - continues to next instruction
600                        if i + 1 < basic_blocks.len() {
601                            Self::add_cfg_edge(basic_blocks, i, i + 1);
602                        }
603                        // Note: Call target is not added as successor for CFG
604                    }
605                    FlowType::Return | FlowType::Interrupt => {
606                        // No successors
607                    }
608                    FlowType::Unknown => {
609                        // Conservatively assume fall through
610                        if i + 1 < basic_blocks.len() {
611                            Self::add_cfg_edge(basic_blocks, i, i + 1);
612                        }
613                    }
614                }
615            }
616        }
617
618        Ok(())
619    }
620
621    fn add_cfg_edge(basic_blocks: &mut [BasicBlock], source: usize, target: usize) {
622        if !basic_blocks[source].successors.contains(&target) {
623            basic_blocks[source].successors.push(target);
624            basic_blocks[target].predecessors.push(source);
625        }
626    }
627
628    /// Calculate complexity metrics for a control flow graph
629    fn calculate_complexity(
630        &self,
631        basic_blocks: &[BasicBlock],
632        dominator_index: &DominatorIndex,
633    ) -> ComplexityMetrics {
634        let basic_block_count = u32::try_from(basic_blocks.len()).unwrap_or(u32::MAX);
635        let edge_count = basic_blocks.iter().fold(0_u32, |count, block| {
636            count.saturating_add(u32::try_from(block.successors.len()).unwrap_or(u32::MAX))
637        });
638
639        // Cyclomatic complexity = E - N + 2P
640        // Where E = edges, N = nodes, and P = weakly connected components.
641        let cyclomatic_complexity = if basic_block_count > 0 {
642            edge_count
643                .saturating_add(self.weak_component_count(basic_blocks).saturating_mul(2))
644                .saturating_sub(basic_block_count)
645        } else {
646            0
647        };
648
649        // Detect loops (simplified)
650        let loop_count = self.detect_loops(basic_blocks);
651
652        // Calculate nesting depth (simplified)
653        let nesting_depth = dominator_index
654            .nesting_depth
655            .iter()
656            .copied()
657            .max()
658            .unwrap_or(0);
659
660        // Calculate cognitive complexity
661        let cognitive_complexity = if self.config.enable_cognitive_complexity {
662            self.calculate_cognitive_complexity(basic_blocks, &dominator_index.nesting_depth)
663        } else {
664            0
665        };
666
667        // Calculate Halstead metrics if available
668        let halstead_metrics = self.calculate_halstead_metrics(basic_blocks);
669
670        // Calculate maintainability index if Halstead metrics are available
671        let maintainability_index = if let Some(ref halstead) = halstead_metrics {
672            let instruction_count = basic_blocks.iter().fold(0_u32, |count, block| {
673                count.saturating_add(u32::try_from(block.instructions.len()).unwrap_or(u32::MAX))
674            });
675            self.calculate_maintainability_index(halstead, cyclomatic_complexity, instruction_count)
676        } else {
677            None
678        };
679
680        ComplexityMetrics {
681            cyclomatic_complexity,
682            basic_block_count,
683            edge_count,
684            nesting_depth,
685            loop_count,
686            cognitive_complexity,
687            halstead_metrics,
688            maintainability_index,
689        }
690    }
691
692    /// Detect loops in the control flow graph
693    fn detect_loops(&self, basic_blocks: &[BasicBlock]) -> u32 {
694        if !self.config.detect_loops {
695            return 0;
696        }
697
698        u32::try_from(Self::collect_back_edges(basic_blocks).len()).unwrap_or(u32::MAX)
699    }
700
701    /// Find DFS back edges without using the process call stack.
702    fn collect_back_edges(basic_blocks: &[BasicBlock]) -> Vec<(usize, usize)> {
703        let mut state = vec![0_u8; basic_blocks.len()];
704        let mut back_edges = Vec::new();
705
706        for root in 0..basic_blocks.len() {
707            if state[root] != 0 {
708                continue;
709            }
710
711            state[root] = 1;
712            let mut stack = vec![(root, 0_usize)];
713            while let Some(&(node, next_successor)) = stack.last() {
714                if next_successor == basic_blocks[node].successors.len() {
715                    state[node] = 2;
716                    stack.pop();
717                    continue;
718                }
719
720                if let Some(last) = stack.last_mut() {
721                    last.1 += 1;
722                }
723                let successor = basic_blocks[node].successors[next_successor];
724                match state[successor] {
725                    0 => {
726                        state[successor] = 1;
727                        stack.push((successor, 0));
728                    }
729                    1 => back_edges.push((node, successor)),
730                    _ => {}
731                }
732            }
733        }
734
735        back_edges
736    }
737
738    fn weak_component_count(&self, basic_blocks: &[BasicBlock]) -> u32 {
739        let mut visited = vec![false; basic_blocks.len()];
740        let mut components = 0_u32;
741
742        for root in 0..basic_blocks.len() {
743            if visited[root] {
744                continue;
745            }
746            components = components.saturating_add(1);
747            visited[root] = true;
748            let mut stack = vec![root];
749            while let Some(node) = stack.pop() {
750                for neighbor in basic_blocks[node]
751                    .successors
752                    .iter()
753                    .chain(&basic_blocks[node].predecessors)
754                    .copied()
755                {
756                    if !visited[neighbor] {
757                        visited[neighbor] = true;
758                        stack.push(neighbor);
759                    }
760                }
761            }
762        }
763
764        components
765    }
766
767    /// Calculate cognitive complexity (different from cyclomatic complexity)
768    /// Cognitive complexity measures how difficult the code is to understand
769    fn calculate_cognitive_complexity(
770        &self,
771        basic_blocks: &[BasicBlock],
772        nesting_depths: &[u32],
773    ) -> u32 {
774        let mut complexity = 0_u32;
775        for (index, block) in basic_blocks.iter().enumerate() {
776            if matches!(
777                block
778                    .instructions
779                    .last()
780                    .map(|instruction| &instruction.flow),
781                Some(FlowType::ConditionalJump(_))
782            ) {
783                complexity = complexity.saturating_add(
784                    1_u32.saturating_add(nesting_depths.get(index).copied().unwrap_or(0)),
785                );
786            }
787        }
788
789        complexity.saturating_add(
790            u32::try_from(Self::collect_back_edges(basic_blocks).len()).unwrap_or(u32::MAX),
791        )
792    }
793
794    /// Calculate Halstead metrics for software complexity
795    fn calculate_halstead_metrics(
796        &self,
797        basic_blocks: &[BasicBlock],
798    ) -> Option<crate::types::HalsteadMetrics> {
799        let mut operators = HashMap::new();
800        let mut operands = HashMap::new();
801        let mut total_operators = 0_u32;
802        let mut total_operands = 0_u32;
803
804        for block in basic_blocks {
805            for instruction in &block.instructions {
806                // Count operators (mnemonics)
807                let operator_count = operators
808                    .entry(instruction.mnemonic.clone())
809                    .or_insert(0_u32);
810                *operator_count = operator_count.saturating_add(1);
811                total_operators = total_operators.saturating_add(1);
812
813                // Count operands (simplified - split operands string)
814                if !instruction.operands.is_empty() {
815                    for op in instruction.operands.split(',') {
816                        let trimmed = op.trim();
817                        if !trimmed.is_empty() {
818                            let operand_count =
819                                operands.entry(trimmed.to_string()).or_insert(0_u32);
820                            *operand_count = operand_count.saturating_add(1);
821                            total_operands = total_operands.saturating_add(1);
822                        }
823                    }
824                }
825            }
826        }
827
828        let n1 = u32::try_from(operators.len()).unwrap_or(u32::MAX); // Distinct operators
829        let n2 = u32::try_from(operands.len()).unwrap_or(u32::MAX); // Distinct operands
830        let capital_n1 = total_operators; // Total operators
831        let capital_n2 = total_operands; // Total operands
832
833        if n1 == 0 && n2 == 0 {
834            return None;
835        }
836
837        let vocabulary = n1.saturating_add(n2);
838        let length = capital_n1.saturating_add(capital_n2);
839        let x_log2_x = |value: u32| {
840            if value == 0 {
841                0.0
842            } else {
843                let value = f64::from(value);
844                value * value.log2()
845            }
846        };
847        let calculated_length = x_log2_x(n1) + x_log2_x(n2);
848        let volume = (length as f64) * (vocabulary as f64).log2();
849        let difficulty = if n2 > 0 {
850            ((n1 as f64) / 2.0) * ((capital_n2 as f64) / (n2 as f64))
851        } else {
852            0.0
853        };
854        let effort = difficulty * volume;
855        let time = effort / 18.0; // Assuming 18 mental discriminations per second
856        let bugs = volume / 3000.0; // Estimated bugs
857
858        Some(crate::types::HalsteadMetrics {
859            n1,
860            n2,
861            capital_n1,
862            capital_n2,
863            vocabulary,
864            length,
865            calculated_length,
866            volume,
867            difficulty,
868            effort,
869            time,
870            bugs,
871        })
872    }
873
874    /// Calculate maintainability index
875    fn calculate_maintainability_index(
876        &self,
877        halstead: &crate::types::HalsteadMetrics,
878        cyclomatic_complexity: u32,
879        lines_of_code: u32,
880    ) -> Option<f64> {
881        if halstead.volume <= 0.0 || lines_of_code == 0 {
882            return None;
883        }
884
885        // Maintainability Index = 171 - 5.2 * ln(HV) - 0.23 * CC - 16.2 * ln(LOC)
886        // Where HV = Halstead Volume, CC = Cyclomatic Complexity, LOC = Lines of Code
887        let mi = 171.0
888            - 5.2 * halstead.volume.ln()
889            - 0.23 * (cyclomatic_complexity as f64)
890            - 16.2 * (lines_of_code as f64).ln();
891
892        // Clamp to 0-100 range
893        Some(mi.clamp(0.0, 100.0))
894    }
895
896    /// Perform enhanced loop analysis
897    pub fn analyze_loops(
898        &mut self,
899        basic_blocks: &mut [BasicBlock],
900    ) -> Result<Vec<crate::types::Loop>> {
901        if !self.config.enable_advanced_loops {
902            return Ok(Vec::new());
903        }
904
905        self.build_dominator_tree(basic_blocks)?;
906        let dominator_index = Self::build_dominator_index(basic_blocks)?;
907        self.analyze_loops_from_dominators(
908            basic_blocks,
909            &dominator_index,
910            LoopBudget::full(&self.config),
911        )
912        .map_err(FunctionAnalysisFailure::into_error)
913    }
914
915    fn analyze_loops_from_dominators(
916        &mut self,
917        basic_blocks: &mut [BasicBlock],
918        dominator_index: &DominatorIndex,
919        budget: LoopBudget,
920    ) -> std::result::Result<Vec<crate::types::Loop>, FunctionAnalysisFailure> {
921        let mut loops = Vec::new();
922        let mut total_body_blocks = 0_usize;
923        let mut back_edges = Self::collect_back_edges(basic_blocks);
924        back_edges.sort_unstable_by_key(|&(tail, head)| (head, tail));
925
926        // Back edges are grouped by header so multiple latches can be merged
927        // immediately instead of retaining duplicate loop bodies.
928        for (tail, head) in back_edges {
929            if dominator_index.dominates(head, tail) {
930                let extends_existing = loops
931                    .last()
932                    .is_some_and(|loop_info: &crate::types::Loop| loop_info.header_block == head);
933                if !extends_existing && loops.len() >= budget.remaining_loops {
934                    return Err(FunctionAnalysisFailure::ResourceLimit(
935                        BinaryError::control_flow(format!(
936                            "Advanced loop count exceeds configured max_loops {}",
937                            budget.configured_max_loops
938                        )),
939                    ));
940                }
941
942                let mut loop_info = self.analyze_natural_loop(
943                    head,
944                    tail,
945                    basic_blocks,
946                    dominator_index,
947                    budget.configured_max_body_blocks,
948                )?;
949
950                if extends_existing {
951                    let existing = loops.last_mut().expect("existing loop was checked above");
952                    let old_len = existing.body_blocks.len();
953                    let merged_len =
954                        Self::sorted_union_len(&existing.body_blocks, &loop_info.body_blocks);
955                    let added = merged_len.saturating_sub(old_len);
956                    let next_total = total_body_blocks.checked_add(added).ok_or_else(|| {
957                        FunctionAnalysisFailure::ResourceLimit(BinaryError::control_flow(
958                            "Loop body membership count overflowed usize",
959                        ))
960                    })?;
961                    if next_total > budget.remaining_body_blocks {
962                        return Err(FunctionAnalysisFailure::ResourceLimit(
963                            BinaryError::control_flow(format!(
964                                "Advanced loop body membership exceeds configured max_total_loop_body_blocks {}",
965                                budget.configured_max_body_blocks
966                            )),
967                        ));
968                    }
969                    existing
970                        .body_blocks
971                        .try_reserve(loop_info.body_blocks.len())
972                        .map_err(|error| {
973                            FunctionAnalysisFailure::ResourceLimit(Self::allocation_error(
974                                "merged loop body blocks",
975                                error,
976                            ))
977                        })?;
978                    existing.body_blocks.append(&mut loop_info.body_blocks);
979                    existing.body_blocks.sort_unstable();
980                    existing.body_blocks.dedup();
981                    total_body_blocks = next_total;
982                } else {
983                    let next_total = total_body_blocks
984                        .checked_add(loop_info.body_blocks.len())
985                        .ok_or_else(|| {
986                            FunctionAnalysisFailure::ResourceLimit(BinaryError::control_flow(
987                                "Loop body membership count overflowed usize",
988                            ))
989                        })?;
990                    if next_total > budget.remaining_body_blocks {
991                        return Err(FunctionAnalysisFailure::ResourceLimit(
992                            BinaryError::control_flow(format!(
993                                "Advanced loop body membership exceeds configured max_total_loop_body_blocks {}",
994                                budget.configured_max_body_blocks
995                            )),
996                        ));
997                    }
998                    loops.try_reserve(1).map_err(|error| {
999                        FunctionAnalysisFailure::ResourceLimit(Self::allocation_error(
1000                            "advanced loops",
1001                            error,
1002                        ))
1003                    })?;
1004                    loops.push(loop_info);
1005                    total_body_blocks = next_total;
1006                }
1007            }
1008        }
1009
1010        // Classify loop types and detect induction variables
1011        for loop_info in &mut loops {
1012            Self::refresh_loop_exits(loop_info, basic_blocks)
1013                .map_err(FunctionAnalysisFailure::ResourceLimit)?;
1014            self.classify_loop_type(loop_info);
1015            self.detect_induction_variables(loop_info, basic_blocks);
1016        }
1017
1018        let mut containing_loops = Vec::new();
1019        containing_loops
1020            .try_reserve_exact(basic_blocks.len())
1021            .map_err(|error| {
1022                FunctionAnalysisFailure::ResourceLimit(Self::allocation_error(
1023                    "loop nesting counters",
1024                    error,
1025                ))
1026            })?;
1027        containing_loops.resize(basic_blocks.len(), 0_u32);
1028        for loop_info in &loops {
1029            containing_loops[loop_info.header_block] =
1030                containing_loops[loop_info.header_block].saturating_add(1);
1031            for &block in &loop_info.body_blocks {
1032                containing_loops[block] = containing_loops[block].saturating_add(1);
1033            }
1034        }
1035        for loop_info in &mut loops {
1036            loop_info.nesting_level = containing_loops[loop_info.header_block];
1037        }
1038
1039        Ok(loops)
1040    }
1041
1042    /// Analyze a natural loop given a back edge
1043    fn analyze_natural_loop(
1044        &self,
1045        header: usize,
1046        tail: usize,
1047        basic_blocks: &[BasicBlock],
1048        dominator_index: &DominatorIndex,
1049        max_body_blocks: usize,
1050    ) -> std::result::Result<crate::types::Loop, FunctionAnalysisFailure> {
1051        let mut loop_blocks = HashSet::new();
1052        let mut worklist = Vec::new();
1053        let reservation = basic_blocks.len().min(max_body_blocks.saturating_add(1));
1054        loop_blocks.try_reserve(reservation).map_err(|error| {
1055            FunctionAnalysisFailure::ResourceLimit(Self::allocation_error(
1056                "natural loop block set",
1057                error,
1058            ))
1059        })?;
1060        worklist.try_reserve(reservation).map_err(|error| {
1061            FunctionAnalysisFailure::ResourceLimit(Self::allocation_error(
1062                "natural loop worklist",
1063                error,
1064            ))
1065        })?;
1066
1067        // Start with the header and tail
1068        loop_blocks.insert(header);
1069        if tail != header {
1070            loop_blocks.insert(tail);
1071            if max_body_blocks == 0 {
1072                return Err(FunctionAnalysisFailure::ResourceLimit(
1073                    BinaryError::control_flow(format!(
1074                        "Advanced loop body membership exceeds configured max_total_loop_body_blocks {max_body_blocks}"
1075                    )),
1076                ));
1077            }
1078            worklist.push(tail);
1079        }
1080
1081        // Find all blocks in the loop using backwards traversal
1082        while let Some(current) = worklist.pop() {
1083            for &pred in &basic_blocks[current].predecessors {
1084                if !loop_blocks.contains(&pred) && dominator_index.dominates(header, pred) {
1085                    if loop_blocks.len().saturating_sub(1) >= max_body_blocks {
1086                        return Err(FunctionAnalysisFailure::ResourceLimit(
1087                            BinaryError::control_flow(format!(
1088                                "Advanced loop body membership exceeds configured max_total_loop_body_blocks {max_body_blocks}"
1089                            )),
1090                        ));
1091                    }
1092                    loop_blocks.insert(pred);
1093                    worklist.push(pred);
1094                }
1095            }
1096        }
1097
1098        let mut body_blocks: Vec<usize> =
1099            loop_blocks.into_iter().filter(|&id| id != header).collect();
1100        body_blocks.sort_unstable();
1101
1102        Ok(crate::types::Loop {
1103            header_block: header,
1104            body_blocks,
1105            exit_blocks: Vec::new(),
1106            loop_type: crate::types::LoopType::Unknown, // Will be classified later
1107            induction_variables: Vec::new(),            // Will be detected later
1108            is_natural: true,                           // Natural loops by definition
1109            nesting_level: 0,                           // Will be calculated later
1110        })
1111    }
1112
1113    fn sorted_union_len(left: &[usize], right: &[usize]) -> usize {
1114        let (mut left_index, mut right_index, mut count) = (0_usize, 0_usize, 0_usize);
1115        while left_index < left.len() && right_index < right.len() {
1116            count = count.saturating_add(1);
1117            match left[left_index].cmp(&right[right_index]) {
1118                std::cmp::Ordering::Less => left_index += 1,
1119                std::cmp::Ordering::Greater => right_index += 1,
1120                std::cmp::Ordering::Equal => {
1121                    left_index += 1;
1122                    right_index += 1;
1123                }
1124            }
1125        }
1126        count
1127            .saturating_add(left.len().saturating_sub(left_index))
1128            .saturating_add(right.len().saturating_sub(right_index))
1129    }
1130
1131    fn refresh_loop_exits(
1132        loop_info: &mut crate::types::Loop,
1133        basic_blocks: &[BasicBlock],
1134    ) -> Result<()> {
1135        let mut blocks = HashSet::new();
1136        blocks
1137            .try_reserve(loop_info.body_blocks.len().saturating_add(1))
1138            .map_err(|error| Self::allocation_error("loop exit block set", error))?;
1139        blocks.insert(loop_info.header_block);
1140        blocks.extend(loop_info.body_blocks.iter().copied());
1141
1142        loop_info.exit_blocks.clear();
1143        for &block in &blocks {
1144            loop_info
1145                .exit_blocks
1146                .try_reserve(basic_blocks[block].successors.len())
1147                .map_err(|error| Self::allocation_error("loop exit blocks", error))?;
1148            loop_info.exit_blocks.extend(
1149                basic_blocks[block]
1150                    .successors
1151                    .iter()
1152                    .copied()
1153                    .filter(|successor| !blocks.contains(successor)),
1154            );
1155        }
1156        loop_info.exit_blocks.sort_unstable();
1157        loop_info.exit_blocks.dedup();
1158        Ok(())
1159    }
1160
1161    /// Classify only distinctions that can be established from a CFG alone.
1162    fn classify_loop_type(&self, loop_info: &mut crate::types::Loop) {
1163        loop_info.loop_type = if loop_info.exit_blocks.is_empty() {
1164            crate::types::LoopType::Infinite
1165        } else if loop_info.is_natural {
1166            crate::types::LoopType::Natural
1167        } else {
1168            crate::types::LoopType::Irreducible
1169        };
1170    }
1171
1172    /// Detect induction variables in a loop
1173    fn detect_induction_variables(
1174        &self,
1175        loop_info: &mut crate::types::Loop,
1176        basic_blocks: &[BasicBlock],
1177    ) {
1178        let mut induction_vars = HashSet::new();
1179
1180        // Look for variables that are incremented/decremented in the loop
1181        for block_id in
1182            std::iter::once(loop_info.header_block).chain(loop_info.body_blocks.iter().copied())
1183        {
1184            let block = &basic_blocks[block_id];
1185            for instruction in &block.instructions {
1186                match instruction.mnemonic.as_str() {
1187                    "inc" | "dec" | "add" | "sub" if !instruction.operands.is_empty() => {
1188                        // Extract operand as potential induction variable
1189                        let operand = instruction.operands.split(',').next().unwrap_or("").trim();
1190                        if !operand.is_empty()
1191                            && !operand.starts_with('#')
1192                            && !operand.starts_with('$')
1193                        {
1194                            induction_vars.insert(operand.to_string());
1195                        }
1196                    }
1197                    _ => {}
1198                }
1199            }
1200        }
1201
1202        loop_info.induction_variables = induction_vars.into_iter().collect();
1203        loop_info.induction_variables.sort_unstable();
1204    }
1205
1206    /// Build dominator tree for enhanced block classification
1207    pub fn build_dominator_tree(&mut self, basic_blocks: &mut [BasicBlock]) -> Result<()> {
1208        if basic_blocks.is_empty() {
1209            return Ok(());
1210        }
1211        Self::validate_cfg(basic_blocks)?;
1212
1213        let n = basic_blocks.len();
1214        let reverse_postorder = Self::reverse_postorder(basic_blocks);
1215        let mut rpo_index = vec![usize::MAX; n];
1216        for (index, &block) in reverse_postorder.iter().enumerate() {
1217            rpo_index[block] = index;
1218        }
1219        let mut dominators = vec![None; n];
1220        dominators[0] = Some(0); // Entry block dominates itself
1221
1222        // Cooper-Harvey-Kennedy iterative immediate-dominator algorithm.
1223        let mut changed = true;
1224        while changed {
1225            changed = false;
1226            for &block in reverse_postorder.iter().skip(1) {
1227                let mut processed_predecessors = basic_blocks[block]
1228                    .predecessors
1229                    .iter()
1230                    .copied()
1231                    .filter(|&pred| dominators[pred].is_some());
1232                let Some(mut new_dominator) = processed_predecessors.next() else {
1233                    continue;
1234                };
1235
1236                for predecessor in processed_predecessors {
1237                    new_dominator = Self::intersect_dominators(
1238                        new_dominator,
1239                        predecessor,
1240                        &dominators,
1241                        &rpo_index,
1242                    )
1243                    .ok_or_else(|| {
1244                        BinaryError::invalid_data("Malformed dominator predecessor chain")
1245                    })?;
1246                }
1247
1248                if dominators[block] != Some(new_dominator) {
1249                    dominators[block] = Some(new_dominator);
1250                    changed = true;
1251                }
1252            }
1253        }
1254
1255        // Set dominator information in basic blocks
1256        for (i, &dom) in dominators.iter().enumerate() {
1257            basic_blocks[i].dominator = dom;
1258        }
1259
1260        Ok(())
1261    }
1262
1263    /// Index the immediate-dominator forest once for linear-time nesting-depth
1264    /// calculation and constant-time dominance queries.
1265    fn build_dominator_index(basic_blocks: &[BasicBlock]) -> Result<DominatorIndex> {
1266        let block_count = basic_blocks.len();
1267        let mut first_child = vec![None; block_count];
1268        let mut next_sibling = vec![None; block_count];
1269        let mut roots = Vec::new();
1270        roots
1271            .try_reserve(block_count)
1272            .map_err(|error| Self::allocation_error("dominator roots", error))?;
1273
1274        for (block, basic_block) in basic_blocks.iter().enumerate() {
1275            match basic_block.dominator {
1276                None => roots.push(block),
1277                Some(parent) if parent == block => roots.push(block),
1278                Some(parent) => {
1279                    if parent >= block_count {
1280                        return Err(BinaryError::invalid_data(format!(
1281                            "Basic block {block} has out-of-range dominator {parent}"
1282                        )));
1283                    }
1284                    next_sibling[block] = first_child[parent];
1285                    first_child[parent] = Some(block);
1286                }
1287            }
1288        }
1289
1290        let mut entry = vec![usize::MAX; block_count];
1291        let mut exit = vec![usize::MAX; block_count];
1292        let mut nesting_depth = vec![0_u32; block_count];
1293        let mut stack = Vec::new();
1294        stack
1295            .try_reserve(block_count.saturating_mul(2))
1296            .map_err(|error| Self::allocation_error("dominator traversal", error))?;
1297        let mut clock = 0_usize;
1298
1299        for root in roots {
1300            if entry[root] != usize::MAX {
1301                continue;
1302            }
1303            stack.push((root, false));
1304            while let Some((block, exiting)) = stack.pop() {
1305                if exiting {
1306                    exit[block] = clock;
1307                    clock = clock.saturating_add(1);
1308                    continue;
1309                }
1310                if entry[block] != usize::MAX {
1311                    return Err(BinaryError::invalid_data(
1312                        "Immediate-dominator relation contains a cycle",
1313                    ));
1314                }
1315
1316                entry[block] = clock;
1317                clock = clock.saturating_add(1);
1318                if let Some(parent) = basic_blocks[block]
1319                    .dominator
1320                    .filter(|&parent| parent != block)
1321                {
1322                    nesting_depth[block] = nesting_depth[parent]
1323                        .saturating_add(u32::from(basic_blocks[parent].successors.len() > 1));
1324                }
1325
1326                stack.push((block, true));
1327                let mut child = first_child[block];
1328                while let Some(child_block) = child {
1329                    stack.push((child_block, false));
1330                    child = next_sibling[child_block];
1331                }
1332            }
1333        }
1334
1335        if entry.contains(&usize::MAX) {
1336            return Err(BinaryError::invalid_data(
1337                "Immediate-dominator relation contains a rootless cycle",
1338            ));
1339        }
1340
1341        Ok(DominatorIndex {
1342            entry,
1343            exit,
1344            nesting_depth,
1345        })
1346    }
1347
1348    /// Intersect two dominators to find common dominator
1349    fn intersect_dominators(
1350        mut b1: usize,
1351        mut b2: usize,
1352        dominators: &[Option<usize>],
1353        rpo_index: &[usize],
1354    ) -> Option<usize> {
1355        while b1 != b2 {
1356            while rpo_index[b1] > rpo_index[b2] {
1357                b1 = dominators[b1]?;
1358            }
1359            while rpo_index[b2] > rpo_index[b1] {
1360                b2 = dominators[b2]?;
1361            }
1362        }
1363        Some(b1)
1364    }
1365
1366    fn reverse_postorder(basic_blocks: &[BasicBlock]) -> Vec<usize> {
1367        let mut visited = vec![false; basic_blocks.len()];
1368        let mut postorder = Vec::with_capacity(basic_blocks.len());
1369        visited[0] = true;
1370        let mut stack = vec![(0_usize, 0_usize)];
1371
1372        while let Some(&(block, successor_index)) = stack.last() {
1373            if successor_index == basic_blocks[block].successors.len() {
1374                postorder.push(block);
1375                stack.pop();
1376                continue;
1377            }
1378
1379            if let Some(last) = stack.last_mut() {
1380                last.1 += 1;
1381            }
1382            let successor = basic_blocks[block].successors[successor_index];
1383            if !visited[successor] {
1384                visited[successor] = true;
1385                stack.push((successor, 0));
1386            }
1387        }
1388
1389        postorder.reverse();
1390        postorder
1391    }
1392
1393    fn validate_cfg(basic_blocks: &[BasicBlock]) -> Result<()> {
1394        let mut successor_edges = HashSet::new();
1395        let mut predecessor_edges = HashSet::new();
1396        for (source, block) in basic_blocks.iter().enumerate() {
1397            for &target in &block.successors {
1398                if basic_blocks.get(target).is_none() {
1399                    return Err(BinaryError::invalid_data(format!(
1400                        "Basic block {source} has out-of-range successor {target}"
1401                    )));
1402                }
1403                if !successor_edges.insert((source, target)) {
1404                    return Err(BinaryError::invalid_data(format!(
1405                        "CFG contains duplicate successor edge {source} -> {target}"
1406                    )));
1407                }
1408            }
1409            for &predecessor in &block.predecessors {
1410                if basic_blocks.get(predecessor).is_none() {
1411                    return Err(BinaryError::invalid_data(format!(
1412                        "Basic block {source} has out-of-range predecessor {predecessor}"
1413                    )));
1414                }
1415                if !predecessor_edges.insert((predecessor, source)) {
1416                    return Err(BinaryError::invalid_data(format!(
1417                        "CFG contains duplicate predecessor edge {predecessor} -> {source}"
1418                    )));
1419                }
1420            }
1421        }
1422        if successor_edges != predecessor_edges {
1423            return Err(BinaryError::invalid_data(
1424                "CFG successor and predecessor edge sets are inconsistent",
1425            ));
1426        }
1427        Ok(())
1428    }
1429
1430    /// Classify basic block types based on their role in control flow
1431    pub fn classify_block_types(&mut self, basic_blocks: &mut [BasicBlock]) -> Result<()> {
1432        if basic_blocks.is_empty() {
1433            return Ok(());
1434        }
1435        Self::validate_cfg(basic_blocks)?;
1436
1437        // Entry block
1438        basic_blocks[0].block_type = crate::types::BlockType::Entry;
1439
1440        // Classify other blocks
1441        #[allow(clippy::needless_range_loop)]
1442        for i in 1..basic_blocks.len() {
1443            let block = &basic_blocks[i];
1444
1445            // Exit blocks (no successors)
1446            if block.successors.is_empty() {
1447                basic_blocks[i].block_type = crate::types::BlockType::Exit;
1448                continue;
1449            }
1450
1451            // Return blocks
1452            if let Some(last_instruction) = block.instructions.last() {
1453                match last_instruction.flow {
1454                    FlowType::Return => {
1455                        basic_blocks[i].block_type = crate::types::BlockType::Return;
1456                        continue;
1457                    }
1458                    FlowType::Call(_) => {
1459                        basic_blocks[i].block_type = crate::types::BlockType::Call;
1460                        continue;
1461                    }
1462                    FlowType::ConditionalJump(_) => {
1463                        basic_blocks[i].block_type = crate::types::BlockType::Conditional;
1464                        continue;
1465                    }
1466                    _ => {}
1467                }
1468            }
1469
1470            // Default to normal block
1471            basic_blocks[i].block_type = crate::types::BlockType::Normal;
1472        }
1473
1474        Ok(())
1475    }
1476}
1477
1478/// Analyze binary control flow
1479pub fn analyze_binary(binary: &BinaryFile) -> Result<Vec<ControlFlowGraph>> {
1480    let analyzer = ControlFlowAnalyzer::new(binary.architecture());
1481    analyzer.analyze_binary(binary)
1482}
1483
1484/// Analyze control flow for a specific function
1485pub fn analyze_function(binary: &BinaryFile, function: &Function) -> Result<ControlFlowGraph> {
1486    let analyzer = ControlFlowAnalyzer::new(binary.architecture());
1487    analyzer.analyze_function(binary, function)
1488}
1489
1490#[cfg(test)]
1491mod tests {
1492    use super::*;
1493    use crate::types::*;
1494
1495    struct TestFormat {
1496        sections: Vec<Section>,
1497        symbols: Vec<Symbol>,
1498        metadata: BinaryMetadata,
1499    }
1500
1501    impl BinaryFormatTrait for TestFormat {
1502        fn format_type(&self) -> BinaryFormat {
1503            BinaryFormat::Raw
1504        }
1505
1506        fn architecture(&self) -> Architecture {
1507            Architecture::X86_64
1508        }
1509
1510        fn entry_point(&self) -> Option<u64> {
1511            None
1512        }
1513
1514        fn sections(&self) -> &[Section] {
1515            &self.sections
1516        }
1517
1518        fn symbols(&self) -> &[Symbol] {
1519            &self.symbols
1520        }
1521
1522        fn imports(&self) -> &[Import] {
1523            &[]
1524        }
1525
1526        fn exports(&self) -> &[Export] {
1527            &[]
1528        }
1529
1530        fn metadata(&self) -> &BinaryMetadata {
1531            &self.metadata
1532        }
1533    }
1534
1535    fn function_symbol(name: &str, address: u64) -> Symbol {
1536        Symbol {
1537            name: name.to_string(),
1538            demangled_name: None,
1539            address,
1540            size: 1,
1541            symbol_type: SymbolType::Function,
1542            binding: SymbolBinding::Global,
1543            visibility: SymbolVisibility::Default,
1544            section_index: None,
1545        }
1546    }
1547
1548    fn function_analysis_binary(include_valid_function: bool) -> BinaryFile {
1549        let data = vec![0xc3];
1550        let mut sections = vec![Section {
1551            name: ".invalid".to_string(),
1552            address: 0x1000,
1553            size: 1,
1554            offset: 2,
1555            file_size: 1,
1556            permissions: SectionPermissions {
1557                read: true,
1558                write: false,
1559                execute: true,
1560            },
1561            section_type: SectionType::Code,
1562            data: None,
1563        }];
1564        let mut symbols = vec![function_symbol("invalid", 0x1000)];
1565
1566        if include_valid_function {
1567            sections.push(Section {
1568                name: ".valid".to_string(),
1569                address: 0x2000,
1570                size: 1,
1571                offset: 0,
1572                file_size: 1,
1573                permissions: SectionPermissions {
1574                    read: true,
1575                    write: false,
1576                    execute: true,
1577                },
1578                section_type: SectionType::Code,
1579                data: Some(data.clone()),
1580            });
1581            symbols.push(function_symbol("valid", 0x2000));
1582        }
1583
1584        let metadata = BinaryMetadata {
1585            size: data.len(),
1586            format: BinaryFormat::Raw,
1587            architecture: Architecture::X86_64,
1588            entry_point: None,
1589            base_address: None,
1590            timestamp: None,
1591            compiler_info: None,
1592            endian: Endianness::Little,
1593            security_features: SecurityFeatures::default(),
1594        };
1595
1596        BinaryFile {
1597            data,
1598            parsed: Box::new(TestFormat {
1599                sections,
1600                symbols,
1601                metadata,
1602            }),
1603        }
1604    }
1605
1606    fn looping_functions_binary() -> BinaryFile {
1607        // Two independent x86 short jumps, each targeting its own address.
1608        let data = vec![0xeb, 0xfe, 0xeb, 0xfe];
1609        let sections = vec![Section {
1610            name: ".text".to_string(),
1611            address: 0x1000,
1612            size: data.len() as u64,
1613            offset: 0,
1614            file_size: data.len() as u64,
1615            permissions: SectionPermissions {
1616                read: true,
1617                write: false,
1618                execute: true,
1619            },
1620            section_type: SectionType::Code,
1621            data: Some(data.clone()),
1622        }];
1623        let symbols = [0x1000, 0x1002]
1624            .into_iter()
1625            .enumerate()
1626            .map(|(index, address)| {
1627                let mut symbol = function_symbol(&format!("loop_{index}"), address);
1628                symbol.size = 2;
1629                symbol
1630            })
1631            .collect();
1632        let metadata = BinaryMetadata {
1633            size: data.len(),
1634            format: BinaryFormat::Raw,
1635            architecture: Architecture::X86_64,
1636            entry_point: None,
1637            base_address: None,
1638            timestamp: None,
1639            compiler_info: None,
1640            endian: Endianness::Little,
1641            security_features: SecurityFeatures::default(),
1642        };
1643
1644        BinaryFile {
1645            data,
1646            parsed: Box::new(TestFormat {
1647                sections,
1648                symbols,
1649                metadata,
1650            }),
1651        }
1652    }
1653
1654    #[test]
1655    fn test_analyzer_creation() {
1656        let analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1657        assert_eq!(analyzer.architecture, Architecture::X86_64);
1658    }
1659
1660    #[test]
1661    fn test_config_default() {
1662        let config = AnalysisConfig::default();
1663        assert_eq!(config.max_instructions, 10000);
1664        assert_eq!(config.max_functions, 10_000);
1665        assert_eq!(config.max_total_instructions, 1_000_000);
1666        assert_eq!(config.max_loops, 10_000);
1667        assert_eq!(config.max_total_loop_body_blocks, 1_000_000);
1668        assert!(config.detect_loops);
1669        assert!(config.calculate_metrics);
1670    }
1671
1672    #[test]
1673    fn analyze_binary_returns_error_when_every_function_fails() {
1674        let binary = function_analysis_binary(false);
1675        let analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1676
1677        let error = analyzer.analyze_binary(&binary).unwrap_err();
1678
1679        assert!(
1680            matches!(error, BinaryError::InvalidData(message) if message.contains("beyond the end of the file"))
1681        );
1682    }
1683
1684    #[test]
1685    fn analyze_binary_preserves_partial_function_success() {
1686        let binary = function_analysis_binary(true);
1687        let analyzer = ControlFlowAnalyzer::with_config(
1688            Architecture::X86_64,
1689            AnalysisConfig {
1690                max_instructions: 1,
1691                max_functions: 2,
1692                max_total_instructions: 1,
1693                ..AnalysisConfig::default()
1694            },
1695        );
1696
1697        let cfgs = analyzer.analyze_binary(&binary).unwrap();
1698
1699        assert_eq!(cfgs.len(), 1);
1700        assert_eq!(cfgs[0].function.name, "valid");
1701    }
1702
1703    #[test]
1704    fn analyze_binary_rejects_function_count_above_configured_cap() {
1705        let binary = function_analysis_binary(true);
1706        let analyzer = ControlFlowAnalyzer::with_config(
1707            Architecture::X86_64,
1708            AnalysisConfig {
1709                max_functions: 1,
1710                ..AnalysisConfig::default()
1711            },
1712        );
1713
1714        let error = analyzer.analyze_binary(&binary).unwrap_err();
1715
1716        assert!(
1717            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_functions 1"))
1718        );
1719    }
1720
1721    #[test]
1722    fn analyze_binary_rejects_total_instructions_above_configured_cap() {
1723        let binary = function_analysis_binary(true);
1724        let analyzer = ControlFlowAnalyzer::with_config(
1725            Architecture::X86_64,
1726            AnalysisConfig {
1727                max_total_instructions: 0,
1728                ..AnalysisConfig::default()
1729            },
1730        );
1731
1732        let error = analyzer.analyze_binary(&binary).unwrap_err();
1733
1734        assert!(
1735            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_total_instructions 0"))
1736        );
1737    }
1738
1739    #[test]
1740    fn analyze_binary_rejects_per_function_instruction_overflow() {
1741        let binary = function_analysis_binary(true);
1742        let analyzer = ControlFlowAnalyzer::with_config(
1743            Architecture::X86_64,
1744            AnalysisConfig {
1745                max_instructions: 0,
1746                ..AnalysisConfig::default()
1747            },
1748        );
1749
1750        let error = analyzer.analyze_binary(&binary).unwrap_err();
1751
1752        assert!(
1753            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_instructions 0"))
1754        );
1755    }
1756
1757    #[test]
1758    fn analyze_binary_rejects_aggregate_loop_count_above_limit() {
1759        let binary = looping_functions_binary();
1760        let analyzer = ControlFlowAnalyzer::with_config(
1761            Architecture::X86_64,
1762            AnalysisConfig {
1763                max_loops: 1,
1764                ..AnalysisConfig::default()
1765            },
1766        );
1767
1768        let error = analyzer.analyze_binary(&binary).unwrap_err();
1769
1770        assert!(
1771            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_loops 1"))
1772        );
1773    }
1774
1775    #[test]
1776    fn test_basic_block_creation() {
1777        let instructions = vec![
1778            Instruction {
1779                address: 0x1000,
1780                bytes: vec![0x90],
1781                mnemonic: "nop".to_string(),
1782                operands: String::new(),
1783                category: InstructionCategory::Unknown,
1784                flow: FlowType::Sequential,
1785                size: 1,
1786            },
1787            Instruction {
1788                address: 0x1001,
1789                bytes: vec![0xc3],
1790                mnemonic: "ret".to_string(),
1791                operands: String::new(),
1792                category: InstructionCategory::Control,
1793                flow: FlowType::Return,
1794                size: 1,
1795            },
1796        ];
1797
1798        let analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1799        let blocks = analyzer.build_basic_blocks(&instructions).unwrap();
1800
1801        assert_eq!(blocks.len(), 1);
1802        assert_eq!(blocks[0].instructions.len(), 2);
1803        assert_eq!(blocks[0].start_address, 0x1000);
1804        assert_eq!(blocks[0].end_address, 0x1002);
1805    }
1806
1807    fn instruction(address: u64, mnemonic: &str, flow: FlowType) -> Instruction {
1808        Instruction {
1809            address,
1810            bytes: vec![0x90],
1811            mnemonic: mnemonic.to_string(),
1812            operands: String::new(),
1813            category: InstructionCategory::Control,
1814            flow,
1815            size: 1,
1816        }
1817    }
1818
1819    #[test]
1820    fn calls_do_not_split_intra_procedural_basic_blocks() {
1821        let instructions = vec![
1822            instruction(0x1000, "call", FlowType::Call(0x2000)),
1823            instruction(0x1001, "nop", FlowType::Sequential),
1824            instruction(0x1002, "ret", FlowType::Return),
1825        ];
1826
1827        let analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1828        let blocks = analyzer.build_basic_blocks(&instructions).unwrap();
1829
1830        assert_eq!(blocks.len(), 1);
1831        assert_eq!(blocks[0].instructions.len(), 3);
1832    }
1833
1834    #[test]
1835    fn coincident_branch_and_fallthrough_edges_are_not_duplicated() {
1836        let instructions = vec![
1837            instruction(0x1000, "je", FlowType::ConditionalJump(0x1001)),
1838            instruction(0x1001, "ret", FlowType::Return),
1839        ];
1840
1841        let analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1842        let blocks = analyzer.build_basic_blocks(&instructions).unwrap();
1843
1844        assert_eq!(blocks.len(), 2);
1845        assert_eq!(blocks[0].successors, vec![1]);
1846        assert_eq!(blocks[1].predecessors, vec![0]);
1847    }
1848
1849    #[test]
1850    fn halstead_metrics_remain_finite_without_operands() {
1851        let instructions = vec![
1852            instruction(0x1000, "nop", FlowType::Sequential),
1853            instruction(0x1001, "ret", FlowType::Return),
1854        ];
1855        let mut analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1856        let mut blocks = analyzer.build_basic_blocks(&instructions).unwrap();
1857        analyzer.build_dominator_tree(&mut blocks).unwrap();
1858        let dominator_index = ControlFlowAnalyzer::build_dominator_index(&blocks).unwrap();
1859        let metrics = analyzer.calculate_complexity(&blocks, &dominator_index);
1860        let halstead = metrics.halstead_metrics.unwrap();
1861
1862        assert!(halstead.calculated_length.is_finite());
1863        assert!(halstead.volume.is_finite());
1864    }
1865
1866    #[test]
1867    fn public_cfg_algorithms_reject_out_of_range_edges() {
1868        let mut blocks = vec![BasicBlock {
1869            id: 0,
1870            start_address: 0,
1871            end_address: 1,
1872            instructions: vec![instruction(0, "jmp", FlowType::Jump(2))],
1873            successors: vec![1],
1874            predecessors: Vec::new(),
1875            block_type: BlockType::Normal,
1876            dominator: None,
1877            dominance_frontier: Vec::new(),
1878        }];
1879        let mut analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1880
1881        assert!(analyzer.build_dominator_tree(&mut blocks).is_err());
1882        assert!(analyzer.analyze_loops(&mut blocks).is_err());
1883    }
1884
1885    fn block(id: usize, successors: Vec<usize>, predecessors: Vec<usize>) -> BasicBlock {
1886        BasicBlock {
1887            id,
1888            start_address: id as u64,
1889            end_address: id as u64 + 1,
1890            instructions: vec![instruction(id as u64, "jmp", FlowType::Jump(0))],
1891            successors,
1892            predecessors,
1893            block_type: BlockType::Normal,
1894            dominator: None,
1895            dominance_frontier: Vec::new(),
1896        }
1897    }
1898
1899    #[test]
1900    fn loop_analysis_merges_latches_and_sets_nesting_level() {
1901        let mut blocks = vec![
1902            block(0, vec![1, 2], vec![1, 2]),
1903            block(1, vec![0], vec![0]),
1904            block(2, vec![0], vec![0]),
1905        ];
1906        let mut analyzer = ControlFlowAnalyzer::new(Architecture::X86_64);
1907
1908        let loops = analyzer.analyze_loops(&mut blocks).unwrap();
1909
1910        assert_eq!(loops.len(), 1);
1911        assert_eq!(loops[0].header_block, 0);
1912        assert_eq!(loops[0].body_blocks, vec![1, 2]);
1913        assert_eq!(loops[0].nesting_level, 1);
1914        assert_eq!(loops[0].loop_type, LoopType::Infinite);
1915    }
1916
1917    #[test]
1918    fn loop_analysis_accepts_exact_aggregate_body_limit() {
1919        let mut blocks = vec![
1920            block(0, vec![1, 2], vec![1, 2]),
1921            block(1, vec![0], vec![0]),
1922            block(2, vec![0], vec![0]),
1923        ];
1924        let mut analyzer = ControlFlowAnalyzer::with_config(
1925            Architecture::X86_64,
1926            AnalysisConfig {
1927                max_loops: 1,
1928                max_total_loop_body_blocks: 2,
1929                ..AnalysisConfig::default()
1930            },
1931        );
1932
1933        let loops = analyzer.analyze_loops(&mut blocks).unwrap();
1934
1935        assert_eq!(loops.len(), 1);
1936        assert_eq!(loops[0].body_blocks, vec![1, 2]);
1937    }
1938
1939    #[test]
1940    fn loop_analysis_rejects_aggregate_body_membership_above_limit() {
1941        let mut blocks = vec![
1942            block(0, vec![1, 2], vec![1, 2]),
1943            block(1, vec![0], vec![0]),
1944            block(2, vec![0], vec![0]),
1945        ];
1946        let mut analyzer = ControlFlowAnalyzer::with_config(
1947            Architecture::X86_64,
1948            AnalysisConfig {
1949                max_total_loop_body_blocks: 1,
1950                ..AnalysisConfig::default()
1951            },
1952        );
1953
1954        let error = analyzer.analyze_loops(&mut blocks).unwrap_err();
1955
1956        assert!(
1957            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_total_loop_body_blocks 1"))
1958        );
1959    }
1960
1961    #[test]
1962    fn loop_analysis_rejects_loop_count_above_limit() {
1963        let mut blocks = vec![
1964            block(0, vec![1], Vec::new()),
1965            block(1, vec![1, 2], vec![0, 1]),
1966            block(2, vec![2], vec![1, 2]),
1967        ];
1968        let mut analyzer = ControlFlowAnalyzer::with_config(
1969            Architecture::X86_64,
1970            AnalysisConfig {
1971                max_loops: 1,
1972                ..AnalysisConfig::default()
1973            },
1974        );
1975
1976        let error = analyzer.analyze_loops(&mut blocks).unwrap_err();
1977
1978        assert!(
1979            matches!(error, BinaryError::ControlFlowError(message) if message.contains("max_loops 1"))
1980        );
1981    }
1982
1983    #[test]
1984    fn dominator_index_handles_a_long_chain_in_linear_space() {
1985        const BLOCK_COUNT: usize = 20_000;
1986        let mut blocks = Vec::new();
1987        blocks.try_reserve(BLOCK_COUNT).unwrap();
1988        for id in 0..BLOCK_COUNT {
1989            let mut basic_block = block(id, Vec::new(), Vec::new());
1990            basic_block.dominator = Some(id.saturating_sub(1));
1991            if id + 1 < BLOCK_COUNT {
1992                // Only the successor count is relevant to nesting depth here.
1993                basic_block.successors = vec![id + 1, id + 1];
1994            }
1995            blocks.push(basic_block);
1996        }
1997
1998        let index = ControlFlowAnalyzer::build_dominator_index(&blocks).unwrap();
1999
2000        assert_eq!(index.nesting_depth[0], 0);
2001        assert_eq!(index.nesting_depth[BLOCK_COUNT - 1], 19_999);
2002        assert!(index.dominates(0, BLOCK_COUNT - 1));
2003        assert!(!index.dominates(BLOCK_COUNT - 1, 0));
2004    }
2005
2006    #[test]
2007    fn adding_high_fan_in_edges_keeps_each_predecessor_unique() {
2008        const SOURCE_COUNT: usize = 20_000;
2009        let target = SOURCE_COUNT;
2010        let mut blocks: Vec<_> = (0..=SOURCE_COUNT)
2011            .map(|id| block(id, Vec::new(), Vec::new()))
2012            .collect();
2013
2014        for source in 0..SOURCE_COUNT {
2015            ControlFlowAnalyzer::add_cfg_edge(&mut blocks, source, target);
2016            ControlFlowAnalyzer::add_cfg_edge(&mut blocks, source, target);
2017        }
2018
2019        assert_eq!(blocks[target].predecessors.len(), SOURCE_COUNT);
2020        assert_eq!(blocks[target].predecessors[0], 0);
2021        assert_eq!(
2022            blocks[target].predecessors[SOURCE_COUNT - 1],
2023            SOURCE_COUNT - 1
2024        );
2025    }
2026}