use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::Path;
pub mod class_parser;
pub mod javac_invoker;
use javac_invoker::{compile_to_class_temp, JavacCompilationError};
pub fn extract_cfg_from_java(
source_path: &Path,
) -> Result<crate::graph::cfg_edges_extract::CfgWithEdges> {
let class_path =
compile_to_class_temp(source_path).context("Failed to compile Java to bytecode")?;
let class_bytes = std::fs::read(&class_path).context("Failed to read .class file")?;
let method_cfgs = class_parser::extract_cfg_from_class(&class_bytes)
.context("Failed to parse CFG from bytecode")?;
let merged_cfg = merge_method_cfgs(method_cfgs);
let _ = std::fs::remove_file(&class_path);
Ok(merged_cfg)
}
pub fn extract_cfg_for_method(
source_path: &Path,
method_name: &str,
) -> Result<crate::graph::cfg_edges_extract::CfgWithEdges> {
let class_path =
compile_to_class_temp(source_path).context("Failed to compile Java to bytecode")?;
let class_bytes = std::fs::read(&class_path).context("Failed to read .class file")?;
let cfg = class_parser::extract_cfg_for_method(&class_bytes, method_name)
.context("Failed to parse CFG from bytecode")?;
let _ = std::fs::remove_file(&class_path);
Ok(cfg)
}
fn merge_method_cfgs(
method_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 (_method_name, mut cfg) in method_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_javac_available() -> bool {
crate::graph::external_tools::tool_detector::is_tool_available("javac")
}
pub fn get_javac_version() -> Option<String> {
match crate::graph::external_tools::tool_detector::check_javac_version() {
Ok(version) => Some(version),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_is_javac_available() {
let _ = is_javac_available();
}
#[test]
fn test_extract_cfg_simple_java() {
if !is_javac_available() {
return;
}
let source = r#"
public class Test {
public static int foo(int x) {
if (x > 0) {
return x * 2;
} else {
return x + 1;
}
}
public static int bar(int x) {
return x + 42;
}
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".java").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_from_java(source_path);
if let Err(e) = &result {
if e.to_string().contains("javac") {
return; }
}
assert!(result.is_ok());
let cfg = result.unwrap();
assert!(!cfg.blocks.is_empty());
}
#[test]
fn test_extract_cfg_for_method() {
if !is_javac_available() {
return;
}
let source = r#"
public class Test {
public static int foo(int x) {
if (x > 0) {
return x * 2;
} else {
return x + 1;
}
}
public static int bar(int x) {
return x + 42;
}
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".java").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_for_method(source_path, "foo");
if let Err(e) = &result {
if e.to_string().contains("javac") {
return; }
}
assert!(result.is_ok());
let cfg = result.unwrap();
assert!(!cfg.blocks.is_empty());
}
#[test]
fn test_extract_cfg_method_not_found() {
if !is_javac_available() {
return;
}
let source = r#"
public class Test {
public static int foo(int x) {
return x;
}
}
"#;
let mut temp_file = NamedTempFile::with_suffix(".java").unwrap();
temp_file.write_all(source.as_bytes()).unwrap();
let source_path = temp_file.path();
let result = extract_cfg_for_method(source_path, "nonexistent");
if let Err(e) = &result {
if e.to_string().contains("javac") {
return; }
}
assert!(result.is_err());
}
}