passes: rename rewrites invokedynamic descriptors and BootstrapMethods refs

This commit is contained in:
Loki 2026-10-11 10:34:42 +02:00
parent 9a33cef9b1
commit 8fcecee849
Signed by: boba
GPG key ID: 253067914055423B
3 changed files with 166 additions and 6 deletions

View file

@ -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();

View file

@ -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 {

View file

@ -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")));
}