From 5901c9eb69ce76ff14653ab97a730e7edaa505fd Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 11 Oct 2026 11:22:11 +0200 Subject: [PATCH] passes: rename rewrites EnclosingMethod and array-owner method refs --- .../noxis-passes/src/rename/rewrite/code.rs | 9 +- crates/noxis-passes/src/rename/rewrite/mod.rs | 25 +++-- .../src/rename/tests/enclosing.rs | 94 +++++++++++++++++++ crates/noxis-passes/src/rename/tests/mod.rs | 1 + 4 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 crates/noxis-passes/src/rename/tests/enclosing.rs diff --git a/crates/noxis-passes/src/rename/rewrite/code.rs b/crates/noxis-passes/src/rename/rewrite/code.rs index 461bf82..6b08abf 100644 --- a/crates/noxis-passes/src/rename/rewrite/code.rs +++ b/crates/noxis-passes/src/rename/rewrite/code.rs @@ -6,6 +6,7 @@ use noxis_core::{Config, Context}; use super::super::assign::{RenameMaps, rewrite_descriptor}; use super::rewrite_attributes; use super::rewrite_frame; +use super::rewrite_method_ref; pub(super) fn rewrite_code( code: &mut Code, @@ -59,13 +60,7 @@ pub(super) fn rewrite_code( descriptor, .. } => { - if let Some(new_name) = maps.method(owner, name, descriptor) { - *name = new_name; - } - if let Some(mapped) = maps.classes.get(owner.as_str()) { - *owner = mapped.clone(); - } - *descriptor = rewrite_descriptor(descriptor, &maps.classes); + rewrite_method_ref(owner, name, descriptor, maps); } // The call-site name belongs to the functional interface and // stays; the descriptor (captured receiver types) is rewritten, diff --git a/crates/noxis-passes/src/rename/rewrite/mod.rs b/crates/noxis-passes/src/rename/rewrite/mod.rs index 35507ef..2287c59 100644 --- a/crates/noxis-passes/src/rename/rewrite/mod.rs +++ b/crates/noxis-passes/src/rename/rewrite/mod.rs @@ -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) => { for var in vars.iter_mut() { 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 /// 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( owner: &mut String, name: &mut String, descriptor: &mut String, maps: &RenameMaps, ) { - if let Some(new_name) = maps.method(owner, name, descriptor) { - *name = new_name; - } - if let Some(mapped) = maps.classes.get(owner.as_str()) { - *owner = mapped.clone(); + if owner.starts_with('[') { + *owner = rewrite_descriptor(owner, &maps.classes); + } else { + if let Some(new_name) = maps.method(owner, name, descriptor) { + *name = new_name; + } + if let Some(mapped) = maps.classes.get(owner.as_str()) { + *owner = mapped.clone(); + } } *descriptor = rewrite_descriptor(descriptor, &maps.classes); } diff --git a/crates/noxis-passes/src/rename/tests/enclosing.rs b/crates/noxis-passes/src/rename/tests/enclosing.rs new file mode 100644 index 0000000..3cd55b5 --- /dev/null +++ b/crates/noxis-passes/src/rename/tests/enclosing.rs @@ -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};")); +} diff --git a/crates/noxis-passes/src/rename/tests/mod.rs b/crates/noxis-passes/src/rename/tests/mod.rs index 5cc4a41..695c972 100644 --- a/crates/noxis-passes/src/rename/tests/mod.rs +++ b/crates/noxis-passes/src/rename/tests/mod.rs @@ -13,6 +13,7 @@ use noxis_core::{Config, Context, Pass, RenameConfig}; use super::RenamePass; use super::assign::{RenameMaps, rewrite_descriptor}; +mod enclosing; mod rewrite; fn sample() -> Program {