From 8fcecee84908409a895110684dbad5446c0532c0 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sun, 11 Oct 2026 10:34:42 +0200 Subject: [PATCH] passes: rename rewrites invokedynamic descriptors and BootstrapMethods refs --- .../noxis-passes/src/rename/rewrite/code.rs | 14 ++- crates/noxis-passes/src/rename/rewrite/mod.rs | 58 +++++++++- .../noxis-passes/src/rename/tests/rewrite.rs | 100 ++++++++++++++++++ 3 files changed, 166 insertions(+), 6 deletions(-) diff --git a/crates/noxis-passes/src/rename/rewrite/code.rs b/crates/noxis-passes/src/rename/rewrite/code.rs index 1dd2f6c..461bf82 100644 --- a/crates/noxis-passes/src/rename/rewrite/code.rs +++ b/crates/noxis-passes/src/rename/rewrite/code.rs @@ -1,6 +1,6 @@ //! Переписывание тела метода: инструкции, кадры, handler'ы, атрибуты кода. -use noxis_classfile::{Code, Const, Insn}; +use noxis_classfile::{Code, Insn}; use noxis_core::{Config, Context}; use super::super::assign::{RenameMaps, rewrite_descriptor}; @@ -22,10 +22,8 @@ pub(super) fn rewrite_code( } for insn in code.insns.iter_mut() { match insn { - Insn::Const(Const::Class(name)) => { - if let Some(mapped) = maps.classes.get(name.as_str()) { - *name = mapped.clone(); - } + Insn::Const(value) => { + super::rewrite_const(value, maps); } Insn::GetStatic { owner, @@ -69,6 +67,12 @@ pub(super) fn rewrite_code( } *descriptor = rewrite_descriptor(descriptor, &maps.classes); } + // The call-site name belongs to the functional interface and + // stays; the descriptor (captured receiver types) is rewritten, + // and the bootstrap table entry is handled with the attributes. + Insn::InvokeDynamic { descriptor, .. } => { + *descriptor = rewrite_descriptor(descriptor, &maps.classes); + } Insn::New(name) => { if let Some(mapped) = maps.classes.get(name.as_str()) { *name = mapped.clone(); diff --git a/crates/noxis-passes/src/rename/rewrite/mod.rs b/crates/noxis-passes/src/rename/rewrite/mod.rs index 99a47f5..35507ef 100644 --- a/crates/noxis-passes/src/rename/rewrite/mod.rs +++ b/crates/noxis-passes/src/rename/rewrite/mod.rs @@ -2,7 +2,7 @@ mod code; -use noxis_classfile::{Attribute, ClassFile, StackMapFrame, VerificationType}; +use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType}; use noxis_core::{Config, Context}; use super::assign::{RenameMaps, rewrite_descriptor}; @@ -141,6 +141,14 @@ fn rewrite_attributes( } } } + Attribute::BootstrapMethods(methods) => { + for bsm in methods.iter_mut() { + rewrite_method_ref(&mut bsm.owner, &mut bsm.name, &mut bsm.descriptor, maps); + for arg in bsm.args.iter_mut() { + rewrite_const(arg, maps); + } + } + } Attribute::LocalVariableTable(vars) => { for var in vars.iter_mut() { var.descriptor = rewrite_descriptor(&var.descriptor, &maps.classes); @@ -164,6 +172,54 @@ 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. +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(); + } + *descriptor = rewrite_descriptor(descriptor, &maps.classes); +} + +/// A constant-pool reference inside code or bootstrap arguments: class +/// literals (plain or array), method types, method handles, condy payloads. +/// Anything else (numbers, strings) has no references to follow. +fn rewrite_const(value: &mut Const, maps: &RenameMaps) { + match value { + Const::Class(name) => { + if name.starts_with('[') { + *name = rewrite_descriptor(name, &maps.classes); + } else if let Some(mapped) = maps.classes.get(name.as_str()) { + *name = mapped.clone(); + } + } + Const::MethodType(descriptor) => { + *descriptor = rewrite_descriptor(descriptor, &maps.classes); + } + Const::MethodHandle { + owner, + name, + descriptor, + .. + } => { + rewrite_method_ref(owner, name, descriptor, maps); + } + Const::Dynamic { descriptor, .. } => { + *descriptor = rewrite_descriptor(descriptor, &maps.classes); + } + _ => {} + } +} + /// Имена классов внутри одного кадра (`Object(..)` в locals/stack). fn rewrite_frame(frame: &mut StackMapFrame, maps: &RenameMaps) { match frame { diff --git a/crates/noxis-passes/src/rename/tests/rewrite.rs b/crates/noxis-passes/src/rename/tests/rewrite.rs index 16bdaf1..3bc1eaf 100644 --- a/crates/noxis-passes/src/rename/tests/rewrite.rs +++ b/crates/noxis-passes/src/rename/tests/rewrite.rs @@ -139,3 +139,103 @@ fn permitted_subclasses_follow_the_mapping() { .expect("permitted"); assert_eq!(permitted, &vec![helper.to_string()]); } + +/// `invokedynamic` (lambdas) and `BootstrapMethods` follow the mapping: +/// a stale bootstrap owner breaks the lambda at first execution +/// (`NoClassDefFoundError` for the old class), and a stale invokedynamic +/// descriptor breaks the call site shape. Only descriptors move - the +/// invokedynamic name belongs to the functional interface. +#[test] +fn invokedynamic_and_bootstrap_follow_the_mapping() { + use noxis_classfile::{Attribute, BootstrapMethod, ClassBuilder, Const, Version}; + let mut program = sample(); + program.classes.push( + ClassBuilder::new("app/Util", Some("java/lang/Object")) + .version(Version::JAVA_21) + .method_with_code( + 0x000a, + "lambda$run$0", + "()V", + noxis_classfile::empty_code(0), + ) + .build(), + ); + let main = program + .classes + .iter_mut() + .find(|c| c.this_class == "app/Main") + .expect("main"); + main.attributes + .push(Attribute::BootstrapMethods(vec![BootstrapMethod { + kind: 6, + owner: "app/Util".to_string(), + name: "lambda$run$0".to_string(), + descriptor: "()V".to_string(), + itf: false, + args: vec![ + Const::MethodHandle { + kind: 6, + owner: "app/Util".to_string(), + name: "lambda$run$0".to_string(), + descriptor: "()V".to_string(), + itf: false, + }, + Const::Class("app/Util".to_string()), + Const::MethodType("(Lapp/Util;)V".to_string()), + ], + }])); + 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::InvokeDynamic { + name: "run".to_string(), + descriptor: "(Lapp/Util;)Ljava/lang/Runnable;".to_string(), + bootstrap: 0, + }); + code.insns.push(Insn::Pop); + let ctx = run_rename(&mut program); + let util = ctx.mapping.class("app/Util").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 dynamic = insns + .iter() + .find_map(|i| match i { + Insn::InvokeDynamic { + name, descriptor, .. + } => Some((name, descriptor)), + _ => None, + }) + .expect("invokedynamic"); + assert_eq!(dynamic.0, "run"); + assert_eq!(dynamic.1, &format!("(L{util};)Ljava/lang/Runnable;")); + let bootstrap = main + .attributes + .iter() + .find_map(|attr| match attr { + Attribute::BootstrapMethods(methods) => Some(methods), + _ => None, + }) + .expect("bootstrap"); + assert_eq!(bootstrap.len(), 1); + let entry = &bootstrap[0]; + assert_eq!(entry.owner, *util); + assert_ne!(entry.name, "lambda$run$0"); + assert_eq!(entry.descriptor, "()V"); + match &entry.args[0] { + Const::MethodHandle { owner, name, .. } => { + assert_eq!(owner, util); + assert_eq!(name, &entry.name); + } + other => panic!("method handle expected, got {other:?}"), + } + assert!(matches!(&entry.args[1], Const::Class(name) if name == util)); + assert!(matches!(&entry.args[2], Const::MethodType(desc) if desc == &format!("(L{util};)V"))); +}