use std::collections::HashSet;
use std::ops::Range;
use crate::c::{c_one, c_one_marked, find_comment_outside_str};
use crate::diffc::diff_c;
use crate::dump::dump_tree;
use crate::image::{Image, Segment};
use crate::locations::Location;
use crate::runtime::RuntimeMap;
use crate::simplify::simplify_linear;
use crate::slice::slice_fn;
use crate::slice_parse::{collect_stack_ptr_vars, decl_ident, pure_decl_ident, stmt_def_use, target_hints_for_ip};
fn make_img(code: &[u8], f_range: Range<u64>) -> Image {
let mut data = vec![0x90u8; 0x4000];
let offset = usize::try_from(f_range.start.saturating_sub(0x1000)).unwrap_or_default();
let len = code.len();
if offset + len <= data.len() {
data[offset..offset + len].copy_from_slice(code);
}
Image {
data,
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x4000 }],
functions: vec![f_range],
pointers: Vec::new(),
imports: Vec::new(),
symbols: Vec::new(),
metadata: crate::image_format::Metadata::default(),
}
}
#[test]
fn simplify_and_c_escapes_coverage() {
assert_eq!(simplify_linear("rax * -1 + 4"), "-rax + 4");
assert_eq!(simplify_linear("rax + -1"), "rax + -1");
assert_eq!(simplify_linear("rax - -1"), "rax - -1");
assert_eq!(simplify_linear("(rax << -1) + 2"), "(rax << -1) + 2");
assert_eq!(simplify_linear("0 * (rax + rbx)"), "0");
assert_eq!(simplify_linear("0 * rax + 5"), "5");
assert_eq!(simplify_linear("2 * (rax + rax)"), "rax * 4");
let commented = r#""escaped \\ \" str" // comment"#;
assert_eq!(find_comment_outside_str(commented), Some(20));
assert_eq!(find_comment_outside_str(r#""no comment""#), None);
}
#[test]
fn slice_parse_coverage() {
assert_eq!(pure_decl_ident("goto L_1000;"), None);
assert_eq!(pure_decl_ident("break;"), None);
assert_eq!(pure_decl_ident("continue;"), None);
assert_eq!(pure_decl_ident("return 0;"), None);
assert_eq!(pure_decl_ident("u64 var_x;"), Some("var_x".to_owned()));
assert_eq!(decl_ident("u64 var_y;"), Some("var_y".to_owned()));
let empty_set = HashSet::new();
let (d_none, u_none) = stmt_def_use("if (x > 0", &empty_set);
assert!(d_none.is_empty() && !u_none.is_empty());
let (d_empty, u_empty) = stmt_def_use(" /* only comment */ ", &empty_set);
assert!(d_empty.is_empty() && u_empty.is_empty());
let ptr_lines = [" ptr_v = &slot_10;"];
let ptr_vars = collect_stack_ptr_vars(&ptr_lines);
assert!(ptr_vars.contains("ptr_v"));
let (call_defs, call_uses) = stmt_def_use("fn_sub(&slot_20, ptr_v);", &ptr_vars);
assert!(call_defs.contains(&"slot_20".to_owned()));
assert!(call_defs.contains(&"slot_28".to_owned()));
assert!(call_defs.contains(&"slot_30".to_owned()));
assert!(call_defs.contains(&"ptr_v".to_owned()));
assert!(call_uses.contains(&"ptr_v".to_owned()));
let code: &[u8] = &[
0x48, 0x8b, 0x05, 0x09, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x0d, 0x42, 0x00, 0x00, 0x00, 0xc3,
];
let mut img = make_img(code, 0x1000..0x100f);
let p_off = 0x50usize;
img.data[p_off..p_off + 11].copy_from_slice(b"sample_str\0");
let locs = vec![Location { place: 0x1010, file: "src/sample.rs".to_owned(), line: 123, col: 1 }];
let hints = target_hints_for_ip(&img, &locs, &(0x1000..0x100f), 0x1000);
assert!(hints.iter().any(|h| h.contains("sample.rs:123")), "{hints:?}");
assert!(hints.iter().any(|h| h.contains("sample_str")), "{hints:?}");
}
#[test]
fn dump_tree_and_sanitize_coverage() {
let code: &[u8] = &[0x48, 0x8b, 0x05, 0x10, 0x00, 0x00, 0x00, 0xc3]; let mut data = vec![0x90u8; 0x4000];
data[..code.len()].copy_from_slice(code);
data[0x1f..0x27].copy_from_slice(&17u64.to_le_bytes());
data[0x27..0x2b].copy_from_slice(&10u32.to_le_bytes());
data[0x2b..0x2f].copy_from_slice(&1u32.to_le_bytes());
data[0x100..0x111].copy_from_slice(b"nested/sub/mod.rs");
let image = Image {
data,
segments: vec![Segment { va: 0x1000, offset: 0, size: 0x4000 }],
functions: std::iter::once(0x1000..0x1008).collect(),
pointers: vec![(0x1017, 0x1100)],
imports: Vec::new(),
symbols: vec![(0x1000, "demo_func".to_owned())],
metadata: crate::image_format::Metadata::default(),
};
let tmp = std::env::temp_dir().join(format!("xref_test_dump_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&tmp);
let mut out_filtered = Vec::new();
assert!(dump_tree(&image, &tmp, Some("nonexistent_pat"), false, &mut out_filtered).is_ok());
let mut out_all = Vec::new();
assert!(dump_tree(&image, &tmp, None, false, &mut out_all).is_ok());
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn diffc_coverage() {
let code_slot_m: &[u8] = &[
0xb8, 0x78, 0x56, 0x34, 0x12, 0x48, 0x89, 0x44, 0x24, 0xf0, 0xc3, ];
let len_slot = u64::try_from(code_slot_m.len()).unwrap_or_default();
let mut img1 = make_img(code_slot_m, 0x1000..0x1000 + len_slot);
img1.symbols = vec![(0x1000, "function".to_owned())];
let mut img2 = make_img(code_slot_m, 0x1000..0x1000 + len_slot);
img2.symbols = vec![(0x1000, "function".to_owned())];
let mut out = Vec::new();
assert!(diff_c(&img1, &img2, &["0x1000".to_owned()], None, false, &mut out).is_ok());
let s_ident = String::from_utf8_lossy(&out);
assert!(s_ident.contains("identical normalized pseudo-C"), "{s_ident}");
let img_empty = Image {
data: vec![0xccu8; 0x100],
segments: vec![Segment { va: 0x2000, offset: 0, size: 0x100 }],
functions: Vec::new(),
pointers: Vec::new(),
imports: Vec::new(),
symbols: Vec::new(),
metadata: crate::image_format::Metadata::default(),
};
out.clear();
assert!(diff_c(&img1, &img_empty, &["0x1000".to_owned()], None, false, &mut out).is_ok());
let s_no_match = String::from_utf8_lossy(&out);
assert!(s_no_match.contains("no counterpart found in --other"), "{s_no_match}");
let mut seq1 = Vec::new();
for i in 0..10u8 {
seq1.extend([0xc6, 0x44, 0x24, 0x10 + i, i]); }
seq1.extend([0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3]);
let mut seq2 = Vec::new();
for i in 0..10u8 {
seq2.extend([0xc6, 0x44, 0x24, 0x10 + i, i]); }
seq2.extend([0xb8, 0x02, 0x00, 0x00, 0x00, 0xc3]);
let img_ctx1 = make_img(&seq1, 0x1000..0x1000 + u64::try_from(seq1.len()).unwrap_or_default());
let img_ctx2 = make_img(&seq2, 0x1000..0x1000 + u64::try_from(seq2.len()).unwrap_or_default());
out.clear();
assert!(diff_c(&img_ctx1, &img_ctx2, &["0x1000".to_owned()], Some("0x1000"), false, &mut out).is_ok());
let s_ctx = String::from_utf8_lossy(&out);
assert!(s_ctx.contains(" ..."), "{s_ctx}");
let mut big_seq1 = [0xff, 0xc7].repeat(1600); big_seq1.push(0xc3);
let mut big_seq2 = [0xff, 0xc7].repeat(1600);
big_seq2.push(0xc3);
let big_len = u64::try_from(big_seq1.len()).unwrap_or_default();
let big1 = make_img(&big_seq1, 0x1000..0x1000 + big_len);
let big2 = make_img(&big_seq2, 0x1000..0x1000 + big_len);
out.clear();
assert!(diff_c(&big1, &big2, &["0x1000".to_owned()], Some("0x1000"), false, &mut out).is_ok());
}
#[test]
fn c_marked_and_scoped_coverage() {
let code_ret: &[u8] = &[0xb8, 0x2a, 0x00, 0x00, 0x00, 0xc3]; let img_ret = make_img(code_ret, 0x1000..0x1006);
let rt = RuntimeMap::default();
let res_marked = c_one_marked(&img_ret, &[], &rt, 0x1000, None, None, Some(0x1005));
let (_, s_marked) = res_marked.unwrap_or_default();
assert!(s_marked.contains("/* @xref_slice_target */"), "{s_marked}");
let code_spill: &[u8] = &[0x48, 0x89, 0x7c, 0x24, 0x08, 0xc3];
let img_spill = make_img(code_spill, 0x1000..0x1006);
let (_, s_spill) = c_one(&img_spill, &[], &rt, 0x1000, None, None).unwrap_or_default();
assert!(s_spill.contains("fn_1000"), "{s_spill}");
let code_slot_unused: &[u8] = &[0x48, 0x89, 0xe3, 0xc3];
let img_slot_unused = make_img(code_slot_unused, 0x1000..0x1004);
let (_, s_unused) = c_one(&img_slot_unused, &[], &rt, 0x1000, None, None).unwrap_or_default();
assert!(s_unused.contains("fn_1000"), "{s_unused}");
let code_scope: &[u8] = &[
0xbb, 0x0a, 0x00, 0x00, 0x00, 0x85, 0xff, 0x74, 0x07, 0x89, 0xd8, 0xeb, 0x05, 0x90, 0x90, 0x90, 0x89, 0xd8, 0x83, 0xc0, 0x01, 0xc3, ];
let img_scope = make_img(code_scope, 0x1000..0x1016);
let (_, s_scope) = c_one(&img_scope, &[], &rt, 0x1000, None, None).unwrap_or_default();
assert!(s_scope.contains("fn_1000"), "{s_scope}");
}
#[test]
fn slice_fn_coverage() {
let code: &[u8] = &[
0x83, 0xff, 0x00, 0x74, 0x15, 0x83, 0xff, 0x01, 0x75, 0x06, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3, 0xb8, 0x05, 0x00, 0x00, 0x00, 0xc3, 0x90, 0x90, 0x90, 0x90, 0xb8, 0x00, 0x2a, 0x00, 0x00, 0xc3, ];
let len = u64::try_from(code.len()).unwrap_or_default();
let img = make_img(code, 0x1000..0x1000 + len);
let mut out = Vec::new();
let res = slice_fn(&img, "0x1000", "0x2a00", false, &mut out);
assert!(res.is_ok(), "{res:?}");
let s_slice = String::from_utf8_lossy(&out);
assert!(s_slice.contains("0x2a00"), "{s_slice}");
let code_call: &[u8] = &[
0x48, 0x83, 0xec, 0x08, 0xff, 0x15, 0x02, 0x10, 0x00, 0x00, 0x48, 0x83, 0xc4, 0x08, 0xc3,
];
let mut img_call = make_img(code_call, 0x1000..0x1000 + u64::try_from(code_call.len()).unwrap_or_default());
img_call.imports = vec![(0x200c, "malloc".to_owned())];
img_call.pointers = vec![(0x200c, 0x3000)];
out.clear();
assert!(slice_fn(&img_call, "0x1000", "malloc", false, &mut out).is_ok());
let s_call = String::from_utf8_lossy(&out);
assert!(s_call.contains("malloc"), "{s_call}");
}