Files
hakorune/src/mir/cfg_extractor.rs

176 lines
5.3 KiB
Rust
Raw Normal View History

feat(hako_check): Phase 154 MIR CFG integration & HC020 dead block detection Implements block-level unreachable code detection using MIR CFG information. Complements Phase 153's method-level HC019 with fine-grained analysis. Core Infrastructure (Complete): - CFG Extractor: Extract block reachability from MirModule - DeadBlockAnalyzerBox: HC020 rule for unreachable blocks - CLI Integration: --dead-blocks flag and rule execution - Test Cases: 4 comprehensive patterns (early return, constant false, infinite loop, break) - Smoke Test: Validation script for all test cases Implementation Details: - src/mir/cfg_extractor.rs: New module for CFG→JSON extraction - tools/hako_check/rules/rule_dead_blocks.hako: HC020 analyzer box - tools/hako_check/cli.hako: Added --dead-blocks flag and HC020 integration - apps/tests/hako_check/test_dead_blocks_*.hako: 4 test cases Architecture: - Follows Phase 153 boxed modular pattern (DeadCodeAnalyzerBox) - Optional CFG field in Analysis IR (backward compatible) - Uses MIR's built-in reachability computation - Gracefully skips if CFG unavailable Known Limitation: - CFG data bridge pending (Phase 155): analysis_consumer.hako needs MIR access - Current: DeadBlockAnalyzerBox implemented, but CFG not yet in Analysis IR - Estimated 2-3 hours to complete bridge in Phase 155 Test Coverage: - Unit tests: cfg_extractor (simple CFG, unreachable blocks) - Integration tests: 4 test cases ready (will activate with bridge) - Smoke test: tools/hako_check_deadblocks_smoke.sh Documentation: - phase154_mir_cfg_inventory.md: CFG structure investigation - phase154_implementation_summary.md: Complete implementation guide - hako_check_design.md: HC020 rule documentation Next Phase 155: - Implement CFG data bridge (extract_mir_cfg builtin) - Update analysis_consumer.hako to call bridge - Activate HC020 end-to-end testing 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:00:45 +09:00
/*!
* MIR CFG Extractor - Extract Control Flow Graph information for analysis
*
* Phase 154: Provides CFG data to hako_check for dead block detection
*/
use super::{MirFunction, MirInstruction, MirModule};
use serde_json::{json, Value};
/// Extract CFG information from MIR module as JSON
///
/// Output format:
/// ```json
/// {
/// "functions": [
/// {
/// "name": "Main.main/0",
/// "entry_block": 0,
/// "blocks": [
/// {
/// "id": 0,
/// "reachable": true,
/// "successors": [1, 2],
/// "terminator": "Branch"
/// }
/// ]
/// }
/// ]
/// }
/// ```
pub fn extract_cfg_info(module: &MirModule) -> Value {
let mut functions = Vec::new();
for (_func_id, function) in &module.functions {
functions.push(extract_function_cfg(function));
}
json!({
"functions": functions
})
}
/// Extract CFG info for a single function
fn extract_function_cfg(function: &MirFunction) -> Value {
let mut blocks = Vec::new();
for (block_id, block) in &function.blocks {
// Extract successor IDs
let successors: Vec<u32> = block.successors.iter().map(|id| id.0).collect();
// Determine terminator type
let terminator_name = match &block.terminator {
Some(inst) => terminator_to_string(inst),
None => "None".to_string(),
};
blocks.push(json!({
"id": block_id.0,
"reachable": block.reachable,
"successors": successors,
"terminator": terminator_name
}));
}
// Sort blocks by ID for deterministic output
blocks.sort_by_key(|b| b["id"].as_u64().unwrap_or(0));
json!({
"name": function.signature.name,
"entry_block": function.entry_block.0,
"blocks": blocks
})
}
/// Convert terminator instruction to string name
fn terminator_to_string(inst: &MirInstruction) -> String {
match inst {
MirInstruction::Branch { .. } => "Branch".to_string(),
MirInstruction::Jump { .. } => "Jump".to_string(),
MirInstruction::Return { .. } => "Return".to_string(),
_ => "Unknown".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirModule, FunctionSignature, MirType, EffectMask};
feat(hako_check): Phase 154 MIR CFG integration & HC020 dead block detection Implements block-level unreachable code detection using MIR CFG information. Complements Phase 153's method-level HC019 with fine-grained analysis. Core Infrastructure (Complete): - CFG Extractor: Extract block reachability from MirModule - DeadBlockAnalyzerBox: HC020 rule for unreachable blocks - CLI Integration: --dead-blocks flag and rule execution - Test Cases: 4 comprehensive patterns (early return, constant false, infinite loop, break) - Smoke Test: Validation script for all test cases Implementation Details: - src/mir/cfg_extractor.rs: New module for CFG→JSON extraction - tools/hako_check/rules/rule_dead_blocks.hako: HC020 analyzer box - tools/hako_check/cli.hako: Added --dead-blocks flag and HC020 integration - apps/tests/hako_check/test_dead_blocks_*.hako: 4 test cases Architecture: - Follows Phase 153 boxed modular pattern (DeadCodeAnalyzerBox) - Optional CFG field in Analysis IR (backward compatible) - Uses MIR's built-in reachability computation - Gracefully skips if CFG unavailable Known Limitation: - CFG data bridge pending (Phase 155): analysis_consumer.hako needs MIR access - Current: DeadBlockAnalyzerBox implemented, but CFG not yet in Analysis IR - Estimated 2-3 hours to complete bridge in Phase 155 Test Coverage: - Unit tests: cfg_extractor (simple CFG, unreachable blocks) - Integration tests: 4 test cases ready (will activate with bridge) - Smoke test: tools/hako_check_deadblocks_smoke.sh Documentation: - phase154_mir_cfg_inventory.md: CFG structure investigation - phase154_implementation_summary.md: Complete implementation guide - hako_check_design.md: HC020 rule documentation Next Phase 155: - Implement CFG data bridge (extract_mir_cfg builtin) - Update analysis_consumer.hako to call bridge - Activate HC020 end-to-end testing 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:00:45 +09:00
use std::collections::BTreeMap;
#[test]
fn test_extract_simple_cfg() {
let mut module = MirModule::new("test");
// Create simple function with 2 blocks
let signature = FunctionSignature {
name: "test_fn".to_string(),
params: vec![],
return_type: MirType::Void,
effects: EffectMask::empty(),
};
let mut function = MirFunction::new(signature, BasicBlockId(0));
feat(hako_check): Phase 154 MIR CFG integration & HC020 dead block detection Implements block-level unreachable code detection using MIR CFG information. Complements Phase 153's method-level HC019 with fine-grained analysis. Core Infrastructure (Complete): - CFG Extractor: Extract block reachability from MirModule - DeadBlockAnalyzerBox: HC020 rule for unreachable blocks - CLI Integration: --dead-blocks flag and rule execution - Test Cases: 4 comprehensive patterns (early return, constant false, infinite loop, break) - Smoke Test: Validation script for all test cases Implementation Details: - src/mir/cfg_extractor.rs: New module for CFG→JSON extraction - tools/hako_check/rules/rule_dead_blocks.hako: HC020 analyzer box - tools/hako_check/cli.hako: Added --dead-blocks flag and HC020 integration - apps/tests/hako_check/test_dead_blocks_*.hako: 4 test cases Architecture: - Follows Phase 153 boxed modular pattern (DeadCodeAnalyzerBox) - Optional CFG field in Analysis IR (backward compatible) - Uses MIR's built-in reachability computation - Gracefully skips if CFG unavailable Known Limitation: - CFG data bridge pending (Phase 155): analysis_consumer.hako needs MIR access - Current: DeadBlockAnalyzerBox implemented, but CFG not yet in Analysis IR - Estimated 2-3 hours to complete bridge in Phase 155 Test Coverage: - Unit tests: cfg_extractor (simple CFG, unreachable blocks) - Integration tests: 4 test cases ready (will activate with bridge) - Smoke test: tools/hako_check_deadblocks_smoke.sh Documentation: - phase154_mir_cfg_inventory.md: CFG structure investigation - phase154_implementation_summary.md: Complete implementation guide - hako_check_design.md: HC020 rule documentation Next Phase 155: - Implement CFG data bridge (extract_mir_cfg builtin) - Update analysis_consumer.hako to call bridge - Activate HC020 end-to-end testing 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:00:45 +09:00
let mut block0 = BasicBlock::new(BasicBlockId(0));
block0.reachable = true;
block0.successors.insert(BasicBlockId(1));
block0.terminator = Some(MirInstruction::Jump {
target: BasicBlockId(1),
});
let mut block1 = BasicBlock::new(BasicBlockId(1));
block1.reachable = true;
block1.terminator = Some(MirInstruction::Return { value: None });
function.blocks.insert(BasicBlockId(0), block0);
function.blocks.insert(BasicBlockId(1), block1);
module.functions.insert("test_fn".to_string(), function);
// Extract CFG
let cfg = extract_cfg_info(&module);
// Verify structure
assert!(cfg["functions"].is_array());
let functions = cfg["functions"].as_array().unwrap();
assert_eq!(functions.len(), 1);
let func = &functions[0];
assert_eq!(func["name"], "test_fn");
assert_eq!(func["entry_block"], 0);
let blocks = func["blocks"].as_array().unwrap();
assert_eq!(blocks.len(), 2);
// Check block 0
assert_eq!(blocks[0]["id"], 0);
assert_eq!(blocks[0]["reachable"], true);
assert_eq!(blocks[0]["terminator"], "Jump");
assert_eq!(blocks[0]["successors"].as_array().unwrap(), &[json!(1)]);
// Check block 1
assert_eq!(blocks[1]["id"], 1);
assert_eq!(blocks[1]["reachable"], true);
assert_eq!(blocks[1]["terminator"], "Return");
}
#[test]
fn test_unreachable_block() {
let mut module = MirModule::new("test");
let mut function = MirFunction::new(MirSignature::new("test_dead".to_string()));
function.entry_block = BasicBlockId(0);
let mut block0 = BasicBlock::new(BasicBlockId(0));
block0.reachable = true;
block0.terminator = Some(MirInstruction::Return { value: None });
// Unreachable block
let mut block1 = BasicBlock::new(BasicBlockId(1));
block1.reachable = false; // Marked as unreachable
block1.terminator = Some(MirInstruction::Return { value: None });
function.blocks.insert(BasicBlockId(0), block0);
function.blocks.insert(BasicBlockId(1), block1);
module.functions.insert("test_dead".to_string(), function);
let cfg = extract_cfg_info(&module);
let blocks = cfg["functions"][0]["blocks"].as_array().unwrap();
// Find unreachable block
let dead_block = blocks.iter().find(|b| b["id"] == 1).unwrap();
assert_eq!(dead_block["reachable"], false);
}
}