use std::fs;
use std::process::Command;
use cure_engine::Config;
use cure_java_parser::parse;
use cure_java_print::print_unit;
use cure_java_simplify::simplify_unit;
fn javac_available() -> bool {
Command::new("javac")
.arg("-version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn run_case(name: &str, src: &str) -> (String, usize) {
let mut outcome = parse(src);
assert!(
outcome.errors.is_empty(),
"{name}: 源码解析失败:{:?}",
outcome.errors
);
let report = simplify_unit(&mut outcome.ast, &mut outcome.unit, &Config::default());
let cleaned = print_unit(&outcome.ast, &outcome.unit);
if javac_available() {
let base = std::env::temp_dir().join(format!("cure_cff_{name}"));
let _ = fs::remove_dir_all(&base);
let orig_dir = base.join("orig");
let clean_dir = base.join("clean");
fs::create_dir_all(&orig_dir).unwrap();
fs::create_dir_all(&clean_dir).unwrap();
let cls = format!("{name}C");
fs::write(orig_dir.join(format!("{cls}.java")), src).unwrap();
fs::write(clean_dir.join(format!("{cls}.java")), &cleaned).unwrap();
for d in [&orig_dir, &clean_dir] {
let out = Command::new("javac")
.arg("-encoding")
.arg("UTF-8")
.arg("-nowarn")
.arg("-d")
.arg(d)
.arg(d.join(format!("{cls}.java")))
.output()
.unwrap();
assert!(
out.status.success(),
"{name}: javac 失败\n{}\n== 净化输出 ==\n{cleaned}",
String::from_utf8_lossy(&out.stderr)
);
}
let run = |d: &std::path::Path| {
Command::new("java")
.arg("-cp")
.arg(d)
.arg(&cls)
.output()
.unwrap()
};
let (a, b) = (run(&orig_dir), run(&clean_dir));
assert_eq!(a.status.code(), b.status.code(), "{name}: 退出码不一致");
assert_eq!(
String::from_utf8_lossy(&a.stdout),
String::from_utf8_lossy(&b.stdout),
"{name}: CFF 还原改变行为!\n== 净化输出 ==\n{cleaned}"
);
}
(cleaned, report.edits)
}
#[test]
fn cff_linear_chain() {
let src = r#"
public class LinearC {
static int total;
static int result;
public static void main(String[] args) {
total = 0;
result = 0;
int s = 0;
while (true) {
switch (s) {
case 0: {
total = 10;
s = 1;
break;
}
case 1: {
total += 5;
s = 2;
break;
}
case 2: {
result = total * 2;
s = 7;
break;
}
}
if (s == 7) {
break;
}
}
System.out.println("r=" + result);
}
}
"#;
let (cleaned, edits) = run_case("Linear", src);
assert!(edits >= 1, "CFF 未被还原:\n{cleaned}");
assert!(cleaned.contains("total = 10;"), "{cleaned}");
assert!(cleaned.contains("total += 5;"), "{cleaned}");
assert!(cleaned.contains("result = total * 2;"), "{cleaned}");
assert!(!cleaned.contains("switch"), "{cleaned}");
assert!(!cleaned.contains("int s"), "{cleaned}");
}
#[test]
fn cff_diamond() {
let src = r#"
public class DiamondC {
public static void main(String[] args) {
int x = 3;
String y = null;
String out = null;
int s = 0;
while (true) {
switch (s) {
case 0: {
x = x * 7;
s = 1;
break;
}
case 1: {
if (x > 10) {
s = 2;
} else {
s = 3;
}
break;
}
case 2: {
y = "pos";
s = 4;
break;
}
case 3: {
y = "neg";
s = 4;
break;
}
case 4: {
out = y + "!";
s = 9;
break;
}
}
if (s == 9) {
break;
}
}
System.out.println("out=" + out);
}
}
"#;
let (cleaned, edits) = run_case("Diamond", src);
assert!(edits >= 1, "CFF 未被还原:\n{cleaned}");
assert!(cleaned.contains(r#"println("out=pos!")"#), "{cleaned}");
assert!(!cleaned.contains("switch"), "{cleaned}");
assert!(!cleaned.contains("int s"), "{cleaned}");
assert!(!cleaned.contains("x"), "{cleaned}");
}
#[test]
fn cff_loop_shape() {
let src = r#"
public class LoopC {
public static void main(String[] args) {
int i = 0;
int acc = 0;
int s = 0;
while (true) {
switch (s) {
case 0: {
if (i < 5) {
s = 1;
} else {
s = 2;
}
break;
}
case 1: {
acc += i;
i++;
s = 0;
break;
}
case 2: {
s = 8;
break;
}
}
if (s == 8) {
break;
}
}
System.out.println("acc=" + acc);
}
}
"#;
let (cleaned, edits) = run_case("Loop", src);
assert!(edits >= 1, "CFF 未被还原:\n{cleaned}");
assert!(cleaned.contains(r#"println("acc=10")"#), "{cleaned}");
assert!(!cleaned.contains("switch"), "{cleaned}");
assert!(!cleaned.contains("s = 8"), "{cleaned}");
}
#[test]
fn cff_default_return_exit() {
let src = r#"
public class DefaultC {
public static void main(String[] args) {
int v = 3;
int s = 0;
while (true) {
switch (s) {
case 0: {
v = v + 9;
s = 1;
break;
}
case 1: {
System.out.println("v=" + v);
s = 99;
break;
}
default: {
return;
}
}
}
}
}
"#;
let (cleaned, edits) = run_case("Default", src);
assert!(edits >= 1, "CFF 未被还原:\n{cleaned}");
assert!(cleaned.contains(r#"println("v=12")"#), "{cleaned}");
assert!(!cleaned.contains("switch"), "{cleaned}");
assert!(!cleaned.contains("v = v"), "{cleaned}");
}
#[test]
fn cff_negative_var_used_after() {
let src = r#"
public class NegC {
public static void main(String[] args) {
int s = 0;
int x = 0;
while (true) {
switch (s) {
case 0: {
x = 5;
s = 1;
break;
}
case 1: {
s = 2;
break;
}
}
if (s == 2) {
break;
}
}
System.out.println("s=" + s + " x=" + x);
}
}
"#;
let mut outcome = parse(src);
assert!(outcome.errors.is_empty());
let before = print_unit(&outcome.ast, &outcome.unit);
simplify_unit(&mut outcome.ast, &mut outcome.unit, &Config::default());
let after = print_unit(&outcome.ast, &outcome.unit);
assert!(after.contains(r#"println("s=2 x=5")"#), "值必须一致:\n{after}");
let _ = before;
}
#[test]
fn cff_unreachable_case_dropped_as_dead_code() {
let src = r#"
public class UnrC {
public static void main(String[] args) {
int s = 0;
int x = 0;
while (true) {
switch (s) {
case 0: {
x = 5;
s = 9;
break;
}
case 5: {
x = 6;
s = 9;
break;
}
}
if (s == 9) {
break;
}
}
System.out.println("x=" + x);
}
}
"#;
let mut outcome = parse(src);
assert!(outcome.errors.is_empty());
let _report = simplify_unit(&mut outcome.ast, &mut outcome.unit, &Config::default());
let after = print_unit(&outcome.ast, &outcome.unit);
assert!(after.contains(r#"println("x=5")"#), "{after}");
assert!(!after.contains("x = 6;"), "死 case 体必须消失:\n{after}");
assert!(!after.contains("switch"), "{after}");
}