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'ы, атрибуты кода.
|
//! Переписывание тела метода: инструкции, кадры, handler'ы, атрибуты кода.
|
||||||
|
|
||||||
use noxis_classfile::{Code, Const, Insn};
|
use noxis_classfile::{Code, Insn};
|
||||||
use noxis_core::{Config, Context};
|
use noxis_core::{Config, Context};
|
||||||
|
|
||||||
use super::super::assign::{RenameMaps, rewrite_descriptor};
|
use super::super::assign::{RenameMaps, rewrite_descriptor};
|
||||||
|
|
@ -22,10 +22,8 @@ pub(super) fn rewrite_code(
|
||||||
}
|
}
|
||||||
for insn in code.insns.iter_mut() {
|
for insn in code.insns.iter_mut() {
|
||||||
match insn {
|
match insn {
|
||||||
Insn::Const(Const::Class(name)) => {
|
Insn::Const(value) => {
|
||||||
if let Some(mapped) = maps.classes.get(name.as_str()) {
|
super::rewrite_const(value, maps);
|
||||||
*name = mapped.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Insn::GetStatic {
|
Insn::GetStatic {
|
||||||
owner,
|
owner,
|
||||||
|
|
@ -69,6 +67,12 @@ pub(super) fn rewrite_code(
|
||||||
}
|
}
|
||||||
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
|
*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) => {
|
Insn::New(name) => {
|
||||||
if let Some(mapped) = maps.classes.get(name.as_str()) {
|
if let Some(mapped) = maps.classes.get(name.as_str()) {
|
||||||
*name = mapped.clone();
|
*name = mapped.clone();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
mod code;
|
mod code;
|
||||||
|
|
||||||
use noxis_classfile::{Attribute, ClassFile, StackMapFrame, VerificationType};
|
use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType};
|
||||||
use noxis_core::{Config, Context};
|
use noxis_core::{Config, Context};
|
||||||
|
|
||||||
use super::assign::{RenameMaps, rewrite_descriptor};
|
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) => {
|
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);
|
||||||
|
|
@ -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).
|
/// Имена классов внутри одного кадра (`Object(..)` в locals/stack).
|
||||||
fn rewrite_frame(frame: &mut StackMapFrame, maps: &RenameMaps) {
|
fn rewrite_frame(frame: &mut StackMapFrame, maps: &RenameMaps) {
|
||||||
match frame {
|
match frame {
|
||||||
|
|
|
||||||
|
|
@ -139,3 +139,103 @@ fn permitted_subclasses_follow_the_mapping() {
|
||||||
.expect("permitted");
|
.expect("permitted");
|
||||||
assert_eq!(permitted, &vec![helper.to_string()]);
|
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