passes: generic signatures rewrite nested references; rename tests split
This commit is contained in:
parent
63630dd223
commit
2655f83d3c
3 changed files with 130 additions and 76 deletions
|
|
@ -196,6 +196,11 @@ pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewrite a descriptor/signature: `Lpkg/Class;` → the new class name.
|
/// Rewrite a descriptor/signature: `Lpkg/Class;` → the new class name.
|
||||||
|
///
|
||||||
|
/// Generic signatures nest (`LMap<LString;LFoo;>;`): the name scan stops at
|
||||||
|
/// `<` as well as `;`, so type arguments fall back into the main loop and
|
||||||
|
/// get rewritten in turn. Stopping only at `;` swallowed the inner
|
||||||
|
/// reference into one unmapped blob (a stale `Comparable<Old>` leak).
|
||||||
pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String {
|
pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String {
|
||||||
if !desc.contains('L') {
|
if !desc.contains('L') {
|
||||||
return desc.to_string();
|
return desc.to_string();
|
||||||
|
|
@ -208,15 +213,17 @@ pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
|
let mut term = ';';
|
||||||
for c in chars.by_ref() {
|
for c in chars.by_ref() {
|
||||||
if c == ';' {
|
if c == ';' || c == '<' {
|
||||||
|
term = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
name.push(c);
|
name.push(c);
|
||||||
}
|
}
|
||||||
out.push('L');
|
out.push('L');
|
||||||
out.push_str(classes.get(&name).map_or(name.as_str(), String::as_str));
|
out.push_str(classes.get(&name).map_or(name.as_str(), String::as_str));
|
||||||
out.push(';');
|
out.push(term);
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ use noxis_core::{Config, Context, Pass, RenameConfig};
|
||||||
use super::RenamePass;
|
use super::RenamePass;
|
||||||
use super::assign::{RenameMaps, rewrite_descriptor};
|
use super::assign::{RenameMaps, rewrite_descriptor};
|
||||||
|
|
||||||
|
mod rewrite;
|
||||||
|
|
||||||
fn sample() -> Program {
|
fn sample() -> Program {
|
||||||
let main = ClassBuilder::new("app/Main", None)
|
let main = ClassBuilder::new("app/Main", None)
|
||||||
.method_with_code(
|
.method_with_code(
|
||||||
|
|
@ -129,6 +131,14 @@ fn descriptor_rewrite_handles_arrays() {
|
||||||
"(Lp/Q;[Lp/R;)Lp/Q;"
|
"(Lp/Q;[Lp/R;)Lp/Q;"
|
||||||
);
|
);
|
||||||
assert_eq!(rewrite_descriptor("([[I)V", &map), "([[I)V");
|
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]
|
#[test]
|
||||||
|
|
@ -191,77 +201,3 @@ fn subclass_gets_alias_for_inherited_member() {
|
||||||
// Чужого члена не выдумываем.
|
// Чужого члена не выдумываем.
|
||||||
assert_eq!(maps.method("p/Sub", "other", "()V"), None);
|
assert_eq!(maps.method("p/Sub", "other", "()V"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Array type operands (`checkcast`, `instanceof`, `anewarray`,
|
|
||||||
/// `multianewarray`) carry descriptors, not plain names: the element class
|
|
||||||
/// inside must follow the mapping, or the reference silently goes stale
|
|
||||||
/// (and the next frame computation merges against a ghost type).
|
|
||||||
#[test]
|
|
||||||
fn array_type_operands_follow_their_element() {
|
|
||||||
let mut program = sample();
|
|
||||||
let main = program
|
|
||||||
.classes
|
|
||||||
.iter_mut()
|
|
||||||
.find(|c| c.this_class == "app/Main")
|
|
||||||
.expect("main");
|
|
||||||
let probe = main
|
|
||||||
.methods
|
|
||||||
.iter_mut()
|
|
||||||
.find(|m| m.name == "main")
|
|
||||||
.expect("main method");
|
|
||||||
let code = probe.code.as_mut().expect("code");
|
|
||||||
code.insns.extend([
|
|
||||||
Insn::CheckCast("[Lapp/Helper;".to_string()),
|
|
||||||
Insn::Pop,
|
|
||||||
Insn::InstanceOf("[Lapp/Helper;".to_string()),
|
|
||||||
Insn::Pop,
|
|
||||||
Insn::ANewArray("app/Helper".to_string()),
|
|
||||||
Insn::Pop,
|
|
||||||
Insn::MultiANewArray {
|
|
||||||
descriptor: "[[Lapp/Helper;".to_string(),
|
|
||||||
dims: 2,
|
|
||||||
},
|
|
||||||
Insn::Pop,
|
|
||||||
]);
|
|
||||||
let ctx = run_rename(&mut program);
|
|
||||||
let helper = ctx.mapping.class("app/Helper").expect("renamed");
|
|
||||||
let main = program.class("app/Main").expect("kept");
|
|
||||||
let method = main
|
|
||||||
.methods
|
|
||||||
.iter()
|
|
||||||
.find(|m| m.name == "main")
|
|
||||||
.expect("main");
|
|
||||||
let insns = &method.code.as_ref().expect("code").insns;
|
|
||||||
let cast = insns
|
|
||||||
.iter()
|
|
||||||
.find_map(|i| match i {
|
|
||||||
Insn::CheckCast(name) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.expect("checkcast");
|
|
||||||
assert_eq!(cast, &format!("[L{helper};"));
|
|
||||||
let inst = insns
|
|
||||||
.iter()
|
|
||||||
.find_map(|i| match i {
|
|
||||||
Insn::InstanceOf(name) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.expect("instanceof");
|
|
||||||
assert_eq!(inst, &format!("[L{helper};"));
|
|
||||||
let anew = insns
|
|
||||||
.iter()
|
|
||||||
.find_map(|i| match i {
|
|
||||||
Insn::ANewArray(name) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.expect("anewarray");
|
|
||||||
assert_eq!(anew, helper);
|
|
||||||
let multi = insns
|
|
||||||
.iter()
|
|
||||||
.find_map(|i| match i {
|
|
||||||
Insn::MultiANewArray { descriptor, .. } => Some(descriptor),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.expect("multianewarray");
|
|
||||||
assert_eq!(multi, &format!("[[L{helper};"));
|
|
||||||
}
|
|
||||||
111
crates/noxis-passes/src/rename/tests/rewrite.rs
Normal file
111
crates/noxis-passes/src/rename/tests/rewrite.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
//! Reference rewriting: array operands and generic signatures.
|
||||||
|
|
||||||
|
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||||
|
|
||||||
|
use super::{run_rename, sample};
|
||||||
|
use noxis_classfile::Insn;
|
||||||
|
/// Array type operands (`checkcast`, `instanceof`, `anewarray`,
|
||||||
|
/// `multianewarray`) carry descriptors, not plain names: the element class
|
||||||
|
/// inside must follow the mapping, or the reference silently goes stale
|
||||||
|
/// (and the next frame computation merges against a ghost type).
|
||||||
|
#[test]
|
||||||
|
fn array_type_operands_follow_their_element() {
|
||||||
|
let mut program = sample();
|
||||||
|
let main = program
|
||||||
|
.classes
|
||||||
|
.iter_mut()
|
||||||
|
.find(|c| c.this_class == "app/Main")
|
||||||
|
.expect("main");
|
||||||
|
let probe = main
|
||||||
|
.methods
|
||||||
|
.iter_mut()
|
||||||
|
.find(|m| m.name == "main")
|
||||||
|
.expect("main method");
|
||||||
|
let code = probe.code.as_mut().expect("code");
|
||||||
|
code.insns.extend([
|
||||||
|
Insn::CheckCast("[Lapp/Helper;".to_string()),
|
||||||
|
Insn::Pop,
|
||||||
|
Insn::InstanceOf("[Lapp/Helper;".to_string()),
|
||||||
|
Insn::Pop,
|
||||||
|
Insn::ANewArray("app/Helper".to_string()),
|
||||||
|
Insn::Pop,
|
||||||
|
Insn::MultiANewArray {
|
||||||
|
descriptor: "[[Lapp/Helper;".to_string(),
|
||||||
|
dims: 2,
|
||||||
|
},
|
||||||
|
Insn::Pop,
|
||||||
|
]);
|
||||||
|
let ctx = run_rename(&mut program);
|
||||||
|
let helper = ctx.mapping.class("app/Helper").expect("renamed");
|
||||||
|
let main = program.class("app/Main").expect("kept");
|
||||||
|
let method = main
|
||||||
|
.methods
|
||||||
|
.iter()
|
||||||
|
.find(|m| m.name == "main")
|
||||||
|
.expect("main");
|
||||||
|
let insns = &method.code.as_ref().expect("code").insns;
|
||||||
|
let cast = insns
|
||||||
|
.iter()
|
||||||
|
.find_map(|i| match i {
|
||||||
|
Insn::CheckCast(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("checkcast");
|
||||||
|
assert_eq!(cast, &format!("[L{helper};"));
|
||||||
|
let inst = insns
|
||||||
|
.iter()
|
||||||
|
.find_map(|i| match i {
|
||||||
|
Insn::InstanceOf(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("instanceof");
|
||||||
|
assert_eq!(inst, &format!("[L{helper};"));
|
||||||
|
let anew = insns
|
||||||
|
.iter()
|
||||||
|
.find_map(|i| match i {
|
||||||
|
Insn::ANewArray(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("anewarray");
|
||||||
|
assert_eq!(anew, helper);
|
||||||
|
let multi = insns
|
||||||
|
.iter()
|
||||||
|
.find_map(|i| match i {
|
||||||
|
Insn::MultiANewArray { descriptor, .. } => Some(descriptor),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("multianewarray");
|
||||||
|
assert_eq!(multi, &format!("[[L{helper};"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic `Signature`s rewrite every referenced class, not just the first:
|
||||||
|
/// a stale `Comparable<Old>` leaks the original name and breaks reflective
|
||||||
|
/// generic resolution at runtime.
|
||||||
|
#[test]
|
||||||
|
fn generic_signatures_rewrite_all_references() {
|
||||||
|
use noxis_classfile::Attribute;
|
||||||
|
let mut program = sample();
|
||||||
|
let main = program
|
||||||
|
.classes
|
||||||
|
.iter_mut()
|
||||||
|
.find(|c| c.this_class == "app/Main")
|
||||||
|
.expect("main");
|
||||||
|
main.attributes.push(Attribute::Signature(
|
||||||
|
"Ljava/lang/Object;Ljava/lang/Comparable<Lapp/Helper;>;".to_string(),
|
||||||
|
));
|
||||||
|
let ctx = run_rename(&mut program);
|
||||||
|
let helper = ctx.mapping.class("app/Helper").expect("renamed");
|
||||||
|
let main = program.class("app/Main").expect("kept");
|
||||||
|
let signature = main
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.find_map(|attr| match attr {
|
||||||
|
Attribute::Signature(text) => Some(text),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("signature");
|
||||||
|
assert_eq!(
|
||||||
|
signature,
|
||||||
|
&format!("Ljava/lang/Object;Ljava/lang/Comparable<L{helper};>;")
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue