passes: rename rewrites EnclosingMethod and array-owner method refs

This commit is contained in:
Loki 2026-10-11 11:22:11 +02:00
parent 8fcecee849
commit 5901c9eb69
Signed by: boba
GPG key ID: 253067914055423B
4 changed files with 116 additions and 13 deletions

View file

@ -6,6 +6,7 @@ use noxis_core::{Config, Context};
use super::super::assign::{RenameMaps, rewrite_descriptor}; use super::super::assign::{RenameMaps, rewrite_descriptor};
use super::rewrite_attributes; use super::rewrite_attributes;
use super::rewrite_frame; use super::rewrite_frame;
use super::rewrite_method_ref;
pub(super) fn rewrite_code( pub(super) fn rewrite_code(
code: &mut Code, code: &mut Code,
@ -59,13 +60,7 @@ pub(super) fn rewrite_code(
descriptor, descriptor,
.. ..
} => { } => {
if let Some(new_name) = maps.method(owner, name, descriptor) { rewrite_method_ref(owner, name, descriptor, maps);
*name = new_name;
}
if let Some(mapped) = maps.classes.get(owner.as_str()) {
*owner = mapped.clone();
}
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
} }
// The call-site name belongs to the functional interface and // The call-site name belongs to the functional interface and
// stays; the descriptor (captured receiver types) is rewritten, // stays; the descriptor (captured receiver types) is rewritten,

View file

@ -149,6 +149,14 @@ fn rewrite_attributes(
} }
} }
} }
Attribute::EnclosingMethod { class, method } => {
if let Some((name, descriptor)) = method.as_mut() {
rewrite_method_ref(class, name, descriptor, maps);
}
if let Some(mapped) = maps.classes.get(class.as_str()) {
*class = mapped.clone();
}
}
Attribute::LocalVariableTable(vars) => { Attribute::LocalVariableTable(vars) => {
for var in vars.iter_mut() { for var in vars.iter_mut() {
var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes); var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes);
@ -174,18 +182,23 @@ fn rewrite_attributes(
/// A method reference (invoke/bootstrap/method-handle): the owner follows /// A method reference (invoke/bootstrap/method-handle): the owner follows
/// class renames, the name follows member renames, the descriptor is /// class renames, the name follows member renames, the descriptor is
/// rewritten. Callers decide whether the name may move at all. /// rewritten. Array owners (`[Lpkg/C;` - e.g. `clone` on arrays) name the
/// element class inside a descriptor, so they take the descriptor path.
fn rewrite_method_ref( fn rewrite_method_ref(
owner: &mut String, owner: &mut String,
name: &mut String, name: &mut String,
descriptor: &mut String, descriptor: &mut String,
maps: &RenameMaps, maps: &RenameMaps,
) { ) {
if let Some(new_name) = maps.method(owner, name, descriptor) { if owner.starts_with('[') {
*name = new_name; *owner = rewrite_descriptor(owner, &maps.classes);
} } else {
if let Some(mapped) = maps.classes.get(owner.as_str()) { if let Some(new_name) = maps.method(owner, name, descriptor) {
*owner = mapped.clone(); *name = new_name;
}
if let Some(mapped) = maps.classes.get(owner.as_str()) {
*owner = mapped.clone();
}
} }
*descriptor = rewrite_descriptor(descriptor, &maps.classes); *descriptor = rewrite_descriptor(descriptor, &maps.classes);
} }

View file

@ -0,0 +1,94 @@
//! Local-class attributes (`EnclosingMethod`) and array-owner references.
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::{run_rename, sample};
use noxis_classfile::Insn;
/// `EnclosingMethod` (local/anonymous classes) keeps a class + optional
/// method pair: a stale class here breaks reflection on the local class
/// and, with certain enum patterns, class initialization itself.
#[test]
fn enclosing_method_follows_the_mapping() {
use noxis_classfile::{Attribute, ClassBuilder, Version};
let mut program = sample();
program.classes.push(
ClassBuilder::new("app/Local", Some("java/lang/Object"))
.version(Version::JAVA_21)
.method_with_code(0x000a, "go", "()V", noxis_classfile::empty_code(0))
.build(),
);
let local = program
.classes
.iter_mut()
.find(|c| c.this_class == "app/Local")
.expect("local");
local.attributes.push(Attribute::EnclosingMethod {
class: "app/Helper".to_string(),
method: Some(("make".to_string(), "()Lapp/Helper;".to_string())),
});
let ctx = run_rename(&mut program);
let helper = ctx.mapping.class("app/Helper").expect("renamed");
let local_name = ctx.mapping.class("app/Local").expect("local renamed");
let local = program.class(local_name).expect("renamed class");
let attr = local
.attributes
.iter()
.find_map(|a| match a {
Attribute::EnclosingMethod { class, method } => Some((class, method)),
_ => None,
})
.expect("enclosing");
assert_eq!(attr.0, helper);
let (name, desc) = attr.1.as_ref().expect("method");
assert_ne!(name, "make");
assert_eq!(desc, &format!("()L{helper};"));
}
/// `invokevirtual` on an array (`[Lpkg/C;`.clone) names the *array* class
/// as owner: the mapping key is the element class, so a plain lookup misses
/// and the array component goes stale - enum `values()` clones break.
#[test]
fn array_owner_method_refs_follow_their_element() {
use noxis_classfile::InvokeKind;
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.push(Insn::Invoke {
kind: InvokeKind::Virtual,
owner: "[Lapp/Helper;".to_string(),
name: "clone".to_string(),
descriptor: "()Ljava/lang/Object;".to_string(),
itf: false,
});
code.insns.push(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 invoke = method
.code
.as_ref()
.expect("code")
.insns
.iter()
.find_map(|i| match i {
Insn::Invoke { owner, name, .. } if name == "clone" => Some(owner),
_ => None,
})
.expect("invoke");
assert_eq!(invoke, &format!("[L{helper};"));
}

View file

@ -13,6 +13,7 @@ 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 enclosing;
mod rewrite; mod rewrite;
fn sample() -> Program { fn sample() -> Program {