passes: rename rewrites invokedynamic descriptors and BootstrapMethods refs
This commit is contained in:
parent
9a33cef9b1
commit
8fcecee849
3 changed files with 166 additions and 6 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue