203 lines
6.3 KiB
Rust
203 lines
6.3 KiB
Rust
//! Тесты пасса переименования: keep, ссылки, семейства, алиасы.
|
|
|
|
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use noxis_analysis::ClassHierarchy;
|
|
use noxis_classfile::{
|
|
ClassBuilder, ExceptionHandler, Insn, InvokeKind, Program, Resource, empty_code,
|
|
};
|
|
use noxis_core::{Config, Context, Pass, RenameConfig};
|
|
|
|
use super::RenamePass;
|
|
use super::assign::{RenameMaps, rewrite_descriptor};
|
|
|
|
mod rewrite;
|
|
|
|
fn sample() -> Program {
|
|
let main = ClassBuilder::new("app/Main", None)
|
|
.method_with_code(
|
|
0x0009,
|
|
"main",
|
|
"([Ljava/lang/String;)V",
|
|
noxis_classfile::Code {
|
|
max_stack: 4,
|
|
max_locals: 1,
|
|
insns: vec![
|
|
Insn::Invoke {
|
|
kind: InvokeKind::Static,
|
|
owner: "app/Helper".into(),
|
|
name: "make".into(),
|
|
descriptor: "()Lapp/Helper;".into(),
|
|
itf: false,
|
|
},
|
|
Insn::Pop,
|
|
Insn::Return(None),
|
|
],
|
|
exception_table: Vec::new(),
|
|
frames: Vec::new(),
|
|
attributes: Vec::new(),
|
|
},
|
|
)
|
|
.build();
|
|
let helper = ClassBuilder::new("app/Helper", None)
|
|
.method_with_code(
|
|
0x0009 | 0x0001,
|
|
"make",
|
|
"()Lapp/Helper;",
|
|
noxis_classfile::Code {
|
|
max_stack: 1,
|
|
max_locals: 0,
|
|
insns: vec![Insn::New("app/Helper".into()), Insn::Return(None)],
|
|
exception_table: Vec::new(),
|
|
frames: Vec::new(),
|
|
attributes: Vec::new(),
|
|
},
|
|
)
|
|
.method_with_code(0x0002, "secret", "()V", empty_code(0))
|
|
.build();
|
|
Program {
|
|
classes: vec![main, helper],
|
|
resources: vec![Resource {
|
|
path: "data.txt".into(),
|
|
data: b"x".to_vec(),
|
|
}],
|
|
}
|
|
}
|
|
|
|
fn run_rename(program: &mut Program) -> Context {
|
|
let config = Config {
|
|
seed: Some(9),
|
|
keep: vec!["app/Main".to_string()],
|
|
rename: RenameConfig {
|
|
package_pool: 4,
|
|
..RenameConfig::default()
|
|
},
|
|
..Config::default()
|
|
};
|
|
let mut ctx = Context::new(config);
|
|
let report = RenamePass.apply(&mut ctx, program).expect("rename");
|
|
assert!(report.transformed > 0, "ничего не переименовано");
|
|
ctx
|
|
}
|
|
|
|
#[test]
|
|
fn renames_unlocked_class_and_references() {
|
|
let mut program = sample();
|
|
let ctx = run_rename(&mut program);
|
|
|
|
assert_eq!(
|
|
program.class("app/Main").map(|c| c.this_class.as_str()),
|
|
Some("app/Main"),
|
|
"keep-класс обязан остаться"
|
|
);
|
|
let helper = program
|
|
.classes
|
|
.iter()
|
|
.find(|c| c.this_class != "app/Main")
|
|
.cloned()
|
|
.expect("helper");
|
|
assert_ne!(helper.this_class, "app/Helper");
|
|
|
|
let main = program.class("app/Main").expect("main");
|
|
let code = main.methods[0].code.as_ref().expect("code");
|
|
let (new_owner, new_desc) = match &code.insns[0] {
|
|
Insn::Invoke {
|
|
owner, descriptor, ..
|
|
} => (owner.clone(), descriptor.clone()),
|
|
other => panic!("ожидался Invoke, получено {other:?}"),
|
|
};
|
|
assert_eq!(new_owner, helper.this_class);
|
|
assert!(new_desc.contains(&helper.this_class), "desc: {new_desc}");
|
|
|
|
let renamed_secret = helper
|
|
.methods
|
|
.iter()
|
|
.find(|m| m.name != "make")
|
|
.map(|m| m.name.clone())
|
|
.expect("secret переименован");
|
|
assert_ne!(renamed_secret, "secret");
|
|
assert!(!ctx.mapping.methods.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn descriptor_rewrite_handles_arrays() {
|
|
let mut map = BTreeMap::new();
|
|
map.insert("a/b/C".to_string(), "p/Q".to_string());
|
|
map.insert("a/b/D".to_string(), "p/R".to_string());
|
|
assert_eq!(
|
|
rewrite_descriptor("(La/b/C;[La/b/D;)La/b/C;", &map),
|
|
"(Lp/Q;[Lp/R;)Lp/Q;"
|
|
);
|
|
assert_eq!(rewrite_descriptor("([[I)V", &map), "([[I)V");
|
|
map.insert("app/Helper".to_string(), "p/H".to_string());
|
|
assert_eq!(
|
|
rewrite_descriptor(
|
|
"Ljava/lang/Object;Ljava/lang/Comparable<Lapp/Helper;>;",
|
|
&map
|
|
),
|
|
"Ljava/lang/Object;Ljava/lang/Comparable<Lp/H;>;"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exception_handler_type_is_rewritten() {
|
|
let mut program = sample();
|
|
program
|
|
.classes
|
|
.push(ClassBuilder::new("app/Boom", None).build());
|
|
program.classes[1].methods[0]
|
|
.code
|
|
.as_mut()
|
|
.expect("code")
|
|
.exception_table
|
|
.push(ExceptionHandler {
|
|
start: 0,
|
|
end: 1,
|
|
handler: 2,
|
|
catch_type: Some("app/Boom".into()),
|
|
});
|
|
let ctx = run_rename(&mut program);
|
|
let new_boom = ctx.mapping.class("app/Boom").expect("boom renamed");
|
|
let helper = program
|
|
.classes
|
|
.iter()
|
|
.find(|c| c.this_class != "app/Main")
|
|
.expect("helper");
|
|
let handler = &helper.methods[0]
|
|
.code
|
|
.as_ref()
|
|
.expect("code")
|
|
.exception_table[0];
|
|
assert_eq!(handler.catch_type.as_deref(), Some(new_boom));
|
|
}
|
|
|
|
#[test]
|
|
fn subclass_gets_alias_for_inherited_member() {
|
|
let base = ClassBuilder::new("p/Base", None)
|
|
.method_with_code(0x0009, "shared", "()V", empty_code(0))
|
|
.build();
|
|
let sub = ClassBuilder::new("p/Sub", Some("p/Base")).build();
|
|
let program = Program {
|
|
classes: vec![base, sub],
|
|
resources: Vec::new(),
|
|
};
|
|
let hierarchy = ClassHierarchy::build(&program);
|
|
let mut maps = RenameMaps::default();
|
|
maps.methods.insert(
|
|
("p/Base".into(), "shared".into(), "()V".into()),
|
|
"zz".into(),
|
|
);
|
|
let owners = vec!["p/Base".to_string(), "p/Sub".to_string()];
|
|
maps.expand(&hierarchy, &owners);
|
|
|
|
// Вызов через подкласс обязан находить новое имя.
|
|
assert_eq!(
|
|
maps.method("p/Base", "shared", "()V").as_deref(),
|
|
Some("zz")
|
|
);
|
|
assert_eq!(maps.method("p/Sub", "shared", "()V").as_deref(), Some("zz"));
|
|
// Чужого члена не выдумываем.
|
|
assert_eq!(maps.method("p/Sub", "other", "()V"), None);
|
|
}
|