classfile,passes: model runtime-visible annotations, rewrite their descriptors

This commit is contained in:
Loki 2026-10-11 13:02:26 +02:00
parent 4b14eeb40f
commit 48a5bc23a9
Signed by: boba
GPG key ID: 253067914055423B
13 changed files with 527 additions and 4 deletions

View file

@ -0,0 +1,41 @@
//! Annotation rewriting: class descriptors follow the mapping, string
//! values are data and stay put.
use noxis_classfile::{Annotation, AnnotationValue};
use super::super::assign::{RenameMaps, rewrite_descriptor};
/// A list of annotations (visible or invisible) - same rewrite for both.
pub(super) fn rewrite_annotations(annotations: &mut [Annotation], maps: &RenameMaps) {
for anno in annotations.iter_mut() {
rewrite_annotation(anno, maps);
}
}
fn rewrite_annotation(anno: &mut Annotation, maps: &RenameMaps) {
anno.descriptor = rewrite_descriptor(&anno.descriptor, &maps.classes);
for (_, value) in anno.elements.iter_mut() {
rewrite_value(value, maps);
}
}
fn rewrite_value(value: &mut AnnotationValue, maps: &RenameMaps) {
match value {
// The `'e'` tag: enum type descriptor moves, the constant name does not.
AnnotationValue::Enum { descriptor, .. } => {
*descriptor = rewrite_descriptor(descriptor, &maps.classes);
}
// The `'c'` tag: class-info descriptor (`LFoo;` or a primitive `I`).
AnnotationValue::Class(desc) => {
*desc = rewrite_descriptor(desc, &maps.classes);
}
AnnotationValue::Nested(anno) => rewrite_annotation(anno, maps),
AnnotationValue::Array(values) => {
for value in values.iter_mut() {
rewrite_value(value, maps);
}
}
// Scalars and strings hold no class references.
_ => {}
}
}

View file

@ -1,7 +1,9 @@
//! Переписывание одного класса: имена, дескрипторы, код, атрибуты.
mod annotations;
mod code;
use annotations::rewrite_annotations;
use noxis_classfile::{Attribute, ClassFile, Const, StackMapFrame, VerificationType};
use noxis_core::{Config, Context};
@ -141,6 +143,10 @@ fn rewrite_attributes(
}
}
}
Attribute::RuntimeVisibleAnnotations(annos)
| Attribute::RuntimeInvisibleAnnotations(annos) => {
rewrite_annotations(annos, maps);
}
Attribute::BootstrapMethods(methods) => {
for bsm in methods.iter_mut() {
rewrite_method_ref(&mut bsm.owner, &mut bsm.name, &mut bsm.descriptor, maps);

View file

@ -0,0 +1,100 @@
//! Annotation rewriting: descriptors follow the mapping, values do not.
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::run_rename;
use noxis_classfile::{Annotation, AnnotationValue, Attribute, ClassBuilder, Version};
/// Class references inside annotations live as descriptors: a stale one
/// makes the JVM's reflection parser fail (`AnnotationFormatError`) or
/// resolve a ghost type. String *values* are data and stay put.
#[test]
fn annotation_descriptors_follow_the_mapping() {
let mut program = super::sample();
program.classes.push(
ClassBuilder::new("app/Annotated", Some("java/lang/Object"))
.version(Version::JAVA_21)
.method_with_code(0x000a, "go", "()V", noxis_classfile::empty_code(0))
.build(),
);
let annotated = program
.classes
.iter_mut()
.find(|c| c.this_class == "app/Annotated")
.expect("annotated");
annotated
.attributes
.push(Attribute::RuntimeVisibleAnnotations(vec![Annotation {
descriptor: "Lapp/Helper;".to_string(),
elements: vec![
(
"kind".to_string(),
AnnotationValue::Enum {
descriptor: "Lapp/Helper;".to_string(),
name: "ONE".to_string(),
},
),
(
"klass".to_string(),
AnnotationValue::Class("Lapp/Helper;".to_string()),
),
(
"nested".to_string(),
AnnotationValue::Nested(Box::new(Annotation {
descriptor: "Lapp/Helper;".to_string(),
elements: vec![(
"note".to_string(),
AnnotationValue::String("app/Helper".to_string()),
)],
})),
),
(
"arr".to_string(),
AnnotationValue::Array(vec![AnnotationValue::Class(
"[Lapp/Helper;".to_string(),
)]),
),
],
}]));
let ctx = run_rename(&mut program);
let helper = ctx.mapping.class("app/Helper").expect("renamed");
let annotated_name = ctx.mapping.class("app/Annotated").expect("annotated");
let class = program.class(annotated_name).expect("class");
let annos = class
.attributes
.iter()
.find_map(|a| match a {
Attribute::RuntimeVisibleAnnotations(list) => Some(list),
_ => None,
})
.expect("annotations");
let anno = &annos[0];
assert_eq!(anno.descriptor, format!("L{helper};"));
let expect_desc = format!("L{helper};");
assert_eq!(
anno.elements[0].1,
AnnotationValue::Enum {
descriptor: expect_desc.clone(),
name: "ONE".to_string(),
}
);
assert_eq!(
anno.elements[1].1,
AnnotationValue::Class(expect_desc.clone())
);
match &anno.elements[2].1 {
AnnotationValue::Nested(nested) => {
assert_eq!(nested.descriptor, expect_desc);
assert_eq!(
nested.elements[0].1,
AnnotationValue::String("app/Helper".to_string()),
"string values are data, not references"
);
}
other => panic!("nested expected, got {other:?}"),
}
assert_eq!(
anno.elements[3].1,
AnnotationValue::Array(vec![AnnotationValue::Class(format!("[L{helper};"))])
);
}

View file

@ -13,6 +13,7 @@ use noxis_core::{Config, Context, Pass, RenameConfig};
use super::RenamePass;
use super::assign::{RenameMaps, rewrite_descriptor};
mod annotations;
mod enclosing;
mod rewrite;