use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::Path;
pub mod clang_invoker;
pub mod llvm_ir_parser;
use clang_invoker::{compile_to_llvm_ir_temp, ClangCompilationError};
pub fn extract_cfg_from_cpp(
source_path: &Path,
) -> Result<crate::graph::cfg_edges_extract::CfgWithEdges> {
let ll_path =
compile_to_llvm_ir_temp(source_path).context("Failed to compile C/C++ to LLVM IR")?;
let ll_content = std::fs::read_to_string(&ll_path).context("Failed to read LLVM IR file")?;
let function_cfgs = llvm_ir_parser::extract_cfg_from_llvm_ir(&ll_content)
.context("Failed to parse CFG from LLVM IR")?;
let merged_cfg = merge_function_cfgs(function_cfgs);
let _ = std::fs::remove_file(&ll_path);
Ok(merged_cfg)
}
pub fn extract_cfg_for_function(
source_path: &Path,
function_name: &str,
) -> Result<crate::graph::cfg_edges_extract::CfgWithEdges> {
let ll_path =
compile_to_llvm_ir_temp(source_path).context("Failed to compile C/C++ to LLVM IR")?;
let ll_content = std::fs::read_to_string(&ll_path).context("Failed to read LLVM IR file")?;
let cfg = llvm_ir_parser::extract_cfg_for_function(&ll_content, function_name)
.context("Failed to parse CFG from LLVM IR")?;
let _ = std::fs::remove_file(&ll_path);
Ok(cfg)
}
fn merge_function_cfgs(
function_cfgs: HashMap<String, crate::graph::cfg_edges_extract::CfgWithEdges>,
) -> crate::graph::cfg_edges_extract::CfgWithEdges {
use crate::graph::cfg_edges_extract::CfgWithEdges;
let mut all_blocks = Vec::new();
let mut all_edges = Vec::new();
let mut block_id_offset = 0i64;
for (_func_name, mut cfg) in function_cfgs {
for edge in &mut cfg.edges {
edge.source_idx += block_id_offset as usize;
edge.target_idx += block_id_offset as usize;
}
block_id_offset += cfg.blocks.len() as i64;
all_blocks.extend(cfg.blocks);
all_edges.extend(cfg.edges);
}
CfgWithEdges {
blocks: all_blocks,
edges: all_edges,
function_id: 0,
}
}
pub fn is_clang_available() -> bool {
crate::graph::external_tools::tool_detector::is_tool_available("clang")
}
pub fn get_clang_version() -> Option<String> {
match crate::graph::external_tools::tool_detector::check_clang_version() {
Ok(version) => Some(version),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_is_clang_available() {
let _ = is_clang_available();
}
#[test]
fn test_extract_cfg_simple_c() {
if !is_clang_available() {
return;
}
let source = r#"
int foo(int x) {
if (x > 0) {
return x * 2;
} else {
return x + 1;
}
}
int bar(int x) {
return x + 42;
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".c").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_from_cpp(source_path);
if let Err(e) = &result {
if e.to_string().contains("clang") {
return; }
}
assert!(result.is_ok());
let cfg = result.unwrap();
assert!(!cfg.blocks.is_empty());
assert!(!cfg.edges.is_empty());
}
#[test]
fn test_extract_cfg_for_function() {
if !is_clang_available() {
return;
}
let source = r#"
int foo(int x) {
if (x > 0) {
return x * 2;
} else {
return x + 1;
}
}
int bar(int x) {
return x + 42;
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".c").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_for_function(source_path, "foo");
if let Err(e) = &result {
if e.to_string().contains("clang") {
return; }
}
assert!(result.is_ok());
let cfg = result.unwrap();
assert!(!cfg.blocks.is_empty());
assert!(!cfg.edges.is_empty());
}
#[test]
fn test_extract_cfg_function_not_found() {
if !is_clang_available() {
return;
}
let source = r#"
int foo(int x) {
return x;
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".c").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_for_function(source_path, "nonexistent");
if let Err(e) = &result {
if e.to_string().contains("clang") {
return; }
}
assert!(result.is_err());
}
}