obfuskator/crates/noxis-passes/tests/debug_info.rs
loki5512344 6a68c9d69b
passes,core: debug fakes - line numbers and source removal
DebugConfig gains fake_line_numbers (deterministic upward walk
replacing LineNumberTable rows when stripping is off; strip wins)
and strip_source_file (drops SourceFile entirely, beating spoof).
Debug tests moved to passes/tests/debug_info.rs with new cases
for both flags and their precedence.
2026-10-09 14:08:08 +02:00

189 lines
5.6 KiB
Rust

//! Debug-info pass tests: strip, spoof, fake lines, source removal.
#![allow(clippy::expect_used, clippy::unwrap_used)]
use noxis_classfile::{
Attribute, ClassBuilder, Code, Insn, LineNumber, LocalVariable, Program, Resource, Version,
};
use noxis_core::{Config, Context, DebugConfig, Pass, PassReport, RenameConfig};
use noxis_passes::DebugInfoPass;
fn sample() -> Program {
let code = Code {
max_stack: 0,
max_locals: 1,
insns: vec![Insn::Return(None)],
exception_table: Vec::new(),
frames: Vec::new(),
attributes: vec![
Attribute::LineNumberTable(vec![
LineNumber { start: 0, line: 12 },
LineNumber { start: 0, line: 15 },
]),
Attribute::LocalVariableTable(vec![LocalVariable {
start: 0,
end: 0,
name: "this".into(),
descriptor: "Ldemo/App;".into(),
index: 0,
}]),
],
};
let mut class = ClassBuilder::new("demo/App", Some("java/lang/Object"))
.version(Version::JAVA_21)
.method_with_code(0x0001, "run", "()V", code)
.attribute(Attribute::SourceFile("RealApp.java".into()))
.attribute(Attribute::Signature("Ljava/lang/Object;".into()))
.attribute(Attribute::InnerClasses(Vec::new()))
.build();
class.methods[0].attributes.push(Attribute::Raw {
name: "MethodParameters".into(),
data: vec![],
});
Program {
classes: vec![class],
resources: vec![Resource {
path: "app.json".into(),
data: br#"{}"#.to_vec(),
}],
}
}
fn run(program: &mut Program, debug: DebugConfig) -> PassReport {
let config = Config {
seed: Some(7),
debug,
rename: RenameConfig {
class_style: noxis_core::NameStyle::ShortLatin,
..RenameConfig::default()
},
..Config::default()
};
let mut ctx = Context::new(config);
DebugInfoPass.apply(&mut ctx, program).expect("debug pass")
}
#[test]
fn default_config_strips_and_spoofs() {
let mut program = sample();
let report = run(&mut program, DebugConfig::default());
let class = &program.classes[0];
let code = class.methods[0].code.as_ref().expect("code");
assert!(code.attributes.is_empty(), "code attributes must go");
assert!(
class.methods[0].attributes.is_empty(),
"MethodParameters goes"
);
match class.attributes[0] {
Attribute::SourceFile(ref name) => assert_ne!(name, "RealApp.java"),
_ => panic!("SourceFile must come first"),
}
assert_eq!(class.attributes.len(), 3, "Signature/InnerClasses stay");
assert!(report.transformed > 0);
}
#[test]
fn signatures_and_parameters_survive_when_disabled() {
let debug = DebugConfig {
strip_line_numbers: false,
fake_line_numbers: false,
strip_local_variables: false,
spoof_source_file: false,
strip_source_file: false,
strip_signatures: false,
};
let mut program = sample();
run(&mut program, debug);
let class = &program.classes[0];
assert_eq!(class.methods[0].code.as_ref().unwrap().attributes.len(), 2);
assert_eq!(class.methods[0].attributes.len(), 1);
assert_eq!(class.attributes.len(), 3);
assert_eq!(
class.attributes[0],
Attribute::SourceFile("RealApp.java".into())
);
}
#[test]
fn same_seed_gives_the_same_spoofed_name() {
let mut first = sample();
let mut second = sample();
run(&mut first, DebugConfig::default());
run(&mut second, DebugConfig::default());
assert_eq!(first.classes[0].attributes, second.classes[0].attributes);
}
fn line_numbers(program: &Program) -> Vec<u16> {
let code = program.classes[0].methods[0].code.as_ref().expect("code");
code.attributes
.iter()
.find_map(|attr| match attr {
Attribute::LineNumberTable(rows) => Some(rows.iter().map(|r| r.line).collect()),
_ => None,
})
.unwrap_or_default()
}
/// Fake lines replace the table instead of removing it, deterministically.
#[test]
fn fake_line_numbers_replace_not_remove() {
let debug = DebugConfig {
strip_line_numbers: false,
fake_line_numbers: true,
..DebugConfig::default()
};
let mut first = sample();
let mut second = sample();
run(&mut first, debug.clone());
run(&mut second, debug);
for program in [&first, &second] {
let lines = line_numbers(program);
assert_eq!(lines.len(), 2, "table must survive with 2 rows");
assert_ne!(lines, vec![12, 15], "numbers must change");
assert!(lines.iter().all(|l| *l > 0), "numbers stay plausible");
}
assert_eq!(
line_numbers(&first),
line_numbers(&second),
"same seed, same fake lines"
);
}
/// Stripping the source removes the attribute even with spoofing on.
#[test]
fn strip_source_file_removes() {
let debug = DebugConfig {
strip_source_file: true,
..DebugConfig::default()
};
let mut program = sample();
run(&mut program, debug);
assert!(
program.classes[0]
.attributes
.iter()
.all(|attr| !matches!(attr, Attribute::SourceFile(_))),
"no SourceFile may survive"
);
}
/// Stripping wins over faking: a removed table has nothing to fake.
#[test]
fn strip_wins_over_fake() {
let debug = DebugConfig {
strip_line_numbers: true,
fake_line_numbers: true,
..DebugConfig::default()
};
let mut program = sample();
run(&mut program, debug);
assert!(line_numbers(&program).is_empty());
}